mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-25 17:43:18 +00:00
Star reaction improvements
This commit is contained in:
parent
b7f4fe2c9e
commit
03eeb3515c
@ -1524,7 +1524,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
case let .done(receiptMessageId):
|
case let .done(receiptMessageId, _):
|
||||||
proceedWithCompletion(true, receiptMessageId)
|
proceedWithCompletion(true, receiptMessageId)
|
||||||
case let .externalVerificationRequired(url):
|
case let .externalVerificationRequired(url):
|
||||||
strongSelf.updateActionButton()
|
strongSelf.updateActionButton()
|
||||||
|
|||||||
@ -345,7 +345,7 @@ public struct ComponentTransition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setPosition(view: UIView, position: CGPoint, completion: ((Bool) -> Void)? = nil) {
|
public func setPosition(view: UIView, position: CGPoint, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
|
||||||
if view.center == position {
|
if view.center == position {
|
||||||
completion?(true)
|
completion?(true)
|
||||||
return
|
return
|
||||||
@ -364,7 +364,7 @@ public struct ComponentTransition {
|
|||||||
}
|
}
|
||||||
view.center = position
|
view.center = position
|
||||||
|
|
||||||
self.animatePosition(view: view, from: previousPosition, to: view.center, completion: completion)
|
self.animatePosition(view: view, from: previousPosition, to: view.center, delay: delay, completion: completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -803,8 +803,8 @@ public struct ComponentTransition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func animatePosition(view: UIView, from fromValue: CGPoint, to toValue: CGPoint, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
public func animatePosition(view: UIView, from fromValue: CGPoint, to toValue: CGPoint, delay: Double = 0.0, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||||
self.animatePosition(layer: view.layer, from: fromValue, to: toValue, additive: additive, completion: completion)
|
self.animatePosition(layer: view.layer, from: fromValue, to: toValue, delay: delay, additive: additive, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func animateBounds(view: UIView, from fromValue: CGRect, to toValue: CGRect, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
public func animateBounds(view: UIView, from fromValue: CGRect, to toValue: CGRect, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||||
@ -819,7 +819,7 @@ public struct ComponentTransition {
|
|||||||
self.animateBoundsSize(layer: view.layer, from: fromValue, to: toValue, additive: additive, completion: completion)
|
self.animateBoundsSize(layer: view.layer, from: fromValue, to: toValue, additive: additive, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func animatePosition(layer: CALayer, from fromValue: CGPoint, to toValue: CGPoint, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
public func animatePosition(layer: CALayer, from fromValue: CGPoint, to toValue: CGPoint, delay: Double = 0.0, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||||
switch self.animation {
|
switch self.animation {
|
||||||
case .none:
|
case .none:
|
||||||
completion?(true)
|
completion?(true)
|
||||||
@ -829,7 +829,7 @@ public struct ComponentTransition {
|
|||||||
to: NSValue(cgPoint: toValue),
|
to: NSValue(cgPoint: toValue),
|
||||||
keyPath: "position",
|
keyPath: "position",
|
||||||
duration: duration,
|
duration: duration,
|
||||||
delay: 0.0,
|
delay: delay,
|
||||||
curve: curve,
|
curve: curve,
|
||||||
removeOnCompletion: true,
|
removeOnCompletion: true,
|
||||||
additive: additive,
|
additive: additive,
|
||||||
|
|||||||
@ -198,6 +198,28 @@ public func sendStarsReactionsInteractively(account: Account, messageId: Message
|
|||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cancelPendingSendStarsReactionInteractively(account: Account, messageId: MessageId) -> Signal<Never, NoError> {
|
||||||
|
return account.postbox.transaction { transaction -> Void in
|
||||||
|
transaction.setPendingMessageAction(type: .sendStarsReaction, id: messageId, action: nil)
|
||||||
|
transaction.updateMessage(messageId, update: { currentMessage in
|
||||||
|
var storeForwardInfo: StoreMessageForwardInfo?
|
||||||
|
if let forwardInfo = currentMessage.forwardInfo {
|
||||||
|
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
|
||||||
|
}
|
||||||
|
var attributes = currentMessage.attributes
|
||||||
|
loop: for j in 0 ..< attributes.count {
|
||||||
|
if let _ = attributes[j] as? PendingStarsReactionsMessageAttribute {
|
||||||
|
attributes.remove(at: j)
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
|
|
||||||
private enum RequestUpdateMessageReactionError {
|
private enum RequestUpdateMessageReactionError {
|
||||||
case generic
|
case generic
|
||||||
}
|
}
|
||||||
|
|||||||
@ -338,6 +338,10 @@ public extension TelegramEngine {
|
|||||||
let _ = sendStarsReactionsInteractively(account: self.account, messageId: id, count: count).startStandalone()
|
let _ = sendStarsReactionsInteractively(account: self.account, messageId: id, count: count).startStandalone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func cancelPendingSendStarsReaction(id: EngineMessage.Id) {
|
||||||
|
let _ = cancelPendingSendStarsReactionInteractively(account: self.account, messageId: id).startStandalone()
|
||||||
|
}
|
||||||
|
|
||||||
public func requestChatContextResults(botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String, incompleteResults: Bool = false, staleCachedResults: Bool = false) -> Signal<RequestChatContextResultsResult?, RequestChatContextResultsError> {
|
public func requestChatContextResults(botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String, incompleteResults: Bool = false, staleCachedResults: Bool = false) -> Signal<RequestChatContextResultsResult?, RequestChatContextResultsError> {
|
||||||
return _internal_requestChatContextResults(account: self.account, botId: botId, peerId: peerId, query: query, location: location, offset: offset, incompleteResults: incompleteResults, staleCachedResults: staleCachedResults)
|
return _internal_requestChatContextResults(account: self.account, botId: botId, peerId: peerId, query: query, location: location, offset: offset, incompleteResults: incompleteResults, staleCachedResults: staleCachedResults)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -76,6 +76,8 @@ public final class AnimatedTextComponent: Component {
|
|||||||
|
|
||||||
let delayNorm: CGFloat = 0.002
|
let delayNorm: CGFloat = 0.002
|
||||||
|
|
||||||
|
var firstDelayWidth: CGFloat?
|
||||||
|
|
||||||
var validKeys: [CharacterKey] = []
|
var validKeys: [CharacterKey] = []
|
||||||
for item in component.items {
|
for item in component.items {
|
||||||
var itemText: [String] = []
|
var itemText: [String] = []
|
||||||
@ -138,20 +140,32 @@ public final class AnimatedTextComponent: Component {
|
|||||||
if characterTransition.animation.isImmediate {
|
if characterTransition.animation.isImmediate {
|
||||||
characterComponentView.frame = characterFrame
|
characterComponentView.frame = characterFrame
|
||||||
} else {
|
} else {
|
||||||
|
var delayWidth: Double = 0.0
|
||||||
|
if let firstDelayWidth {
|
||||||
|
delayWidth = size.width - firstDelayWidth
|
||||||
|
} else {
|
||||||
|
firstDelayWidth = size.width
|
||||||
|
}
|
||||||
|
|
||||||
characterComponentView.bounds = CGRect(origin: CGPoint(), size: characterFrame.size)
|
characterComponentView.bounds = CGRect(origin: CGPoint(), size: characterFrame.size)
|
||||||
let deltaPosition = CGPoint(x: characterFrame.midX - characterComponentView.frame.midX, y: characterFrame.midY - characterComponentView.frame.midY)
|
let deltaPosition = CGPoint(x: characterFrame.midX - characterComponentView.frame.midX, y: characterFrame.midY - characterComponentView.frame.midY)
|
||||||
characterComponentView.center = characterFrame.center
|
characterComponentView.center = characterFrame.center
|
||||||
characterComponentView.layer.animatePosition(from: CGPoint(x: -deltaPosition.x, y: -deltaPosition.y), to: CGPoint(), duration: 0.4, delay: delayNorm * size.width, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
characterComponentView.layer.animatePosition(from: CGPoint(x: -deltaPosition.x, y: -deltaPosition.y), to: CGPoint(), duration: 0.4, delay: delayNorm * delayWidth, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
characterTransition.setFrame(view: characterComponentView, frame: characterFrame)
|
characterTransition.setFrame(view: characterComponentView, frame: characterFrame)
|
||||||
|
|
||||||
|
|
||||||
if animateIn, !transition.animation.isImmediate {
|
if animateIn, !transition.animation.isImmediate {
|
||||||
characterComponentView.layer.animateScale(from: 0.001, to: 1.0, duration: 0.4, delay: delayNorm * size.width, timingFunction: kCAMediaTimingFunctionSpring)
|
var delayWidth: Double = 0.0
|
||||||
//characterComponentView.layer.animateSpring(from: (characterSize.height * 0.5) as NSNumber, to: 0.0 as NSNumber, keyPath: "position.y", duration: 0.5, additive: true)
|
if let firstDelayWidth {
|
||||||
characterComponentView.layer.animatePosition(from: CGPoint(x: 0.0, y: characterSize.height * 0.5), to: CGPoint(), duration: 0.4, delay: delayNorm * size.width, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
delayWidth = size.width - firstDelayWidth
|
||||||
characterComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18, delay: delayNorm * size.width)
|
} else {
|
||||||
|
firstDelayWidth = size.width
|
||||||
|
}
|
||||||
|
|
||||||
|
characterComponentView.layer.animateScale(from: 0.001, to: 1.0, duration: 0.4, delay: delayNorm * delayWidth, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
characterComponentView.layer.animatePosition(from: CGPoint(x: 0.0, y: characterSize.height * 0.5), to: CGPoint(), duration: 0.4, delay: delayNorm * delayWidth, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
|
characterComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18, delay: delayNorm * delayWidth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,6 +174,11 @@ public final class AnimatedTextComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let outScaleTransition: ComponentTransition = .spring(duration: 0.4)
|
||||||
|
let outAlphaTransition: ComponentTransition = .easeInOut(duration: 0.18)
|
||||||
|
|
||||||
|
var outFirstDelayWidth: CGFloat?
|
||||||
|
|
||||||
var removedKeys: [CharacterKey] = []
|
var removedKeys: [CharacterKey] = []
|
||||||
for (key, characterView) in self.characters {
|
for (key, characterView) in self.characters {
|
||||||
if !validKeys.contains(key) {
|
if !validKeys.contains(key) {
|
||||||
@ -167,9 +186,16 @@ public final class AnimatedTextComponent: Component {
|
|||||||
|
|
||||||
if let characterComponentView = characterView.view {
|
if let characterComponentView = characterView.view {
|
||||||
if !transition.animation.isImmediate {
|
if !transition.animation.isImmediate {
|
||||||
characterComponentView.layer.animateScale(from: 1.0, to: 0.001, duration: 0.4, delay: delayNorm * characterComponentView.frame.minX, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
var delayWidth: Double = 0.0
|
||||||
characterComponentView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -characterComponentView.bounds.height * 0.4), duration: 0.4, delay: delayNorm * characterComponentView.frame.minX, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
if let outFirstDelayWidth {
|
||||||
characterComponentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, delay: delayNorm * characterComponentView.frame.minX, removeOnCompletion: false, completion: { [weak characterComponentView] _ in
|
delayWidth = characterComponentView.frame.minX - outFirstDelayWidth
|
||||||
|
} else {
|
||||||
|
outFirstDelayWidth = characterComponentView.frame.minX
|
||||||
|
}
|
||||||
|
|
||||||
|
outScaleTransition.setScale(view: characterComponentView, scale: 0.01, delay: delayNorm * delayWidth)
|
||||||
|
outScaleTransition.setPosition(view: characterComponentView, position: CGPoint(x: characterComponentView.center.x, y: characterComponentView.center.y - characterComponentView.bounds.height * 0.4), delay: delayNorm * delayWidth)
|
||||||
|
outAlphaTransition.setAlpha(view: characterComponentView, alpha: 0.0, delay: delayNorm * delayWidth, completion: { [weak characterComponentView] _ in
|
||||||
characterComponentView?.removeFromSuperview()
|
characterComponentView?.removeFromSuperview()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -374,6 +374,7 @@ extension ChatControllerImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.context.engine.messages.sendStarsReaction(id: message.id, count: 1)
|
self.context.engine.messages.sendStarsReaction(id: message.id, count: 1)
|
||||||
|
self.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1)
|
||||||
} else {
|
} else {
|
||||||
let chosenReaction: MessageReaction.Reaction = chosenUpdatedReaction.reaction
|
let chosenReaction: MessageReaction.Reaction = chosenUpdatedReaction.reaction
|
||||||
|
|
||||||
|
|||||||
@ -613,6 +613,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
var messageComposeController: MFMessageComposeViewController?
|
var messageComposeController: MFMessageComposeViewController?
|
||||||
|
|
||||||
|
weak var currentSendStarsUndoController: UndoOverlayController?
|
||||||
|
var currentSendStarsUndoMessageId: EngineMessage.Id?
|
||||||
|
var currentSendStarsUndoCount: Int = 0
|
||||||
|
|
||||||
public var alwaysShowSearchResultsAsList: Bool = false {
|
public var alwaysShowSearchResultsAsList: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList)
|
self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList)
|
||||||
@ -1709,23 +1713,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1)
|
strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1)
|
||||||
|
strongSelf.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1)
|
||||||
if !"".isEmpty {
|
|
||||||
let _ = (strongSelf.context.engine.stickers.resolveInlineStickers(fileIds: [MessageReaction.starsReactionId])
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak strongSelf, weak itemNode] files in
|
|
||||||
guard let strongSelf, let file = files[MessageReaction.starsReactionId] else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
//TODO:localize
|
|
||||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .starsSent(context: strongSelf.context, file: file, amount: 1, title: "Star Sent", text: "Long tap on {star} to select custom quantity of stars."), elevatedLayout: false, action: { _ in
|
|
||||||
return false
|
|
||||||
}), in: .current)
|
|
||||||
|
|
||||||
if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: chosenReaction) {
|
|
||||||
strongSelf.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: strongSelf.chatDisplayNode.view))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
var removedReaction: MessageReaction.Reaction?
|
var removedReaction: MessageReaction.Reaction?
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import ChatSendStarsScreen
|
|||||||
import ChatMessageItemCommon
|
import ChatMessageItemCommon
|
||||||
import ChatMessageItemView
|
import ChatMessageItemView
|
||||||
import ReactionSelectionNode
|
import ReactionSelectionNode
|
||||||
|
import AnimatedTextComponent
|
||||||
|
|
||||||
extension ChatControllerImpl {
|
extension ChatControllerImpl {
|
||||||
func presentTagPremiumPaywall() {
|
func presentTagPremiumPaywall() {
|
||||||
@ -261,24 +262,7 @@ extension ChatControllerImpl {
|
|||||||
let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount))
|
let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [MessageReaction.starsReactionId])
|
self.displayOrUpdateSendStarsUndo(messageId: message.id, count: Int(amount))
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] files in
|
|
||||||
guard let self, let file = files[MessageReaction.starsReactionId] else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let title: String
|
|
||||||
if amount == 1 {
|
|
||||||
title = "Star Sent"
|
|
||||||
} else {
|
|
||||||
title = "\(amount) Stars Sent"
|
|
||||||
}
|
|
||||||
|
|
||||||
self.present(UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, file: file, amount: amount, title: title, text: nil), elevatedLayout: false, action: { _ in
|
|
||||||
return false
|
|
||||||
}), in: .current)
|
|
||||||
})
|
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -480,4 +464,50 @@ extension ChatControllerImpl {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func displayOrUpdateSendStarsUndo(messageId: EngineMessage.Id, count: Int) {
|
||||||
|
if self.currentSendStarsUndoMessageId != messageId {
|
||||||
|
if let current = self.currentSendStarsUndoController {
|
||||||
|
self.currentSendStarsUndoController = nil
|
||||||
|
current.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let _ = self.currentSendStarsUndoController {
|
||||||
|
self.currentSendStarsUndoCount += count
|
||||||
|
} else {
|
||||||
|
self.currentSendStarsUndoCount = count
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
let title: String
|
||||||
|
if self.currentSendStarsUndoCount == 1 {
|
||||||
|
title = "Star sent!"
|
||||||
|
} else {
|
||||||
|
title = "Stars sent!"
|
||||||
|
}
|
||||||
|
|
||||||
|
var textItems: [AnimatedTextComponent.Item] = []
|
||||||
|
textItems.append(AnimatedTextComponent.Item(id: AnyHashable(0), isUnbreakable: true, content: .text("You have reacted with ")))
|
||||||
|
textItems.append(AnimatedTextComponent.Item(id: AnyHashable(1), content: .number(self.currentSendStarsUndoCount, minDigits: 1)))
|
||||||
|
textItems.append(AnimatedTextComponent.Item(id: AnyHashable(2), isUnbreakable: true, content: .text(self.currentSendStarsUndoCount == 1 ? " star." : " stars.")))
|
||||||
|
|
||||||
|
self.currentSendStarsUndoMessageId = messageId
|
||||||
|
//TODO:localize
|
||||||
|
if let current = self.currentSendStarsUndoController {
|
||||||
|
current.content = .starsSent(context: self.context, title: title, text: textItems)
|
||||||
|
} else {
|
||||||
|
let controller = UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, title: title, text: textItems), elevatedLayout: false, position: .top, action: { [weak self] action in
|
||||||
|
guard let self else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if case .undo = action {
|
||||||
|
self.context.engine.messages.cancelPendingSendStarsReaction(id: messageId)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
self.currentSendStarsUndoController = controller
|
||||||
|
self.present(controller, in: .current)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,6 +30,9 @@ swift_library(
|
|||||||
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
|
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
|
||||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||||
|
"//submodules/Components/BundleIconComponent",
|
||||||
|
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||||
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import TelegramPresentationData
|
|||||||
import TelegramCore
|
import TelegramCore
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
|
import AnimatedTextComponent
|
||||||
|
|
||||||
public enum UndoOverlayContent {
|
public enum UndoOverlayContent {
|
||||||
case removedChat(title: String, text: String?)
|
case removedChat(title: String, text: String?)
|
||||||
@ -39,7 +40,7 @@ public enum UndoOverlayContent {
|
|||||||
case copy(text: String)
|
case copy(text: String)
|
||||||
case mediaSaved(text: String)
|
case mediaSaved(text: String)
|
||||||
case paymentSent(currencyValue: String, itemTitle: String)
|
case paymentSent(currencyValue: String, itemTitle: String)
|
||||||
case starsSent(context: AccountContext, file: TelegramMediaFile, amount: Int64, title: String, text: String?)
|
case starsSent(context: AccountContext, title: String, text: [AnimatedTextComponent.Item])
|
||||||
case inviteRequestSent(title: String, text: String)
|
case inviteRequestSent(title: String, text: String)
|
||||||
case image(image: UIImage, title: String?, text: String, round: Bool, undoText: String?)
|
case image(image: UIImage, title: String?, text: String, round: Bool, undoText: String?)
|
||||||
case notificationSoundAdded(title: String, text: String, action: (() -> Void)?)
|
case notificationSoundAdded(title: String, text: String, action: (() -> Void)?)
|
||||||
|
|||||||
@ -20,6 +20,9 @@ import AnimatedAvatarSetNode
|
|||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import EmojiStatusComponent
|
import EmojiStatusComponent
|
||||||
import TextNodeWithEntities
|
import TextNodeWithEntities
|
||||||
|
import BundleIconComponent
|
||||||
|
import AnimatedTextComponent
|
||||||
|
import ComponentDisplayAdapters
|
||||||
|
|
||||||
final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||||
private let presentationData: PresentationData
|
private let presentationData: PresentationData
|
||||||
@ -42,6 +45,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
private var emojiStatus: ComponentView<Empty>?
|
private var emojiStatus: ComponentView<Empty>?
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
private let textNode: ImmediateTextNodeWithEntities
|
private let textNode: ImmediateTextNodeWithEntities
|
||||||
|
private var textComponent: ComponentView<Empty>?
|
||||||
|
private var animatedTextItems: [AnimatedTextComponent.Item]?
|
||||||
private let buttonNode: HighlightTrackingButtonNode
|
private let buttonNode: HighlightTrackingButtonNode
|
||||||
private let undoButtonTextNode: ImmediateTextNode
|
private let undoButtonTextNode: ImmediateTextNode
|
||||||
private let undoButtonNode: HighlightTrackingButtonNode
|
private let undoButtonNode: HighlightTrackingButtonNode
|
||||||
@ -84,6 +89,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
self.timerTextNode.displaysAsynchronously = false
|
self.timerTextNode.displaysAsynchronously = false
|
||||||
|
|
||||||
self.titleNode = ImmediateTextNode()
|
self.titleNode = ImmediateTextNode()
|
||||||
|
self.titleNode.layer.anchorPoint = CGPoint()
|
||||||
self.titleNode.displaysAsynchronously = false
|
self.titleNode.displaysAsynchronously = false
|
||||||
self.titleNode.maximumNumberOfLines = 0
|
self.titleNode.maximumNumberOfLines = 0
|
||||||
|
|
||||||
@ -380,32 +386,23 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
self.textNode.attributedText = string
|
self.textNode.attributedText = string
|
||||||
displayUndo = false
|
displayUndo = false
|
||||||
self.originalRemainingSeconds = 5
|
self.originalRemainingSeconds = 5
|
||||||
case let .starsSent(context, file, _, title, text):
|
case let .starsSent(_, title, textItems):
|
||||||
self.avatarNode = nil
|
self.avatarNode = nil
|
||||||
self.iconNode = nil
|
self.iconNode = nil
|
||||||
self.iconCheckNode = nil
|
self.iconCheckNode = nil
|
||||||
self.animationNode = nil
|
self.animationNode = nil
|
||||||
|
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
||||||
|
|
||||||
let imageBoundingSize = CGSize(width: 34.0, height: 34.0)
|
let imageBoundingSize = CGSize(width: 34.0, height: 34.0)
|
||||||
|
|
||||||
let emojiStatus = ComponentView<Empty>()
|
let emojiStatus = ComponentView<Empty>()
|
||||||
self.emojiStatus = emojiStatus
|
self.emojiStatus = emojiStatus
|
||||||
let _ = emojiStatus.update(
|
let _ = emojiStatus.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(EmojiStatusComponent(
|
component: AnyComponent(BundleIconComponent(
|
||||||
context: context,
|
name: "Premium/Stars/StarLarge",
|
||||||
animationCache: context.animationCache,
|
tintColor: nil
|
||||||
animationRenderer: context.animationRenderer,
|
|
||||||
content: .animation(
|
|
||||||
content: .file(file: file),
|
|
||||||
size: imageBoundingSize,
|
|
||||||
placeholderColor: UIColor(white: 1.0, alpha: 0.1),
|
|
||||||
themeColor: .white,
|
|
||||||
loopMode: .count(1)
|
|
||||||
),
|
|
||||||
isVisibleForAnimations: true,
|
|
||||||
useSharedAnimation: false,
|
|
||||||
action: nil
|
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: imageBoundingSize
|
containerSize: imageBoundingSize
|
||||||
@ -413,34 +410,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
self.stickerImageSize = imageBoundingSize
|
self.stickerImageSize = imageBoundingSize
|
||||||
|
|
||||||
if let text {
|
self.animatedTextItems = textItems
|
||||||
let formattedString = text
|
|
||||||
|
|
||||||
let string = NSMutableAttributedString(attributedString: NSAttributedString(string: formattedString, font: Font.regular(14.0), textColor: .white))
|
displayUndo = true
|
||||||
let starRange = (string.string as NSString).range(of: "{star}")
|
self.originalRemainingSeconds = 4.5
|
||||||
if starRange.location != NSNotFound {
|
|
||||||
string.replaceCharacters(in: starRange, with: "")
|
|
||||||
string.insert(NSAttributedString(string: ".", attributes: [
|
|
||||||
.font: Font.regular(14.0),
|
|
||||||
ChatTextInputAttributes.customEmoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: MessageReaction.starsReactionId, file: file, custom: nil)
|
|
||||||
]), at: starRange.location)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.textNode.attributedText = string
|
|
||||||
self.textNode.arguments = TextNodeWithEntities.Arguments(
|
|
||||||
context: context,
|
|
||||||
cache: context.animationCache,
|
|
||||||
renderer: context.animationRenderer,
|
|
||||||
placeholderColor: UIColor(white: 1.0, alpha: 0.1),
|
|
||||||
attemptSynchronous: false
|
|
||||||
)
|
|
||||||
self.textNode.visibility = true
|
|
||||||
}
|
|
||||||
|
|
||||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
|
||||||
|
|
||||||
displayUndo = false
|
|
||||||
self.originalRemainingSeconds = 3
|
|
||||||
isUserInteractionEnabled = true
|
isUserInteractionEnabled = true
|
||||||
case let .messagesUnpinned(title, text, undo, isHidden):
|
case let .messagesUnpinned(title, text, undo, isHidden):
|
||||||
self.avatarNode = nil
|
self.avatarNode = nil
|
||||||
@ -1485,6 +1458,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
var undoTextColor = self.presentationData.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0)
|
var undoTextColor = self.presentationData.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0)
|
||||||
|
|
||||||
|
var transition: ContainedViewLayoutTransition = .immediate
|
||||||
|
|
||||||
switch content {
|
switch content {
|
||||||
case let .info(title, text, _, _), let .universal(_, _, _, title, text, _, _):
|
case let .info(title, text, _, _), let .universal(_, _, _, title, text, _, _):
|
||||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||||
@ -1516,12 +1491,19 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
||||||
}
|
}
|
||||||
self.textNode.attributedText = attributedText
|
self.textNode.attributedText = attributedText
|
||||||
|
case let .starsSent(_, title, textItems):
|
||||||
|
self.animatedTextItems = textItems
|
||||||
|
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
||||||
|
|
||||||
|
self.renewWithCurrentContent()
|
||||||
|
transition = .animated(duration: 0.1, curve: .easeInOut)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if let validLayout = self.validLayout {
|
if let validLayout = self.validLayout {
|
||||||
self.containerLayoutUpdated(layout: validLayout, transition: .immediate)
|
self.containerLayoutUpdated(layout: validLayout, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1579,7 +1561,41 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let titleSize = self.titleNode.updateLayout(CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - leftMargin, height: .greatestFiniteMagnitude))
|
let titleSize = self.titleNode.updateLayout(CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - leftMargin, height: .greatestFiniteMagnitude))
|
||||||
let textSize = self.textNode.updateLayout(CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - leftMargin, height: .greatestFiniteMagnitude))
|
|
||||||
|
let maxTextSize = CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - leftMargin, height: .greatestFiniteMagnitude)
|
||||||
|
|
||||||
|
let textSize: CGSize
|
||||||
|
if let animatedTextItems = self.animatedTextItems {
|
||||||
|
let textComponent: ComponentView<Empty>
|
||||||
|
if let current = self.textComponent {
|
||||||
|
textComponent = current
|
||||||
|
} else {
|
||||||
|
textComponent = ComponentView()
|
||||||
|
self.textComponent = textComponent
|
||||||
|
}
|
||||||
|
textSize = textComponent.update(
|
||||||
|
transition: ComponentTransition(transition),
|
||||||
|
component: AnyComponent(AnimatedTextComponent(
|
||||||
|
font: Font.regular(14.0),
|
||||||
|
color: .white,
|
||||||
|
items: animatedTextItems
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: maxTextSize
|
||||||
|
)
|
||||||
|
if let textComponentView = textComponent.view {
|
||||||
|
if textComponentView.superview == nil {
|
||||||
|
textComponentView.layer.anchorPoint = CGPoint()
|
||||||
|
self.panelWrapperNode.view.addSubview(textComponentView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let textComponentView = self.textComponent?.view {
|
||||||
|
self.textComponent = nil
|
||||||
|
textComponentView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
textSize = self.textNode.updateLayout(maxTextSize)
|
||||||
|
}
|
||||||
|
|
||||||
if !titleSize.width.isZero {
|
if !titleSize.width.isZero {
|
||||||
contentHeight += titleSize.height + 1.0
|
contentHeight += titleSize.height + 1.0
|
||||||
@ -1630,8 +1646,17 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let textContentOrigin = floor((contentHeight - textContentHeight) / 2.0)
|
let textContentOrigin = floor((contentHeight - textContentHeight) / 2.0)
|
||||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin), size: titleSize))
|
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin), size: titleSize)
|
||||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin + textOffset), size: textSize))
|
transition.updatePosition(node: self.titleNode, position: titleFrame.origin)
|
||||||
|
self.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||||
|
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin + textOffset), size: textSize)
|
||||||
|
if let textComponentView = self.textComponent?.view {
|
||||||
|
transition.updatePosition(layer: textComponentView.layer, position: textFrame.origin)
|
||||||
|
textComponentView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
|
||||||
|
} else {
|
||||||
|
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||||
|
}
|
||||||
|
|
||||||
if let iconNode = self.iconNode {
|
if let iconNode = self.iconNode {
|
||||||
let iconSize: CGSize
|
let iconSize: CGSize
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user