mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Reaction experiments
This commit is contained in:
parent
ad1ddf65c0
commit
8af8de7096
@ -601,6 +601,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
id: AnyHashable("items"),
|
||||
items: items,
|
||||
reactionItems: nil,
|
||||
previewReaction: nil,
|
||||
tip: nil,
|
||||
tipSignal: .single(nil),
|
||||
dismissed: nil
|
||||
@ -630,6 +631,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
||||
id: AnyHashable("items"),
|
||||
items: items,
|
||||
reactionItems: nil,
|
||||
previewReaction: nil,
|
||||
tip: nil,
|
||||
tipSignal: .single(nil),
|
||||
dismissed: nil
|
||||
|
@ -24,7 +24,7 @@ private final class StarsButtonEffectLayer: SimpleLayer {
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = UIColor.blue.withAlphaComponent(0.2).cgColor
|
||||
self.backgroundColor = UIColor.lightGray.withAlphaComponent(0.2).cgColor
|
||||
}
|
||||
|
||||
override init(layer: Any) {
|
||||
|
@ -391,6 +391,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
private final class ItemNode: HighlightTrackingButtonNode {
|
||||
let context: AccountContext
|
||||
let displayReadTimestamps: Bool
|
||||
let displayReactionIcon: Bool
|
||||
let availableReactions: AvailableReactions?
|
||||
let animationCache: AnimationCache
|
||||
let animationRenderer: MultiAnimationRenderer
|
||||
@ -411,10 +412,11 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
|
||||
private var item: EngineMessageReactionListContext.Item?
|
||||
|
||||
init(context: AccountContext, displayReadTimestamps: Bool, availableReactions: AvailableReactions?, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, action: @escaping () -> Void) {
|
||||
init(context: AccountContext, displayReadTimestamps: Bool, displayReactionIcon: Bool, availableReactions: AvailableReactions?, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, action: @escaping () -> Void) {
|
||||
self.action = action
|
||||
self.context = context
|
||||
self.displayReadTimestamps = displayReadTimestamps
|
||||
self.displayReactionIcon = displayReactionIcon
|
||||
self.availableReactions = availableReactions
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
@ -548,7 +550,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
|
||||
let reaction: MessageReaction.Reaction? = item.reaction
|
||||
|
||||
if reaction != self.item?.reaction {
|
||||
if self.displayReactionIcon, reaction != self.item?.reaction {
|
||||
if let reaction = reaction {
|
||||
switch reaction {
|
||||
case .builtin:
|
||||
@ -802,6 +804,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
|
||||
private let context: AccountContext
|
||||
private let displayReadTimestamps: Bool
|
||||
private let displayReactionIcons: Bool
|
||||
private let availableReactions: AvailableReactions?
|
||||
private let animationCache: AnimationCache
|
||||
private let animationRenderer: MultiAnimationRenderer
|
||||
@ -833,6 +836,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
init(
|
||||
context: AccountContext,
|
||||
displayReadTimestamps: Bool,
|
||||
displayReactionIcons: Bool,
|
||||
availableReactions: AvailableReactions?,
|
||||
animationCache: AnimationCache,
|
||||
animationRenderer: MultiAnimationRenderer,
|
||||
@ -845,6 +849,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
) {
|
||||
self.context = context
|
||||
self.displayReadTimestamps = displayReadTimestamps
|
||||
self.displayReactionIcons = displayReactionIcons
|
||||
self.availableReactions = availableReactions
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
@ -955,7 +960,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
} else {
|
||||
let openPeer = self.openPeer
|
||||
let peer = item.peer
|
||||
itemNode = ItemNode(context: self.context, displayReadTimestamps: self.displayReadTimestamps, availableReactions: self.availableReactions, animationCache: self.animationCache, animationRenderer: self.animationRenderer, action: {
|
||||
itemNode = ItemNode(context: self.context, displayReadTimestamps: self.displayReadTimestamps, displayReactionIcon: self.displayReactionIcons, availableReactions: self.availableReactions, animationCache: self.animationCache, animationRenderer: self.animationRenderer, action: {
|
||||
openPeer(peer, item.reaction != nil)
|
||||
})
|
||||
self.itemNodes[index] = itemNode
|
||||
@ -1104,6 +1109,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
final class ItemsNode: ASDisplayNode, ContextControllerItemsNode, ASGestureRecognizerDelegate {
|
||||
private let context: AccountContext
|
||||
private let displayReadTimestamps: Bool
|
||||
private let displayReactionIcons: Bool
|
||||
private let availableReactions: AvailableReactions?
|
||||
private let animationCache: AnimationCache
|
||||
private let animationRenderer: MultiAnimationRenderer
|
||||
@ -1148,6 +1154,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
) {
|
||||
self.context = context
|
||||
self.displayReadTimestamps = displayReadTimestamps
|
||||
self.displayReactionIcons = reaction == nil
|
||||
self.availableReactions = availableReactions
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
@ -1159,9 +1166,6 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
self.requestUpdate = requestUpdate
|
||||
self.requestUpdateApparentHeight = requestUpdateApparentHeight
|
||||
|
||||
//var requestUpdateTab: ((ReactionsTabNode, ContainedViewLayoutTransition) -> Void)?
|
||||
//var requestUpdateTabApparentHeight: ((ReactionsTabNode, ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
if let back = back {
|
||||
self.backButtonNode = BackButtonNode()
|
||||
self.backButtonNode?.action = {
|
||||
@ -1218,45 +1222,9 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
strongSelf.tabListNode?.scrollToTabReaction = ReactionTabListNode.ScrollToTabReaction(value: reaction)
|
||||
strongSelf.currentTabIndex = tabIndex
|
||||
|
||||
/*let currentTabNode = ReactionsTabNode(
|
||||
context: context,
|
||||
availableReactions: availableReactions,
|
||||
message: message,
|
||||
reaction: reaction,
|
||||
readStats: nil,
|
||||
requestUpdate: { tab, transition in
|
||||
requestUpdateTab?(tab, transition)
|
||||
},
|
||||
requestUpdateApparentHeight: { tab, transition in
|
||||
requestUpdateTabApparentHeight?(tab, transition)
|
||||
},
|
||||
openPeer: { id in
|
||||
openPeer(id)
|
||||
}
|
||||
)
|
||||
strongSelf.currentTabNode = currentTabNode
|
||||
strongSelf.addSubnode(currentTabNode)*/
|
||||
strongSelf.requestUpdate(.animated(duration: 0.45, curve: .spring))
|
||||
}
|
||||
|
||||
/*requestUpdateTab = { [weak self] tab, transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.visibleTabNodes.contains(where: { $0.value === tab }) {
|
||||
strongSelf.requestUpdate(transition)
|
||||
}
|
||||
}
|
||||
|
||||
requestUpdateTabApparentHeight = { [weak self] tab, transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.visibleTabNodes.contains(where: { $0.value === tab }) {
|
||||
strongSelf.requestUpdateApparentHeight(transition)
|
||||
}
|
||||
}*/
|
||||
|
||||
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
|
||||
guard let strongSelf = self else {
|
||||
return []
|
||||
@ -1371,6 +1339,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
tabNode = ReactionsTabNode(
|
||||
context: self.context,
|
||||
displayReadTimestamps: self.displayReadTimestamps,
|
||||
displayReactionIcons: self.displayReactionIcons,
|
||||
availableReactions: self.availableReactions,
|
||||
animationCache: self.animationCache,
|
||||
animationRenderer: self.animationRenderer,
|
||||
|
@ -31,6 +31,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/UIKitRuntimeUtils",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -436,6 +436,12 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
||||
self.targetSelectionIndex = nil
|
||||
icon = nil
|
||||
isUserInteractionEnabled = action != nil
|
||||
case let .starsReactions(topCount):
|
||||
self.action = nil
|
||||
self.text = "Send \(topCount) or more to highlight your profile"
|
||||
self.targetSelectionIndex = nil
|
||||
icon = nil
|
||||
isUserInteractionEnabled = action != nil
|
||||
}
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
|
@ -2277,6 +2277,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
public var allPresetReactionsAreAvailable: Bool
|
||||
public var getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?
|
||||
public var disablePositionLock: Bool
|
||||
public var previewReaction: TelegramMediaFile?
|
||||
public var tip: Tip?
|
||||
public var tipSignal: Signal<Tip?, NoError>?
|
||||
public var dismissed: (() -> Void)?
|
||||
@ -2294,6 +2295,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
allPresetReactionsAreAvailable: Bool = false,
|
||||
getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)? = nil,
|
||||
disablePositionLock: Bool = false,
|
||||
previewReaction: TelegramMediaFile? = nil,
|
||||
tip: Tip? = nil,
|
||||
tipSignal: Signal<Tip?, NoError>? = nil,
|
||||
dismissed: (() -> Void)? = nil
|
||||
@ -2310,6 +2312,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
self.allPresetReactionsAreAvailable = allPresetReactionsAreAvailable
|
||||
self.getEmojiContent = getEmojiContent
|
||||
self.disablePositionLock = disablePositionLock
|
||||
self.previewReaction = previewReaction
|
||||
self.tip = tip
|
||||
self.tipSignal = tipSignal
|
||||
self.dismissed = dismissed
|
||||
@ -2327,6 +2330,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
self.allPresetReactionsAreAvailable = false
|
||||
self.getEmojiContent = nil
|
||||
self.disablePositionLock = false
|
||||
self.previewReaction = nil
|
||||
self.tip = nil
|
||||
self.tipSignal = nil
|
||||
self.dismissed = nil
|
||||
@ -2345,6 +2349,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
case messageCopyProtection(isChannel: Bool)
|
||||
case animatedEmoji(text: String?, arguments: TextNodeWithEntities.Arguments?, file: TelegramMediaFile?, action: (() -> Void)?)
|
||||
case notificationTopicExceptions(text: String, action: (() -> Void)?)
|
||||
case starsReactions(topCount: Int)
|
||||
|
||||
public static func ==(lhs: Tip, rhs: Tip) -> Bool {
|
||||
switch lhs {
|
||||
@ -2390,6 +2395,12 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .starsReactions(topCount):
|
||||
if case .starsReactions(topCount) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,16 @@ public struct ContextControllerReactionItems {
|
||||
}
|
||||
}
|
||||
|
||||
public final class ContextControllerPreviewReaction {
|
||||
public let context: AccountContext
|
||||
public let file: TelegramMediaFile
|
||||
|
||||
public init(context: AccountContext, file: TelegramMediaFile) {
|
||||
self.context = context
|
||||
self.file = file
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ContextControllerActionsStackItem: AnyObject {
|
||||
func node(
|
||||
getController: @escaping () -> ContextControllerProtocol?,
|
||||
@ -71,6 +81,7 @@ public protocol ContextControllerActionsStackItem: AnyObject {
|
||||
var tip: ContextController.Tip? { get }
|
||||
var tipSignal: Signal<ContextController.Tip?, NoError>? { get }
|
||||
var reactionItems: ContextControllerReactionItems? { get }
|
||||
var previewReaction: ContextControllerPreviewReaction? { get }
|
||||
var dismissed: (() -> Void)? { get }
|
||||
}
|
||||
|
||||
@ -936,6 +947,7 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
|
||||
public let id: AnyHashable?
|
||||
public let items: [ContextMenuItem]
|
||||
public let reactionItems: ContextControllerReactionItems?
|
||||
public let previewReaction: ContextControllerPreviewReaction?
|
||||
public let tip: ContextController.Tip?
|
||||
public let tipSignal: Signal<ContextController.Tip?, NoError>?
|
||||
public let dismissed: (() -> Void)?
|
||||
@ -944,6 +956,7 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
|
||||
id: AnyHashable?,
|
||||
items: [ContextMenuItem],
|
||||
reactionItems: ContextControllerReactionItems?,
|
||||
previewReaction: ContextControllerPreviewReaction?,
|
||||
tip: ContextController.Tip?,
|
||||
tipSignal: Signal<ContextController.Tip?, NoError>?,
|
||||
dismissed: (() -> Void)?
|
||||
@ -951,6 +964,7 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
|
||||
self.id = id
|
||||
self.items = items
|
||||
self.reactionItems = reactionItems
|
||||
self.previewReaction = previewReaction
|
||||
self.tip = tip
|
||||
self.tipSignal = tipSignal
|
||||
self.dismissed = dismissed
|
||||
@ -1034,6 +1048,7 @@ final class ContextControllerActionsCustomStackItem: ContextControllerActionsSta
|
||||
let id: AnyHashable?
|
||||
private let content: ContextControllerItemsContent
|
||||
let reactionItems: ContextControllerReactionItems?
|
||||
let previewReaction: ContextControllerPreviewReaction?
|
||||
let tip: ContextController.Tip?
|
||||
let tipSignal: Signal<ContextController.Tip?, NoError>?
|
||||
let dismissed: (() -> Void)?
|
||||
@ -1042,6 +1057,7 @@ final class ContextControllerActionsCustomStackItem: ContextControllerActionsSta
|
||||
id: AnyHashable?,
|
||||
content: ContextControllerItemsContent,
|
||||
reactionItems: ContextControllerReactionItems?,
|
||||
previewReaction: ContextControllerPreviewReaction?,
|
||||
tip: ContextController.Tip?,
|
||||
tipSignal: Signal<ContextController.Tip?, NoError>?,
|
||||
dismissed: (() -> Void)?
|
||||
@ -1049,6 +1065,7 @@ final class ContextControllerActionsCustomStackItem: ContextControllerActionsSta
|
||||
self.id = id
|
||||
self.content = content
|
||||
self.reactionItems = reactionItems
|
||||
self.previewReaction = previewReaction
|
||||
self.tip = tip
|
||||
self.tipSignal = tipSignal
|
||||
self.dismissed = dismissed
|
||||
@ -1084,13 +1101,17 @@ func makeContextControllerActionsStackItem(items: ContextController.Items) -> [C
|
||||
getEmojiContent: items.getEmojiContent
|
||||
)
|
||||
}
|
||||
var previewReaction: ContextControllerPreviewReaction?
|
||||
if let context = items.context, let file = items.previewReaction {
|
||||
previewReaction = ContextControllerPreviewReaction(context: context, file: file)
|
||||
}
|
||||
switch items.content {
|
||||
case let .list(listItems):
|
||||
return [ContextControllerActionsListStackItem(id: items.id, items: listItems, reactionItems: reactionItems, tip: items.tip, tipSignal: items.tipSignal, dismissed: items.dismissed)]
|
||||
return [ContextControllerActionsListStackItem(id: items.id, items: listItems, reactionItems: reactionItems, previewReaction: previewReaction, tip: items.tip, tipSignal: items.tipSignal, dismissed: items.dismissed)]
|
||||
case let .twoLists(listItems1, listItems2):
|
||||
return [ContextControllerActionsListStackItem(id: items.id, items: listItems1, reactionItems: nil, tip: nil, tipSignal: nil, dismissed: items.dismissed), ContextControllerActionsListStackItem(id: nil, items: listItems2, reactionItems: nil, tip: nil, tipSignal: nil, dismissed: nil)]
|
||||
return [ContextControllerActionsListStackItem(id: items.id, items: listItems1, reactionItems: nil, previewReaction: nil, tip: nil, tipSignal: nil, dismissed: items.dismissed), ContextControllerActionsListStackItem(id: nil, items: listItems2, reactionItems: nil, previewReaction: nil, tip: nil, tipSignal: nil, dismissed: nil)]
|
||||
case let .custom(customContent):
|
||||
return [ContextControllerActionsCustomStackItem(id: items.id, content: customContent, reactionItems: reactionItems, tip: items.tip, tipSignal: items.tipSignal, dismissed: items.dismissed)]
|
||||
return [ContextControllerActionsCustomStackItem(id: items.id, content: customContent, reactionItems: reactionItems, previewReaction: previewReaction, tip: items.tip, tipSignal: items.tipSignal, dismissed: items.dismissed)]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1207,6 +1228,7 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
let tipSignal: Signal<ContextController.Tip?, NoError>?
|
||||
var tipNode: InnerTextSelectionTipContainerNode?
|
||||
let reactionItems: ContextControllerReactionItems?
|
||||
let previewReaction: ContextControllerPreviewReaction?
|
||||
let itemDismissed: (() -> Void)?
|
||||
var storedScrollingState: CGFloat?
|
||||
let positionLock: CGFloat?
|
||||
@ -1222,6 +1244,7 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
tip: ContextController.Tip?,
|
||||
tipSignal: Signal<ContextController.Tip?, NoError>?,
|
||||
reactionItems: ContextControllerReactionItems?,
|
||||
previewReaction: ContextControllerPreviewReaction?,
|
||||
itemDismissed: (() -> Void)?,
|
||||
positionLock: CGFloat?
|
||||
) {
|
||||
@ -1240,6 +1263,7 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
self.dimNode.alpha = 0.0
|
||||
|
||||
self.reactionItems = reactionItems
|
||||
self.previewReaction = previewReaction
|
||||
self.itemDismissed = itemDismissed
|
||||
self.positionLock = positionLock
|
||||
|
||||
@ -1376,6 +1400,10 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
return self.itemContainers.last?.reactionItems
|
||||
}
|
||||
|
||||
public var topPreviewReaction: ContextControllerPreviewReaction? {
|
||||
return self.itemContainers.last?.previewReaction
|
||||
}
|
||||
|
||||
public var topPositionLock: CGFloat? {
|
||||
return self.itemContainers.last?.positionLock
|
||||
}
|
||||
@ -1509,6 +1537,7 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
tip: item.tip,
|
||||
tipSignal: item.tipSignal,
|
||||
reactionItems: item.reactionItems,
|
||||
previewReaction: item.previewReaction,
|
||||
itemDismissed: item.dismissed,
|
||||
positionLock: positionLock
|
||||
)
|
||||
|
@ -241,6 +241,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
private let scrollNode: ASDisplayNode
|
||||
|
||||
private var reactionContextNode: ReactionContextNode?
|
||||
private var reactionPreviewView: ReactionPreviewView?
|
||||
private var reactionContextNodeIsAnimatingOut: Bool = false
|
||||
|
||||
private var itemContentNode: ItemContentNode?
|
||||
@ -637,6 +638,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
var animateReactionsIn = false
|
||||
var contentTopInset: CGFloat = topInset
|
||||
var removedReactionContextNode: ReactionContextNode?
|
||||
|
||||
if let reactionItems = self.actionsStackNode.topReactionItems, !reactionItems.reactionItems.isEmpty {
|
||||
let reactionContextNode: ReactionContextNode
|
||||
if let current = self.reactionContextNode {
|
||||
@ -733,6 +735,25 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
removedReactionContextNode = reactionContextNode
|
||||
}
|
||||
|
||||
let reactionPreviewSize = CGSize(width: 100.0, height: 100.0)
|
||||
let reactionPreviewInset: CGFloat = 7.0
|
||||
var removedReactionPreviewView: ReactionPreviewView?
|
||||
if self.reactionContextNode == nil, let previewReaction = self.actionsStackNode.topPreviewReaction {
|
||||
let reactionPreviewView: ReactionPreviewView
|
||||
if let current = self.reactionPreviewView {
|
||||
reactionPreviewView = current
|
||||
} else {
|
||||
reactionPreviewView = ReactionPreviewView(context: previewReaction.context, file: previewReaction.file)
|
||||
self.reactionPreviewView = reactionPreviewView
|
||||
self.view.addSubview(reactionPreviewView)
|
||||
}
|
||||
|
||||
contentTopInset += reactionPreviewSize.height + reactionPreviewInset
|
||||
} else {
|
||||
removedReactionPreviewView = self.reactionPreviewView
|
||||
self.reactionPreviewView = nil
|
||||
}
|
||||
|
||||
if let contentNode = itemContentNode {
|
||||
switch stateTransition {
|
||||
case .animateIn, .animateOut:
|
||||
@ -963,6 +984,14 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
self.proposedReactionsPositionLock = nil
|
||||
}
|
||||
|
||||
if let reactionPreviewView = self.reactionPreviewView {
|
||||
let anchorRect = contentRect.offsetBy(dx: contentParentGlobalFrame.minX, dy: 0.0)
|
||||
|
||||
let reactionPreviewFrame = CGRect(origin: CGPoint(x: floor((anchorRect.midX - reactionPreviewSize.width * 0.5)), y: anchorRect.minY - reactionPreviewInset - reactionPreviewSize.height), size: reactionPreviewSize)
|
||||
transition.updateFrame(view: reactionPreviewView, frame: reactionPreviewFrame)
|
||||
reactionPreviewView.update(size: reactionPreviewFrame.size)
|
||||
}
|
||||
|
||||
if let _ = self.currentReactionsPositionLock {
|
||||
transition.updateAlpha(node: self.actionsStackNode, alpha: 0.0)
|
||||
} else {
|
||||
@ -976,6 +1005,12 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
})
|
||||
}
|
||||
|
||||
if let removedReactionPreviewView {
|
||||
transition.updateAlpha(layer: removedReactionPreviewView.layer, alpha: 0.0, completion: { [weak removedReactionPreviewView] _ in
|
||||
removedReactionPreviewView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.contentRectDebugNode, frame: contentRect, beginWithCurrentState: true)
|
||||
|
||||
var actionsFrame: CGRect
|
||||
@ -1205,6 +1240,29 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
damping: springDamping,
|
||||
additive: true
|
||||
)
|
||||
|
||||
if let reactionPreviewView = self.reactionPreviewView {
|
||||
reactionPreviewView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
reactionPreviewView.layer.animateSpring(
|
||||
from: -animationInContentYDistance as NSNumber, to: 0.0 as NSNumber,
|
||||
keyPath: "position.y",
|
||||
duration: duration,
|
||||
delay: 0.0,
|
||||
initialVelocity: 0.0,
|
||||
damping: springDamping,
|
||||
additive: true
|
||||
)
|
||||
reactionPreviewView.layer.animateSpring(
|
||||
from: 0.01 as NSNumber,
|
||||
to: 1.0 as NSNumber,
|
||||
keyPath: "transform.scale",
|
||||
duration: duration,
|
||||
delay: 0.0,
|
||||
initialVelocity: 0.0,
|
||||
damping: springDamping,
|
||||
additive: false
|
||||
)
|
||||
}
|
||||
} else if let contentNode = controllerContentNode {
|
||||
if case let .controller(source) = self.source, let transitionInfo = source.transitionInfo(), let (sourceView, sourceRect) = transitionInfo.sourceNode() {
|
||||
let sourcePoint = sourceView.convert(sourceRect.center, to: self.view)
|
||||
@ -1493,6 +1551,32 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
if let reactionPreviewView = self.reactionPreviewView {
|
||||
reactionPreviewView.layer.animate(
|
||||
from: 0.0 as NSNumber,
|
||||
to: -animationInContentYDistance as NSNumber,
|
||||
keyPath: "position.y",
|
||||
timingFunction: timingFunction,
|
||||
duration: duration,
|
||||
delay: 0.0,
|
||||
removeOnCompletion: true,
|
||||
additive: true,
|
||||
completion: { _ in
|
||||
}
|
||||
)
|
||||
reactionPreviewView.layer.animate(
|
||||
from: 1.0 as NSNumber,
|
||||
to: 0.01 as NSNumber,
|
||||
keyPath: "transform.scale",
|
||||
timingFunction: timingFunction,
|
||||
duration: duration,
|
||||
delay: 0.0,
|
||||
removeOnCompletion: false,
|
||||
additive: false
|
||||
)
|
||||
reactionPreviewView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in })
|
||||
}
|
||||
}
|
||||
if let contentNode = controllerContentNode {
|
||||
if case let .controller(source) = self.source, let transitionInfo = source.transitionInfo(), let (sourceView, sourceRect) = transitionInfo.sourceNode() {
|
||||
|
55
submodules/ContextUI/Sources/ReactionPreviewView.swift
Normal file
55
submodules/ContextUI/Sources/ReactionPreviewView.swift
Normal file
@ -0,0 +1,55 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
import EmojiStatusComponent
|
||||
|
||||
final class ReactionPreviewView: UIView {
|
||||
private let context: AccountContext
|
||||
private let file: TelegramMediaFile
|
||||
|
||||
private let icon = ComponentView<Empty>()
|
||||
|
||||
init(context: AccountContext, file: TelegramMediaFile) {
|
||||
self.context = context
|
||||
self.file = file
|
||||
|
||||
super.init(frame: CGRect())
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func update(size: CGSize) {
|
||||
let iconSize = self.icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(EmojiStatusComponent(
|
||||
context: self.context,
|
||||
animationCache: self.context.animationCache,
|
||||
animationRenderer: self.context.animationRenderer,
|
||||
content: .animation(
|
||||
content: .file(file: self.file),
|
||||
size: size,
|
||||
placeholderColor: .clear,
|
||||
themeColor: .white,
|
||||
loopMode: .count(0)
|
||||
),
|
||||
isVisibleForAnimations: true,
|
||||
action: nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: size
|
||||
)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) * 0.5), y: floor((size.height - iconSize.height) * 0.5)), size: iconSize)
|
||||
if let iconView = self.icon.view {
|
||||
if iconView.superview == nil {
|
||||
self.addSubview(iconView)
|
||||
}
|
||||
iconView.frame = iconFrame
|
||||
}
|
||||
}
|
||||
}
|
@ -664,6 +664,9 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
}
|
||||
|
||||
strongSelf.hideExpandedTopPanel = emojiContent.panelItemGroups.isEmpty
|
||||
if emojiContent.panelItemGroups.count == 1 && emojiContent.panelItemGroups[0].groupId == AnyHashable("recent") {
|
||||
strongSelf.hideExpandedTopPanel = true
|
||||
}
|
||||
|
||||
var emojiContent = emojiContent
|
||||
if let emojiSearchResult = emojiSearchState.result {
|
||||
|
@ -60,7 +60,7 @@ private final class StarsReactionEffectLayer: SimpleLayer {
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = UIColor.blue.withAlphaComponent(0.2).cgColor
|
||||
self.backgroundColor = UIColor.lightGray.withAlphaComponent(0.2).cgColor
|
||||
}
|
||||
|
||||
override init(layer: Any) {
|
||||
|
@ -3,7 +3,11 @@ import Postbox
|
||||
import TelegramApi
|
||||
|
||||
public struct MessageReaction: Equatable, PostboxCoding, Codable {
|
||||
#if DEBUG
|
||||
public static let starsReactionId: Int64 = 5435957248314579621
|
||||
#else
|
||||
public static let starsReactionId: Int64 = 12340000
|
||||
#endif
|
||||
|
||||
public enum Reaction: Hashable, Comparable, Codable, PostboxCoding {
|
||||
case builtin(String)
|
||||
|
@ -455,6 +455,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Chat/FactCheckAlertController",
|
||||
"//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController",
|
||||
"//submodules/TelegramUI/Components/PeerManagement/OldChannelsController",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatSendStarsScreen",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
"//build-system:ios_sim_arm64": [],
|
||||
|
@ -1082,7 +1082,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
canViewReactionList: canViewMessageReactionList(message: item.message, isInline: item.associatedData.isInline),
|
||||
canViewReactionList: canViewMessageReactionList(message: item.message),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
|
@ -737,7 +737,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
replyCount: dateReplies,
|
||||
isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread,
|
||||
hasAutoremove: message.isSelfExpiring,
|
||||
canViewReactionList: canViewMessageReactionList(message: message, isInline: associatedData.isInline),
|
||||
canViewReactionList: canViewMessageReactionList(message: message),
|
||||
animationCache: controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
|
@ -2277,7 +2277,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
replyCount: dateReplies,
|
||||
isPinned: message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||
hasAutoremove: message.isSelfExpiring,
|
||||
canViewReactionList: canViewMessageReactionList(message: message, isInline: item.associatedData.isInline),
|
||||
canViewReactionList: canViewMessageReactionList(message: message),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
|
@ -302,7 +302,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage, isInline: item.associatedData.isInline),
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
|
@ -905,7 +905,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
let canViewReactionList = arguments.canViewReactionList
|
||||
item.node.view.activateAfterCompletion = !canViewReactionList
|
||||
item.node.view.activated = { [weak itemNode] gesture, _ in
|
||||
guard let strongSelf = self, canViewReactionList else {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard let itemNode = itemNode else {
|
||||
|
@ -449,7 +449,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage, isInline: item.associatedData.isInline),
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
|
@ -532,7 +532,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage, isInline: item.associatedData.isInline),
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
|
@ -947,7 +947,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
replyCount: dateReplies,
|
||||
isPinned: arguments.isPinned && !arguments.associatedData.isInPinnedListMode,
|
||||
hasAutoremove: arguments.message.isSelfExpiring,
|
||||
canViewReactionList: canViewMessageReactionList(message: arguments.topMessage, isInline: arguments.associatedData.isInline),
|
||||
canViewReactionList: canViewMessageReactionList(message: arguments.topMessage),
|
||||
animationCache: arguments.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: arguments.controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
|
@ -585,7 +585,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage, isInline: item.associatedData.isInline),
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
|
@ -967,7 +967,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
replyCount: dateAndStatus.dateReplies,
|
||||
isPinned: dateAndStatus.isPinned,
|
||||
hasAutoremove: message.isSelfExpiring,
|
||||
canViewReactionList: canViewMessageReactionList(message: message, isInline: associatedData.isInline),
|
||||
canViewReactionList: canViewMessageReactionList(message: message),
|
||||
animationCache: presentationContext.animationCache,
|
||||
animationRenderer: presentationContext.animationRenderer
|
||||
))
|
||||
|
@ -158,7 +158,7 @@ public struct ChatMessageItemLayoutConstants {
|
||||
}
|
||||
}
|
||||
|
||||
public func canViewMessageReactionList(message: Message, isInline: Bool) -> Bool {
|
||||
public func canViewMessageReactionList(message: Message) -> Bool {
|
||||
var found = false
|
||||
var canViewList = false
|
||||
for attribute in message.attributes {
|
||||
|
@ -286,7 +286,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage, isInline: item.associatedData.isInline),
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
|
@ -1127,7 +1127,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage, isInline: item.associatedData.isInline),
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
|
@ -362,16 +362,13 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
|
||||
let itemValue = item.value
|
||||
let itemNode = item.node
|
||||
item.node.view.isGestureEnabled = true
|
||||
let canViewReactionList = canViewMessageReactionList(message: message, isInline: associatedData.isInline)
|
||||
let canViewReactionList = canViewMessageReactionList(message: message)
|
||||
item.node.view.activateAfterCompletion = !canViewReactionList
|
||||
item.node.view.activated = { [weak itemNode] gesture, _ in
|
||||
guard let strongSelf = self, let itemNode = itemNode else {
|
||||
gesture.cancel()
|
||||
return
|
||||
}
|
||||
if !canViewReactionList {
|
||||
return
|
||||
}
|
||||
strongSelf.openReactionPreview?(gesture, itemNode.view.containerView, itemValue)
|
||||
}
|
||||
item.node.view.additionalActivationProgressLayer = itemMaskView.layer
|
||||
|
@ -142,7 +142,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage, isInline: item.associatedData.isInline),
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
|
@ -647,7 +647,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
canViewReactionList: canViewMessageReactionList(message: item.message, isInline: item.associatedData.isInline),
|
||||
canViewReactionList: canViewMessageReactionList(message: item.message),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
|
@ -649,7 +649,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && (!item.associatedData.isInPinnedListMode || isReplyThread),
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage, isInline: item.associatedData.isInline),
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
|
@ -0,0 +1,38 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "ChatSendStarsScreen",
|
||||
module_name = "ChatSendStarsScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/ChatPresentationInterfaceState",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/ContextUI",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/Components/ViewControllerComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/Markdown",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
"//submodules/TelegramUI/Components/SliderComponent",
|
||||
"//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath",
|
||||
"//submodules/AvatarNode",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,166 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
|
||||
private let labelWidth: CGFloat = 16.0
|
||||
private let labelHeight: CGFloat = 36.0
|
||||
private let labelSize = CGSize(width: labelWidth, height: labelHeight)
|
||||
private let font = Font.with(size: 24.0, design: .round, weight: .semibold, traits: [])
|
||||
|
||||
final class BadgeLabelView: UIView {
|
||||
private class StackView: UIView {
|
||||
var labels: [UILabel] = []
|
||||
|
||||
var currentValue: Int32 = 0
|
||||
|
||||
var color: UIColor = .white {
|
||||
didSet {
|
||||
for view in self.labels {
|
||||
view.textColor = self.color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect(origin: .zero, size: labelSize))
|
||||
|
||||
var height: CGFloat = -labelHeight
|
||||
for i in -1 ..< 10 {
|
||||
let label = UILabel()
|
||||
if i == -1 {
|
||||
label.text = "9"
|
||||
} else {
|
||||
label.text = "\(i)"
|
||||
}
|
||||
label.textColor = self.color
|
||||
label.font = font
|
||||
label.textAlignment = .center
|
||||
label.frame = CGRect(x: 0, y: height, width: labelWidth, height: labelHeight)
|
||||
self.addSubview(label)
|
||||
self.labels.append(label)
|
||||
|
||||
height += labelHeight
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(value: Int32, isFirst: Bool, isLast: Bool, transition: ComponentTransition) {
|
||||
let previousValue = self.currentValue
|
||||
self.currentValue = value
|
||||
|
||||
self.labels[1].alpha = isFirst && !isLast ? 0.0 : 1.0
|
||||
|
||||
if previousValue == 9 && value < 9 {
|
||||
self.bounds = CGRect(
|
||||
origin: CGPoint(
|
||||
x: 0.0,
|
||||
y: -1.0 * labelSize.height
|
||||
),
|
||||
size: labelSize
|
||||
)
|
||||
}
|
||||
|
||||
let bounds = CGRect(
|
||||
origin: CGPoint(
|
||||
x: 0.0,
|
||||
y: CGFloat(value) * labelSize.height
|
||||
),
|
||||
size: labelSize
|
||||
)
|
||||
transition.setBounds(view: self, bounds: bounds)
|
||||
}
|
||||
}
|
||||
|
||||
private var itemViews: [Int: StackView] = [:]
|
||||
private var staticLabel = UILabel()
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
|
||||
self.clipsToBounds = true
|
||||
self.isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
var color: UIColor = .white {
|
||||
didSet {
|
||||
self.staticLabel.textColor = self.color
|
||||
for (_, view) in self.itemViews {
|
||||
view.color = self.color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update(value: String, transition: ComponentTransition) -> CGSize {
|
||||
if value.contains(" ") {
|
||||
for (_, view) in self.itemViews {
|
||||
view.isHidden = true
|
||||
}
|
||||
|
||||
if self.staticLabel.superview == nil {
|
||||
self.staticLabel.textColor = self.color
|
||||
self.staticLabel.font = font
|
||||
|
||||
self.addSubview(self.staticLabel)
|
||||
}
|
||||
|
||||
self.staticLabel.text = value
|
||||
let size = self.staticLabel.sizeThatFits(CGSize(width: 100.0, height: 100.0))
|
||||
self.staticLabel.frame = CGRect(origin: .zero, size: CGSize(width: size.width, height: labelHeight))
|
||||
|
||||
return CGSize(width: ceil(self.staticLabel.bounds.width), height: ceil(self.staticLabel.bounds.height))
|
||||
}
|
||||
|
||||
let string = value
|
||||
let stringArray = Array(string.map { String($0) }.reversed())
|
||||
|
||||
let totalWidth = CGFloat(stringArray.count) * labelWidth
|
||||
|
||||
var validIds: [Int] = []
|
||||
for i in 0 ..< stringArray.count {
|
||||
validIds.append(i)
|
||||
|
||||
let itemView: StackView
|
||||
var itemTransition = transition
|
||||
if let current = self.itemViews[i] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemTransition = transition.withAnimation(.none)
|
||||
itemView = StackView()
|
||||
itemView.color = self.color
|
||||
self.itemViews[i] = itemView
|
||||
self.addSubview(itemView)
|
||||
}
|
||||
|
||||
let digit = Int32(stringArray[i]) ?? 0
|
||||
itemView.update(value: digit, isFirst: i == stringArray.count - 1, isLast: i == 0, transition: transition)
|
||||
|
||||
itemTransition.setFrame(
|
||||
view: itemView,
|
||||
frame: CGRect(x: totalWidth - labelWidth * CGFloat(i + 1), y: 0.0, width: labelWidth, height: labelHeight)
|
||||
)
|
||||
}
|
||||
|
||||
var removeIds: [Int] = []
|
||||
for (id, itemView) in self.itemViews {
|
||||
if !validIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
|
||||
transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in
|
||||
itemView.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
for id in removeIds {
|
||||
self.itemViews.removeValue(forKey: id)
|
||||
}
|
||||
return CGSize(width: totalWidth, height: labelHeight)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -259,7 +259,7 @@ public func topMessageReactions(context: AccountContext, message: Message, subPe
|
||||
if case let .set(reactions) = allowedReactions {
|
||||
#if DEBUG
|
||||
var reactions = reactions
|
||||
if "".isEmpty {
|
||||
if context.sharedContext.applicationBindings.appBuildType == .internal {
|
||||
reactions.insert(.custom(MessageReaction.starsReactionId))
|
||||
}
|
||||
#endif
|
||||
@ -277,13 +277,14 @@ public func topMessageReactions(context: AccountContext, message: Message, subPe
|
||||
}
|
||||
} else {
|
||||
#if DEBUG
|
||||
return context.engine.stickers.resolveInlineStickers(fileIds: [MessageReaction.starsReactionId])
|
||||
|> map { files -> (reactions: AllowedReactions, files: [Int64: TelegramMediaFile]) in
|
||||
return (allowedReactions, files)
|
||||
if context.sharedContext.applicationBindings.appBuildType == .internal {
|
||||
return context.engine.stickers.resolveInlineStickers(fileIds: [MessageReaction.starsReactionId])
|
||||
|> map { files -> (reactions: AllowedReactions, files: [Int64: TelegramMediaFile]) in
|
||||
return (allowedReactions, files)
|
||||
}
|
||||
}
|
||||
#else
|
||||
return .single((allowedReactions, [:]))
|
||||
#endif
|
||||
return .single((allowedReactions, [:]))
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,7 +303,7 @@ public func topMessageReactions(context: AccountContext, message: Message, subPe
|
||||
var existingIds = Set<MessageReaction.Reaction>()
|
||||
|
||||
#if DEBUG
|
||||
if "".isEmpty {
|
||||
if context.sharedContext.applicationBindings.appBuildType == .internal {
|
||||
if let file = allowedReactionsAndFiles.files[MessageReaction.starsReactionId] {
|
||||
existingIds.insert(.custom(MessageReaction.starsReactionId))
|
||||
|
||||
|
@ -60,6 +60,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
|
||||
private var reactionsInfoText: ComponentView<Empty>?
|
||||
private var reactionInput: ComponentView<Empty>?
|
||||
private var reactionCountSection: ComponentView<Empty>?
|
||||
private var paidReactionsSection: ComponentView<Empty>?
|
||||
private let actionButton = ComponentView<Empty>()
|
||||
|
||||
private var reactionSelectionControl: ComponentView<Empty>?
|
||||
@ -79,6 +80,8 @@ final class PeerAllowedReactionsScreenComponent: Component {
|
||||
private var allowedReactionCount: Int = 11
|
||||
private var appliedReactionSettings: PeerReactionSettings?
|
||||
|
||||
private var areStarsReactionsEnabled: Bool = true
|
||||
|
||||
private var emojiContent: EmojiPagerContentComponent?
|
||||
private var emojiContentDisposable: Disposable?
|
||||
private var caretPosition: Int?
|
||||
@ -93,6 +96,8 @@ final class PeerAllowedReactionsScreenComponent: Component {
|
||||
|
||||
private weak var currentUndoController: UndoOverlayController?
|
||||
|
||||
private var cachedChevronImage: (UIImage, PresentationTheme)?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.scrollView = UIScrollView()
|
||||
self.scrollView.showsVerticalScrollIndicator = true
|
||||
@ -862,7 +867,110 @@ final class PeerAllowedReactionsScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
contentHeight += reactionCountSectionSize.height
|
||||
contentHeight += 12.0
|
||||
|
||||
if "".isEmpty {
|
||||
contentHeight += 32.0
|
||||
|
||||
let paidReactionsSection: ComponentView<Empty>
|
||||
if let current = self.paidReactionsSection {
|
||||
paidReactionsSection = current
|
||||
} else {
|
||||
paidReactionsSection = ComponentView()
|
||||
self.paidReactionsSection = paidReactionsSection
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let parsedString = parseMarkdownIntoAttributedString("Switch this on to let your subscribers set paid reactions with Telegram Stars, which you will be able to withdraw later as TON. [Learn More >](https://telegram.org/privacy)", attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.freeTextColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}))
|
||||
|
||||
let paidReactionsFooterText = NSMutableAttributedString(attributedString: parsedString)
|
||||
|
||||
if self.cachedChevronImage == nil || self.cachedChevronImage?.1 !== environment.theme {
|
||||
self.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: environment.theme.list.itemAccentColor)!, environment.theme)
|
||||
}
|
||||
if let range = paidReactionsFooterText.string.range(of: ">"), let chevronImage = self.cachedChevronImage?.0 {
|
||||
paidReactionsFooterText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: paidReactionsFooterText.string))
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let paidReactionsSectionSize = paidReactionsSection.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
theme: environment.theme,
|
||||
header: nil,
|
||||
footer: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(paidReactionsFooterText),
|
||||
maximumNumberOfLines: 0,
|
||||
highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.2),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
|
||||
return NSAttributedString.Key(rawValue: "URL")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}, tapAction: { [weak self] attributes, _ in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: "URL")] as? String {
|
||||
component.context.sharedContext.applicationBindings.openUrl(url)
|
||||
}
|
||||
}
|
||||
)),
|
||||
items: [
|
||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListSwitchItemComponent(
|
||||
theme: environment.theme,
|
||||
title: "Enable Paid Reactions",
|
||||
value: areStarsReactionsEnabled,
|
||||
valueUpdated: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.areStarsReactionsEnabled = value
|
||||
}
|
||||
)))
|
||||
]
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||
)
|
||||
let paidReactionsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: paidReactionsSectionSize)
|
||||
if let paidReactionsSectionView = paidReactionsSection.view {
|
||||
if paidReactionsSectionView.superview == nil {
|
||||
self.scrollView.addSubview(paidReactionsSectionView)
|
||||
}
|
||||
if animateIn {
|
||||
paidReactionsSectionView.frame = paidReactionsSectionFrame
|
||||
if !transition.animation.isImmediate {
|
||||
paidReactionsSectionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
} else {
|
||||
transition.setFrame(view: paidReactionsSectionView, frame: paidReactionsSectionFrame)
|
||||
}
|
||||
}
|
||||
contentHeight += paidReactionsSectionSize.height
|
||||
contentHeight += 12.0
|
||||
} else {
|
||||
contentHeight += 12.0
|
||||
|
||||
if let paidReactionsSection = self.paidReactionsSection {
|
||||
self.paidReactionsSection = nil
|
||||
if let paidReactionsSectionView = paidReactionsSection.view {
|
||||
if !transition.animation.isImmediate {
|
||||
paidReactionsSectionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak paidReactionsSectionView] _ in
|
||||
paidReactionsSectionView?.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
paidReactionsSectionView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let reactionsTitleText = self.reactionsTitleText {
|
||||
self.reactionsTitleText = nil
|
||||
@ -915,6 +1023,19 @@ final class PeerAllowedReactionsScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let paidReactionsSection = self.paidReactionsSection {
|
||||
self.paidReactionsSection = nil
|
||||
if let paidReactionsSectionView = paidReactionsSection.view {
|
||||
if !transition.animation.isImmediate {
|
||||
paidReactionsSectionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak paidReactionsSectionView] _ in
|
||||
paidReactionsSectionView?.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
paidReactionsSectionView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var buttonContents: [AnyComponentWithIdentity<Empty>] = []
|
||||
|
@ -12,6 +12,8 @@ public final class SliderComponent: Component {
|
||||
public let markPositions: Bool
|
||||
public let trackBackgroundColor: UIColor
|
||||
public let trackForegroundColor: UIColor
|
||||
public let knobSize: CGFloat?
|
||||
public let knobColor: UIColor?
|
||||
public let valueUpdated: (Int) -> Void
|
||||
public let isTrackingUpdated: ((Bool) -> Void)?
|
||||
|
||||
@ -21,6 +23,8 @@ public final class SliderComponent: Component {
|
||||
markPositions: Bool,
|
||||
trackBackgroundColor: UIColor,
|
||||
trackForegroundColor: UIColor,
|
||||
knobSize: CGFloat? = nil,
|
||||
knobColor: UIColor? = nil,
|
||||
valueUpdated: @escaping (Int) -> Void,
|
||||
isTrackingUpdated: ((Bool) -> Void)? = nil
|
||||
) {
|
||||
@ -29,6 +33,8 @@ public final class SliderComponent: Component {
|
||||
self.markPositions = markPositions
|
||||
self.trackBackgroundColor = trackBackgroundColor
|
||||
self.trackForegroundColor = trackForegroundColor
|
||||
self.knobSize = knobSize
|
||||
self.knobColor = knobColor
|
||||
self.valueUpdated = valueUpdated
|
||||
self.isTrackingUpdated = isTrackingUpdated
|
||||
}
|
||||
@ -49,6 +55,12 @@ public final class SliderComponent: Component {
|
||||
if lhs.trackForegroundColor != rhs.trackForegroundColor {
|
||||
return false
|
||||
}
|
||||
if lhs.knobSize != rhs.knobSize {
|
||||
return false
|
||||
}
|
||||
if lhs.knobColor != rhs.knobColor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -94,8 +106,12 @@ public final class SliderComponent: Component {
|
||||
} else {
|
||||
sliderView = TGPhotoEditorSliderView()
|
||||
sliderView.enablePanHandling = true
|
||||
sliderView.trackCornerRadius = 2.0
|
||||
sliderView.lineSize = 4.0
|
||||
if let knobSize = component.knobSize {
|
||||
sliderView.lineSize = knobSize + 4.0
|
||||
} else {
|
||||
sliderView.lineSize = 4.0
|
||||
}
|
||||
sliderView.trackCornerRadius = sliderView.lineSize * 0.5
|
||||
sliderView.dotSize = 5.0
|
||||
sliderView.minimumValue = 0.0
|
||||
sliderView.startValue = 0.0
|
||||
@ -110,12 +126,25 @@ public final class SliderComponent: Component {
|
||||
sliderView.backColor = component.trackBackgroundColor
|
||||
sliderView.startColor = component.trackBackgroundColor
|
||||
sliderView.trackColor = component.trackForegroundColor
|
||||
sliderView.knobImage = generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 12.0, color: UIColor(white: 0.0, alpha: 0.25).cgColor)
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 28.0, height: 28.0)))
|
||||
})
|
||||
if let knobSize = component.knobSize {
|
||||
sliderView.knobImage = generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 12.0, color: UIColor(white: 0.0, alpha: 0.25).cgColor)
|
||||
if let knobColor = component.knobColor {
|
||||
context.setFillColor(knobColor.cgColor)
|
||||
} else {
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
}
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: floor((size.width - knobSize) * 0.5), y: floor((size.width - knobSize) * 0.5)), size: CGSize(width: knobSize, height: knobSize)))
|
||||
})
|
||||
} else {
|
||||
sliderView.knobImage = generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 12.0, color: UIColor(white: 0.0, alpha: 0.25).cgColor)
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 28.0, height: 28.0)))
|
||||
})
|
||||
}
|
||||
|
||||
sliderView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)
|
||||
sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX)
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Premium/SendStarsPeerBadgeStarIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/SendStarsPeerBadgeStarIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "star8.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
62
submodules/TelegramUI/Images.xcassets/Premium/SendStarsPeerBadgeStarIcon.imageset/star8.pdf
vendored
Normal file
62
submodules/TelegramUI/Images.xcassets/Premium/SendStarsPeerBadgeStarIcon.imageset/star8.pdf
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Filter /FlateDecode
|
||||
/Length 3 0 R
|
||||
>>
|
||||
stream
|
||||
xe”ÍŠG…÷ý½¤-•~k›²Nò‡ÆcðóûSÏxÆØw¥{Z:ut¤ªw¼ÿòÿãýßþvþþÏñîíßãóñéÐKîß)ß‚__£ï¾ÙZ+“ £Jã||x«ü9øüøxØåé"ªiÒzNIkÉjx4\-Ï'°t8
L|¯øÒÞsšŠ‰ˆiî<0E>U¾RÏu->YµÅÈZ!žÄ£2ÖpGX¥Ýu±6…|Œ¨ûYÃÒ¼Úº!!€À·žë¢sÓ5<C393>¤Ãµ.k—Úp9
›îuÚµ<Ë.0J£› N¿Žœ,kô$ˆôw/]¶ž¹|;ºì
|
||||
ÛBvíŠÖ 0'¶h–Dºîç~ÃJÎÓÚVhÀAº¨ÁÔ+BéGª5F-©\•ºåf_Þ6ý`8~÷<1A>áÆ žŽY‡EÇ
¨m¶òÞwÁ:¿\Wö,íÛ&)2¿n6¯¬ÁëŒÏª÷<C2AA>tí¸‡g±ZÙÌ£¨Ãi&+
|
||||
Ö¸…ú'0—%w^G[²<>-Û&k»%_:Ï Y‘[–k‹ä™´j
ØvFs]ÒO`ÃæXHf-+‰@˜úlÄpu3Èl‘>öî°9( ² @z³C /çÍdË<1øU×lI¡`û›z‰ö xé<78>tç¼ÉyñáqpPóm&Ä~–Ï´ÝF<W¹rƒ ]Ø{-çâúeÂ4.0ÙMº³Úh-œwDµ••…Æ{bÁšPW©pÃ0“œ5xKÉ…0î êS)`,Éø {¡„C’§qHf<48>xÄ.0®žÍm‡½ÜöXA”/=HI>ȬgÂ…†Ž¹,#’Y Nd
|
||||
ô²YèñCœ«D-ð|ÜsuÛ´ÿúRÍŽÔ*‹yÓ–9g2Ùh‰šçéùñ…{ÿÿþr|<þ:¾/T'
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
704
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 8.000000 8.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000000822 00000 n
|
||||
0000000844 00000 n
|
||||
0000001015 00000 n
|
||||
0000001089 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1148
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Premium/SendStarsStarSliderIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/SendStarsStarSliderIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "star24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Premium/SendStarsStarSliderIcon.imageset/star24.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/SendStarsStarSliderIcon.imageset/star24.pdf
vendored
Normal file
Binary file not shown.
@ -1749,6 +1749,29 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageIds: [message.id], reactions: mappedUpdatedReactions, isLarge: false, storeAsRecentlyUsed: false).startStandalone()
|
||||
|
||||
#if DEBUG
|
||||
if strongSelf.context.sharedContext.applicationBindings.appBuildType == .internal {
|
||||
if mappedUpdatedReactions.contains(where: {
|
||||
if case let .custom(fileId, _) = $0, fileId == MessageReaction.starsReactionId {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
let _ = (strongSelf.context.engine.stickers.resolveInlineStickers(fileIds: [MessageReaction.starsReactionId])
|
||||
|> deliverOnMainQueue).start(next: { [weak strongSelf] 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
})
|
||||
}, activateMessagePinch: { [weak self] sourceNode in
|
||||
|
@ -15,6 +15,8 @@ import TextNodeWithEntities
|
||||
import ChatPresentationInterfaceState
|
||||
import SavedTagNameAlertController
|
||||
import PremiumUI
|
||||
import ChatSendStarsScreen
|
||||
import ChatMessageItemCommon
|
||||
|
||||
extension ChatControllerImpl {
|
||||
func presentTagPremiumPaywall() {
|
||||
@ -159,6 +161,41 @@ extension ChatControllerImpl {
|
||||
self.window?.presentInGlobalOverlay(controller)
|
||||
})
|
||||
} else {
|
||||
if self.context.sharedContext.applicationBindings.appBuildType == .internal, case .custom(MessageReaction.starsReactionId) = value {
|
||||
let _ = (ChatSendStarsScreen.initialData(context: self.context, peerId: message.id.peerId)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] initialData in
|
||||
guard let self, let initialData else {
|
||||
return
|
||||
}
|
||||
self.push(ChatSendStarsScreen(context: self.context, initialData: initialData, completion: { [weak self] amount in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [MessageReaction.starsReactionId])
|
||||
|> 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)
|
||||
})
|
||||
}))
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var customFileIds: [Int64] = []
|
||||
if case let .custom(fileId) = value {
|
||||
customFileIds.append(fileId)
|
||||
@ -175,21 +212,28 @@ extension ChatControllerImpl {
|
||||
|
||||
var dismissController: ((@escaping () -> Void) -> Void)?
|
||||
|
||||
var items = ContextController.Items(content: .custom(ReactionListContextMenuContent(
|
||||
context: self.context,
|
||||
displayReadTimestamps: false,
|
||||
availableReactions: availableReactions,
|
||||
animationCache: self.controllerInteraction!.presentationContext.animationCache,
|
||||
animationRenderer: self.controllerInteraction!.presentationContext.animationRenderer,
|
||||
message: EngineMessage(message), reaction: value, readStats: nil, back: nil, openPeer: { peer, hasReaction in
|
||||
dismissController?({ [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
var items: ContextController.Items
|
||||
if canViewMessageReactionList(message: message) {
|
||||
items = ContextController.Items(content: .custom(ReactionListContextMenuContent(
|
||||
context: self.context,
|
||||
displayReadTimestamps: false,
|
||||
availableReactions: availableReactions,
|
||||
animationCache: self.controllerInteraction!.presentationContext.animationCache,
|
||||
animationRenderer: self.controllerInteraction!.presentationContext.animationRenderer,
|
||||
message: EngineMessage(message),
|
||||
reaction: value, readStats: nil, back: nil, openPeer: { peer, hasReaction in
|
||||
dismissController?({ [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.openPeer(peer: peer, navigation: .default, fromMessage: MessageReference(message), fromReactionMessageId: hasReaction ? message.id : nil)
|
||||
})
|
||||
}
|
||||
|
||||
self.openPeer(peer: peer, navigation: .default, fromMessage: MessageReference(message), fromReactionMessageId: hasReaction ? message.id : nil)
|
||||
})
|
||||
})))
|
||||
)))
|
||||
} else {
|
||||
items = ContextController.Items(content: .list([]))
|
||||
}
|
||||
|
||||
var packReferences: [StickerPackReference] = []
|
||||
var existingIds = Set<Int64>()
|
||||
@ -315,6 +359,16 @@ extension ChatControllerImpl {
|
||||
}
|
||||
}
|
||||
|
||||
let reactionFile: TelegramMediaFile?
|
||||
switch value {
|
||||
case .builtin:
|
||||
reactionFile = availableReactions?.reactions.first(where: { $0.value == value })?.selectAnimation
|
||||
case let .custom(fileId):
|
||||
reactionFile = customEmoji[fileId]
|
||||
}
|
||||
items.context = self.context
|
||||
items.previewReaction = reactionFile
|
||||
|
||||
self.canReadHistory.set(false)
|
||||
|
||||
let controller = ContextController(presentationData: self.presentationData, source: .extracted(ChatMessageReactionContextExtractedContentSource(chatNode: self.chatDisplayNode, engine: self.context.engine, message: message, contentView: sourceView)), items: .single(items), recognizer: nil, gesture: gesture)
|
||||
|
@ -39,6 +39,7 @@ public enum UndoOverlayContent {
|
||||
case copy(text: String)
|
||||
case mediaSaved(text: String)
|
||||
case paymentSent(currencyValue: String, itemTitle: String)
|
||||
case starsSent(context: AccountContext, file: TelegramMediaFile, amount: Int64, title: String, text: String?)
|
||||
case inviteRequestSent(title: String, text: String)
|
||||
case image(image: UIImage, title: String?, text: String, round: Bool, undoText: String?)
|
||||
case notificationSoundAdded(title: String, text: String, action: (() -> Void)?)
|
||||
|
@ -380,6 +380,69 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.textNode.attributedText = string
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 5
|
||||
case let .starsSent(context, file, _, title, text):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = nil
|
||||
|
||||
let imageBoundingSize = CGSize(width: 34.0, height: 34.0)
|
||||
|
||||
let emojiStatus = ComponentView<Empty>()
|
||||
self.emojiStatus = emojiStatus
|
||||
let _ = emojiStatus.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(EmojiStatusComponent(
|
||||
context: context,
|
||||
animationCache: context.animationCache,
|
||||
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: {},
|
||||
containerSize: imageBoundingSize
|
||||
)
|
||||
|
||||
self.stickerImageSize = imageBoundingSize
|
||||
|
||||
if let text {
|
||||
let formattedString = text
|
||||
|
||||
let string = NSMutableAttributedString(attributedString: NSAttributedString(string: formattedString, font: Font.regular(14.0), textColor: .white))
|
||||
let starRange = (string.string as NSString).range(of: "{star}")
|
||||
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
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
||||
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 3
|
||||
isUserInteractionEnabled = true
|
||||
case let .messagesUnpinned(title, text, undo, isHidden):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
@ -1232,7 +1295,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
switch content {
|
||||
case .removedChat:
|
||||
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
||||
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal, .premiumPaywall, .peers, .messageTagged:
|
||||
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .starsSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal, .premiumPaywall, .peers, .messageTagged:
|
||||
if self.textNode.tapAttributeAction != nil || displayUndo {
|
||||
self.isUserInteractionEnabled = true
|
||||
} else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user