Reaction improvements

This commit is contained in:
Ali 2022-08-23 22:13:15 +03:00
parent 53690ab199
commit 3b76a3cbfa
13 changed files with 309 additions and 95 deletions

View File

@ -89,7 +89,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
}
}
func item(context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (EnginePeer) -> Void, disabledPeerSelected: @escaping (EnginePeer) -> Void, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, deletePeer: @escaping (EnginePeer.Id) -> Void) -> ListViewItem {
func item(context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (EnginePeer) -> Void, disabledPeerSelected: @escaping (EnginePeer) -> Void, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, deletePeer: @escaping (EnginePeer.Id) -> Void, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) -> ListViewItem {
switch self {
case let .topPeers(peers, theme, strings):
return ChatListRecentPeersListItem(theme: theme, strings: strings, context: context, peers: peers, peerSelected: { peer in
@ -195,17 +195,34 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
badge = ContactsPeerItemBadge(count: peer.unreadCount, type: isMuted ? .inactive : .active)
}
return ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: status, badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .recentPeers, theme: theme, strings: strings, actionTitle: strings.WebSearch_RecentSectionClear, action: {
return ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
sortOrder: nameSortOrder,
displayOrder: nameDisplayOrder,
context: context,
peerMode: .generalSearch,
peer: .peer(peer: primaryPeer, chatPeer: chatPeer),
status: status,
badge: badge,
enabled: enabled,
selection: .none,
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
index: nil,
header: ChatListSearchItemHeader(type: .recentPeers, theme: theme, strings: strings, actionTitle: strings.WebSearch_RecentSectionClear, action: {
clearRecentlySearchedPeers()
}), action: { _ in
}),
action: { _ in
if let chatPeer = peer.peer.peers[peer.peer.peerId] {
peerSelected(EnginePeer(chatPeer))
}
}, disabledAction: { _ in
},
disabledAction: { _ in
if let chatPeer = peer.peer.peers[peer.peer.peerId] {
disabledPeerSelected(EnginePeer(chatPeer))
}
}, deletePeer: deletePeer, contextAction: peerContextAction.flatMap { peerContextAction in
},
deletePeer: deletePeer,
contextAction: peerContextAction.flatMap { peerContextAction in
return { node, gesture, location in
if let chatPeer = peer.peer.peers[peer.peer.peerId] {
peerContextAction(EnginePeer(chatPeer), .recentSearch, node, gesture, location)
@ -213,7 +230,10 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
gesture?.cancel()
}
}
})
},
animationCache: animationCache,
animationRenderer: animationRenderer
)
}
}
}
@ -497,7 +517,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
gesture?.cancel()
}
}
}, arrowAction: nil)
}, arrowAction: nil, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer)
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
let primaryPeer: EnginePeer
var chatPeer: EnginePeer?
@ -584,7 +604,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
gesture?.cancel()
}
}
}, arrowAction: nil)
}, arrowAction: nil, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer)
case let .globalPeer(peer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
var enabled = true
if filter.contains(.onlyWriteable) {
@ -643,7 +663,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
return { node, gesture, location in
peerContextAction(EnginePeer(peer.peer), .search(nil), node, gesture, location)
}
})
}, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer)
case let .message(message, peer, readState, presentationData, _, selected, displayCustomHeader, orderingKey, _, _, allPaused):
let header: ChatListSearchItemHeader
switch orderingKey {
@ -712,12 +732,12 @@ public struct ChatListSearchContainerTransition {
}
}
private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ChatListRecentEntry], to toEntries: [ChatListRecentEntry], context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (EnginePeer) -> Void, disabledPeerSelected: @escaping (EnginePeer) -> Void, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, deletePeer: @escaping (EnginePeer.Id) -> Void) -> ChatListSearchContainerRecentTransition {
private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ChatListRecentEntry], to toEntries: [ChatListRecentEntry], context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (EnginePeer) -> Void, disabledPeerSelected: @escaping (EnginePeer) -> Void, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, deletePeer: @escaping (EnginePeer.Id) -> Void, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) -> ChatListSearchContainerRecentTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, peerSelected: peerSelected, disabledPeerSelected: disabledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, deletePeer: deletePeer), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, peerSelected: peerSelected, disabledPeerSelected: disabledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, deletePeer: deletePeer), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, peerSelected: peerSelected, disabledPeerSelected: disabledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, deletePeer: deletePeer, animationCache: animationCache, animationRenderer: animationRenderer), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, peerSelected: peerSelected, disabledPeerSelected: disabledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, deletePeer: deletePeer, animationCache: animationCache, animationRenderer: animationRenderer), directionHint: nil) }
return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates)
}
@ -2058,7 +2078,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
interaction.clearRecentSearch()
}, deletePeer: { peerId in
let _ = context.engine.peers.removeRecentlySearchedPeer(peerId: peerId).start()
})
}, animationCache: strongSelf.animationCache, animationRenderer: strongSelf.animationRenderer)
strongSelf.enqueueRecentTransition(transition, firstTime: firstTime)
}
}))

View File

@ -435,6 +435,14 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let titleCredibilityIconView = self.titleCredibilityIconView, !titleCredibilityIconView.isHidden, titleCredibilityIconView.alpha != 0.0 {
if titleCredibilityIconView.bounds.insetBy(dx: -8.0, dy: -8.0).contains(self.convert(point, to: titleCredibilityIconView)) {
if let result = titleCredibilityIconView.hitTest(titleCredibilityIconView.bounds.center, with: event) {
return result
}
}
}
if !self.proxyButton.isHidden {
if let result = self.proxyButton.hitTest(point.offsetBy(dx: -self.proxyButton.frame.minX, dy: -self.proxyButton.frame.minY), with: event) {
return result;

View File

@ -393,7 +393,9 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
if let chatPeer = chatPeer {
nodeInteraction.disabledPeerSelected(chatPeer)
}
}
},
animationCache: nodeInteraction.animationCache,
animationRenderer: nodeInteraction.animationRenderer
), directionHint: entry.directionHint)
}
case let .HoleEntry(_, theme):
@ -533,7 +535,9 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
if let chatPeer = chatPeer {
nodeInteraction.disabledPeerSelected(chatPeer)
}
}
},
animationCache: nodeInteraction.animationCache,
animationRenderer: nodeInteraction.animationRenderer
), directionHint: entry.directionHint)
}
case let .HoleEntry(_, theme):

View File

@ -390,7 +390,11 @@ public struct Transition {
}
public func setScale(view: UIView, scale: CGFloat, completion: ((Bool) -> Void)? = nil) {
let t = view.layer.presentation()?.transform ?? view.layer.transform
self.setScale(layer: view.layer, scale: scale, completion: completion)
}
public func setScale(layer: CALayer, scale: CGFloat, completion: ((Bool) -> Void)? = nil) {
let t = layer.presentation()?.transform ?? layer.transform
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
if currentScale == scale {
completion?(true)
@ -398,12 +402,12 @@ public struct Transition {
}
switch self.animation {
case .none:
view.layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
completion?(true)
case let .curve(duration, curve):
let previousScale = currentScale
view.layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
view.layer.animate(
layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
layer.animate(
from: previousScale as NSNumber,
to: scale as NSNumber,
keyPath: "transform.scale",

View File

@ -746,7 +746,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
fileId: fileId,
animationCache: arguments.animationCache,
animationRenderer: arguments.animationRenderer,
placeholderColor: .gray,
placeholderColor: layout.spec.component.chosenOrder != nil ? UIColor(argb: layout.spec.component.colors.selectedForeground).withMultipliedAlpha(0.1) : UIColor(argb: layout.spec.component.colors.deselectedForeground).withMultipliedAlpha(0.1),
animateIdle: animateIdle,
reaction: layout.spec.component.reaction.value,
transition: animation.transition

View File

@ -26,6 +26,10 @@ swift_library(
"//submodules/ListSectionHeaderNode:ListSectionHeaderNode",
"//submodules/ContextUI:ContextUI",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/AnimationCache",
"//submodules/TelegramUI/Components/MultiAnimationRenderer",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
],
visibility = [
"//visibility:public",

View File

@ -16,6 +16,10 @@ import PeerPresenceStatusManager
import ItemListPeerItem
import ContextUI
import AccountContext
import ComponentFlow
import AnimationCache
import MultiAnimationRenderer
import EmojiStatusComponent
public final class ContactItemHighlighting {
public var chatLocation: ChatLocation?
@ -150,6 +154,8 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
let itemHighlighting: ContactItemHighlighting?
let contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?
let arrowAction: (() -> Void)?
let animationCache: AnimationCache?
let animationRenderer: MultiAnimationRenderer?
public let selectable: Bool
@ -157,7 +163,35 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
public let header: ListViewItemHeader?
public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, selectionPosition: ContactsPeerItemSelectionPosition = .right, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], additionalActions: [ContactsPeerItemAction] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: SortIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((EnginePeer.Id?, EnginePeer.Id?) -> Void)? = nil, deletePeer: ((EnginePeer.Id) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, arrowAction: (() -> Void)? = nil) {
public init(
presentationData: ItemListPresentationData,
style: ItemListStyle = .plain,
sectionId: ItemListSectionId = 0,
sortOrder: PresentationPersonNameOrder,
displayOrder: PresentationPersonNameOrder,
context: AccountContext,
peerMode: ContactsPeerItemPeerMode,
peer: ContactsPeerItemPeer,
status: ContactsPeerItemStatus,
badge: ContactsPeerItemBadge? = nil,
enabled: Bool,
selection: ContactsPeerItemSelection,
selectionPosition: ContactsPeerItemSelectionPosition = .right,
editing: ContactsPeerItemEditing,
options: [ItemListPeerItemRevealOption] = [],
additionalActions: [ContactsPeerItemAction] = [],
actionIcon: ContactsPeerItemActionIcon = .none,
index: SortIndex?,
header: ListViewItemHeader?,
action: @escaping (ContactsPeerItemPeer) -> Void,
disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil,
setPeerIdWithRevealedOptions: ((EnginePeer.Id?, EnginePeer.Id?) -> Void)? = nil,
deletePeer: ((EnginePeer.Id) -> Void)? = nil,
itemHighlighting: ContactItemHighlighting? = nil,
contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, arrowAction: (() -> Void)? = nil,
animationCache: AnimationCache? = nil,
animationRenderer: MultiAnimationRenderer? = nil
) {
self.presentationData = presentationData
self.style = style
self.sectionId = sectionId
@ -184,6 +218,8 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
self.selectable = enabled || disabledAction != nil
self.contextAction = contextAction
self.arrowAction = arrowAction
self.animationCache = animationCache
self.animationRenderer = animationRenderer
if let index = index {
var letter: String = "#"
@ -333,7 +369,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
private let avatarNode: AvatarNode
private let titleNode: TextNode
private var credibilityIconNode: ASImageNode?
private var credibilityIconView: ComponentHostView<Empty>?
private let statusNode: TextNode
private var badgeBackgroundNode: ASImageNode?
private var badgeTextNode: TextNode?
@ -571,18 +607,20 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
var currentCredibilityIconImage: UIImage?
var credibilityIcon: EmojiStatusComponent.Content?
switch item.peer {
case let .peer(peer, _):
if let peer = peer, peer.id != item.context.account.peerId {
if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIcon = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor)
} else if peer.isFake {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIcon = .fake(color: item.presentationData.theme.chat.message.incoming.scamColor)
} else if case let .user(user) = peer, let emojiStatus = user.emojiStatus {
credibilityIcon = .emojiStatus(status: emojiStatus, size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor)
} else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
credibilityIcon = .premium(color: item.presentationData.theme.list.itemAccentColor)
}
}
case .deviceContact:
@ -743,8 +781,14 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
}
var additionalTitleInset: CGFloat = 0.0
if let currentCredibilityIconImage = currentCredibilityIconImage {
additionalTitleInset += 3.0 + currentCredibilityIconImage.size.width
if let credibilityIcon = credibilityIcon {
additionalTitleInset += 3.0
switch credibilityIcon {
case .scam, .fake:
additionalTitleInset += 30.0
default:
additionalTitleInset += 16.0
}
}
if let actionButtons = actionButtons {
additionalTitleInset += 3.0
@ -930,23 +974,35 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
strongSelf.statusNode.frame = statusFrame
transition.animatePositionAdditive(node: strongSelf.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0))
if let currentCredibilityIconImage = currentCredibilityIconImage {
let iconNode: ASImageNode
if let current = strongSelf.credibilityIconNode {
iconNode = current
if let credibilityIcon = credibilityIcon, let animationCache = item.animationCache, let animationRenderer = item.animationRenderer {
let credibilityIconView: ComponentHostView<Empty>
if let current = strongSelf.credibilityIconView {
credibilityIconView = current
} else {
iconNode = ASImageNode()
iconNode.isLayerBacked = true
iconNode.displaysAsynchronously = false
iconNode.displayWithoutProcessing = true
strongSelf.offsetContainerNode.addSubnode(iconNode)
strongSelf.credibilityIconNode = iconNode
credibilityIconView = ComponentHostView<Empty>()
strongSelf.offsetContainerNode.view.addSubview(credibilityIconView)
strongSelf.credibilityIconView = credibilityIconView
}
iconNode.image = currentCredibilityIconImage
transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floorToScreenPixels(titleFrame.midY - currentCredibilityIconImage.size.height / 2.0) - UIScreenPixel), size: currentCredibilityIconImage.size))
} else if let credibilityIconNode = strongSelf.credibilityIconNode {
strongSelf.credibilityIconNode = nil
credibilityIconNode.removeFromSupernode()
let iconSize = credibilityIconView.update(
transition: .immediate,
component: AnyComponent(EmojiStatusComponent(
context: item.context,
animationCache: animationCache,
animationRenderer: animationRenderer,
content: credibilityIcon,
action: nil,
longTapAction: nil,
emojiFileUpdated: nil
)),
environment: {},
containerSize: CGSize(width: 20.0, height: 20.0)
)
transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize))
} else if let credibilityIconView = strongSelf.credibilityIconView {
strongSelf.credibilityIconView = nil
credibilityIconView.removeFromSuperview()
}
if let actionButtons = actionButtons {
@ -1146,10 +1202,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
self.statusNode.frame = statusFrame
transition.animatePositionAdditive(node: self.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0))
if let credibilityIconNode = self.credibilityIconNode {
var iconFrame = credibilityIconNode.frame
if let credibilityIconView = self.credibilityIconView {
var iconFrame = credibilityIconView.frame
iconFrame.origin.x = titleFrame.maxX + 4.0
transition.updateFrame(node: credibilityIconNode, frame: iconFrame)
transition.updateFrame(view: credibilityIconView, frame: iconFrame)
}
if let badgeBackgroundNode = self.badgeBackgroundNode, let badgeTextNode = self.badgeTextNode {

View File

@ -215,7 +215,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
}
func animateInFromAnchorRect(size: CGSize, sourceBackgroundFrame: CGRect) {
let springDuration: Double = 0.3
let springDuration: Double = 0.5
let springDamping: CGFloat = 104.0
let springDelay: Double = 0.05
let shadowInset: CGFloat = 15.0

View File

@ -114,7 +114,7 @@ private final class ExpandItemView: UIView {
transition.updateCornerRadius(layer: self.tintView.layer, cornerRadius: size.width / 2.0)
if let image = self.arrowView.image {
transition.updateFrame(view: self.arrowView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - image.size.width) / 2.0), y: floorToScreenPixels(size.height - size.width + (size.width - image.size.height) / 2.0)), size: image.size))
transition.updateFrame(view: self.arrowView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - image.size.width) / 2.0), y: floorToScreenPixels(size.height - size.width + (size.width - image.size.height) / 2.0 + 1.0)), size: image.size))
}
}
}
@ -874,7 +874,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
}
if let animateInFromAnchorRect = animateInFromAnchorRect {
let springDuration: Double = 0.3
let springDuration: Double = 0.5
let springDamping: CGFloat = 104.0
let springDelay: Double = 0.05
@ -902,11 +902,13 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
}
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak self] groupId, item, sourceView, sourceRect, sourceLayer in
performItemAction: { [weak self] groupId, item, sourceView, sourceRect, sourceLayer, isLongPress in
guard let strongSelf = self, let itemFile = item.itemFile else {
return
}
strongSelf.didTriggerExpandedReaction = isLongPress
var found = false
if let groupId = groupId.base as? String, groupId == "recent" {
for reactionItem in strongSelf.items {
@ -915,7 +917,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
found = true
strongSelf.customReactionSource = (sourceView, sourceRect, sourceLayer, reactionItem)
strongSelf.reactionSelected?(reactionItem.updateMessageReaction, false)
strongSelf.reactionSelected?(reactionItem.updateMessageReaction, isLongPress)
break
}
@ -934,7 +936,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
isCustom: true
)
strongSelf.customReactionSource = (sourceView, sourceRect, sourceLayer, reactionItem)
strongSelf.reactionSelected?(reactionItem.updateMessageReaction, false)
strongSelf.reactionSelected?(reactionItem.updateMessageReaction, isLongPress)
}
},
deleteBackwards: {
@ -1157,10 +1159,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
case .builtin:
switchToInlineImmediately = false
case .custom:
switchToInlineImmediately = true
switchToInlineImmediately = !self.didTriggerExpandedReaction
}
} else {
switchToInlineImmediately = true
switchToInlineImmediately = !self.didTriggerExpandedReaction
}
self.animationTargetView = targetView
@ -1192,15 +1194,20 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
var expandedSize: CGSize = selfTargetRect.size
if self.didTriggerExpandedReaction {
if itemNode.item.listAnimation.isVideoEmoji || itemNode.item.listAnimation.isVideoSticker {
expandedSize = CGSize(width: 80.0, height: 80.0)
} else {
expandedSize = CGSize(width: 120.0, height: 120.0)
}
}
let expandedFrame = CGRect(origin: CGPoint(x: selfTargetRect.midX - expandedSize.width / 2.0, y: selfTargetRect.midY - expandedSize.height / 2.0), size: expandedSize)
let effectFrame: CGRect
let incomingMessage: Bool = expandedFrame.midX < self.bounds.width / 2.0
if self.didTriggerExpandedReaction {
effectFrame = expandedFrame.insetBy(dx: -expandedFrame.width * 0.5, dy: -expandedFrame.height * 0.5).offsetBy(dx: incomingMessage ? (expandedFrame.width - 50.0) : (-expandedFrame.width + 50.0), dy: 0.0)
let expandFactor: CGFloat = 0.5
effectFrame = expandedFrame.insetBy(dx: -expandedFrame.width * expandFactor, dy: -expandedFrame.height * expandFactor).offsetBy(dx: incomingMessage ? (expandedFrame.width - 50.0) : (-expandedFrame.width + 50.0), dy: 0.0)
} else {
effectFrame = expandedFrame.insetBy(dx: -expandedSize.width, dy: -expandedSize.height)
}
@ -1265,7 +1272,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
cache: animationCache,
renderer: animationRenderer,
placeholderColor: UIColor(white: 0.0, alpha: 0.0),
pointSize: CGSize(width: 32.0, height: 32.0)
pointSize: CGSize(width: self.didTriggerExpandedReaction ? 64.0 : 32.0, height: self.didTriggerExpandedReaction ? 64.0 : 32.0)
)
if let sublayers = animationLayer.sublayers {
@ -1484,6 +1491,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
if let expandItemView = self.expandItemView, expandItemView.bounds.contains(self.view.convert(point, to: self.expandItemView)) {
self.currentContentHeight = 300.0
self.isExpanded = true
self.longPressRecognizer?.isEnabled = false
self.isExpandedUpdated(.animated(duration: 0.4, curve: .spring))
} else if let reaction = self.reaction(at: point) {
switch reaction {
@ -1504,6 +1512,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
}
self.hapticFeedback?.tap()
self.longPressRecognizer?.isEnabled = false
self.animateFromExtensionDistance = self.extensionDistance
self.extensionDistance = 0.0
self.visibleExtensionDistance = 0.0

View File

@ -235,7 +235,7 @@ public final class EmojiStatusSelectionController: ViewController {
strongSelf.emojiContent = emojiContent
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { _, item, _, _, _ in
performItemAction: { _, item, _, _, _, _ in
guard let strongSelf = self else {
return
}
@ -416,16 +416,14 @@ public final class EmojiStatusSelectionController: ViewController {
transition.setFrame(layer: self.cloudShadowLayer1, frame: cloudFrame1)
transition.setCornerRadius(layer: self.cloudLayer1, cornerRadius: cloudFrame1.width / 2.0)
//transition.setFrame(view: componentView, frame: componentFrame)
transition.setFrame(view: componentView, frame: CGRect(origin: componentFrame.origin, size: CGSize(width: componentFrame.width, height: componentFrame.height)))
if animateIn {
//self.allowsGroupOpacity = true
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak self] _ in
self?.allowsGroupOpacity = false
})
let contentDuration: Double = 0.5
let contentDuration: Double = 0.3
let contentDelay: Double = 0.14
let initialContentFrame = CGRect(origin: CGPoint(x: cloudFrame0.midX - 24.0, y: componentFrame.minY), size: CGSize(width: 24.0 * 2.0, height: 24.0 * 2.0))
@ -439,16 +437,9 @@ public final class EmojiStatusSelectionController: ViewController {
componentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.04, delay: contentDelay)
self.componentShadowLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.04, delay: contentDelay)
//componentView.layer.animateScale(from: 0.5, to: 1.0, duration: contentDuration, delay: contentDelay, timingFunction: kCAMediaTimingFunctionSpring)
//self.componentShadowLayer.animateScale(from: 0.5, to: 1.0, duration: contentDuration, delay: contentDelay, timingFunction: kCAMediaTimingFunctionSpring)
let initialComponentShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: initialContentFrame.size), cornerRadius: 24.0).cgPath
self.componentShadowLayer.animate(from: initialComponentShadowPath, to: self.componentShadowLayer.shadowPath!, keyPath: "shadowPath", timingFunction: kCAMediaTimingFunctionSpring, duration: contentDuration, delay: contentDelay)
//componentView.layer.animateScale(from: (componentView.bounds.width - 10.0) / componentView.bounds.width, to: 1.0, duration: 0.4, delay: 0.1, timingFunction: kCAMediaTimingFunctionSpring)
//componentView.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, delay: contentDelay)
//self.componentShadowLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, delay: contentDelay)
self.cloudLayer0.animateScale(from: 0.01, to: 1.0, duration: 0.4, delay: 0.05, timingFunction: kCAMediaTimingFunctionSpring)
self.cloudShadowLayer0.animateScale(from: 0.01, to: 1.0, duration: 0.4, delay: 0.05, timingFunction: kCAMediaTimingFunctionSpring)

View File

@ -1543,7 +1543,7 @@ public final class EmojiPagerContentComponent: Component {
}
public final class InputInteraction {
public let performItemAction: (AnyHashable, Item, UIView, CGRect, CALayer) -> Void
public let performItemAction: (AnyHashable, Item, UIView, CGRect, CALayer, Bool) -> Void
public let deleteBackwards: () -> Void
public let openStickerSettings: () -> Void
public let openFeatured: () -> Void
@ -1560,7 +1560,7 @@ public final class EmojiPagerContentComponent: Component {
public let externalBackground: ExternalBackground?
public init(
performItemAction: @escaping (AnyHashable, Item, UIView, CGRect, CALayer) -> Void,
performItemAction: @escaping (AnyHashable, Item, UIView, CGRect, CALayer, Bool) -> Void,
deleteBackwards: @escaping () -> Void,
openStickerSettings: @escaping () -> Void,
openFeatured: @escaping () -> Void,
@ -1777,6 +1777,7 @@ public final class EmojiPagerContentComponent: Component {
public let itemGroups: [ItemGroup]
public let itemLayoutType: ItemLayoutType
public let warpContentsOnEdges: Bool
public let enableLongPress: Bool
public init(
id: AnyHashable,
@ -1787,7 +1788,8 @@ public final class EmojiPagerContentComponent: Component {
inputInteractionHolder: InputInteractionHolder,
itemGroups: [ItemGroup],
itemLayoutType: ItemLayoutType,
warpContentsOnEdges: Bool
warpContentsOnEdges: Bool,
enableLongPress: Bool
) {
self.id = id
self.context = context
@ -1798,6 +1800,7 @@ public final class EmojiPagerContentComponent: Component {
self.itemGroups = itemGroups
self.itemLayoutType = itemLayoutType
self.warpContentsOnEdges = warpContentsOnEdges
self.enableLongPress = enableLongPress
}
public static func ==(lhs: EmojiPagerContentComponent, rhs: EmojiPagerContentComponent) -> Bool {
@ -1831,6 +1834,9 @@ public final class EmojiPagerContentComponent: Component {
if lhs.warpContentsOnEdges != rhs.warpContentsOnEdges {
return false
}
if lhs.enableLongPress != rhs.enableLongPress {
return false
}
return true
}
@ -2556,6 +2562,8 @@ public final class EmojiPagerContentComponent: Component {
private var activeItemUpdated: ActionSlot<(AnyHashable, AnyHashable?, Transition)>?
private var itemLayout: ItemLayout?
private var longTapRecognizer: UILongPressGestureRecognizer?
override init(frame: CGRect) {
self.backgroundView = BlurredBackgroundView(color: nil)
@ -2607,6 +2615,12 @@ public final class EmojiPagerContentComponent: Component {
self.scrollView.addSubview(self.placeholdersContainerView)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:)))
longTapRecognizer.minimumPressDuration = 0.2
self.longTapRecognizer = longTapRecognizer
self.addGestureRecognizer(longTapRecognizer)
longTapRecognizer.isEnabled = false
}
required init?(coder: NSCoder) {
@ -2665,7 +2679,7 @@ public final class EmojiPagerContentComponent: Component {
let distance = sqrt(distanceVector.x * distanceVector.x + distanceVector.y * distanceVector.y)
let distanceNorm = min(1.0, max(0.0, distance / self.bounds.width))
let delay = 0.05 + (distanceNorm) * 0.4
let delay = 0.05 + (distanceNorm) * 0.3
itemLayer.animateScale(from: 0.01, to: 1.0, duration: 0.18, delay: delay, timingFunction: kCAMediaTimingFunctionSpring)
}
}
@ -2692,7 +2706,7 @@ public final class EmojiPagerContentComponent: Component {
let clippedDistance = max(0.0, min(distance, maxDistance))
let distanceNorm = clippedDistance / maxDistance
let delay = listViewAnimationCurveSystem(distanceNorm) * 0.16
let delay = listViewAnimationCurveSystem(distanceNorm) * 0.1
itemLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay)
itemLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.6, delay: delay)
@ -3196,7 +3210,7 @@ public final class EmojiPagerContentComponent: Component {
foundExactItem = true
foundItem = true
if !itemLayer.displayPlaceholder {
component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer)
component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer, false)
}
}
@ -3204,7 +3218,7 @@ public final class EmojiPagerContentComponent: Component {
if let (item, itemKey) = self.item(atPoint: recognizer.location(in: self), extendedHitRange: true), let itemLayer = self.visibleItemLayers[itemKey] {
foundItem = true
if !itemLayer.displayPlaceholder {
component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer)
component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer, false)
}
}
}
@ -3213,6 +3227,87 @@ public final class EmojiPagerContentComponent: Component {
}
}
private let longPressDuration: Double = 0.5
private var longPressItem: EmojiPagerContentComponent.View.ItemLayer.Key?
private var hapticFeedback: HapticFeedback?
private var continuousHaptic: AnyObject?
private var longPressTimer: SwiftSignalKit.Timer?
@objc private func longPressGesture(_ recognizer: UILongPressGestureRecognizer) {
switch recognizer.state {
case .began:
let point = recognizer.location(in: self)
guard let item = self.item(atPoint: point), let itemLayer = self.visibleItemLayers[item.1] else {
return
}
switch item.0.content {
case .animation:
break
default:
return
}
self.longPressItem = item.1
if #available(iOS 13.0, *) {
self.continuousHaptic = try? ContinuousHaptic(duration: longPressDuration)
}
if self.hapticFeedback == nil {
self.hapticFeedback = HapticFeedback()
}
let transition = Transition(animation: .curve(duration: longPressDuration, curve: .easeInOut))
transition.setScale(layer: itemLayer, scale: 1.3)
self.longPressTimer?.invalidate()
self.longPressTimer = SwiftSignalKit.Timer(timeout: longPressDuration, repeat: false, completion: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.longTapRecognizer?.state = .ended
}, queue: .mainQueue())
self.longPressTimer?.start()
case .changed:
let point = recognizer.location(in: self)
if let longPressItem = self.longPressItem, let item = self.item(atPoint: point), longPressItem == item.1 {
} else {
self.longTapRecognizer?.state = .cancelled
}
case .cancelled:
self.longPressTimer?.invalidate()
self.continuousHaptic = nil
if let itemKey = self.longPressItem {
self.longPressItem = nil
if let itemLayer = self.visibleItemLayers[itemKey] {
let transition = Transition(animation: .curve(duration: 0.3, curve: .spring))
transition.setScale(layer: itemLayer, scale: 1.0)
}
}
case .ended:
self.longPressTimer?.invalidate()
self.continuousHaptic = nil
if let itemKey = self.longPressItem {
self.longPressItem = nil
if let component = self.component, let itemLayer = self.visibleItemLayers[itemKey] {
component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, itemLayer.item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer, true)
} else {
if let itemLayer = self.visibleItemLayers[itemKey] {
let transition = Transition(animation: .curve(duration: 0.3, curve: .spring))
transition.setScale(layer: itemLayer, scale: 1.0)
}
}
}
default:
break
}
}
private func item(atPoint point: CGPoint, extendedHitRange: Bool = false) -> (Item, ItemLayer.Key)? {
let localPoint = self.convert(point, to: self.scrollView)
@ -3411,7 +3506,7 @@ public final class EmojiPagerContentComponent: Component {
guard let strongSelf = self, let component = strongSelf.component else {
return
}
component.inputInteractionHolder.inputInteraction?.performItemAction(groupId, item, view, rect, layer)
component.inputInteractionHolder.inputInteraction?.performItemAction(groupId, item, view, rect, layer, false)
}
)
self.visibleGroupHeaders[itemGroup.groupId] = groupHeaderView
@ -4133,6 +4228,10 @@ public final class EmojiPagerContentComponent: Component {
self.updateIsWarpEnabled(isEnabled: component.warpContentsOnEdges)
if let longTapRecognizer = self.longTapRecognizer {
longTapRecognizer.isEnabled = component.enableLongPress
}
if let shimmerHostView = self.shimmerHostView {
transition.setFrame(view: shimmerHostView, frame: CGRect(origin: CGPoint(), size: availableSize))
}
@ -4892,7 +4991,8 @@ public final class EmojiPagerContentComponent: Component {
)
},
itemLayoutType: .compact,
warpContentsOnEdges: isReactionSelection || isStatusSelection
warpContentsOnEdges: isReactionSelection || isStatusSelection,
enableLongPress: isReactionSelection
)
}
return emojiItems

View File

@ -505,7 +505,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
)
},
itemLayoutType: .detailed,
warpContentsOnEdges: false
warpContentsOnEdges: false,
enableLongPress: false
)
}
@ -902,7 +903,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
var premiumToastCounter = 0
self.emojiInputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak self, weak interfaceInteraction, weak controllerInteraction] _, item, _, _, _ in
performItemAction: { [weak self, weak interfaceInteraction, weak controllerInteraction] _, item, _, _, _, _ in
let _ = (ChatEntityKeyboardInputNode.hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: true) |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in
guard let strongSelf = self, let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else {
return
@ -1099,7 +1100,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
)
}
self.stickerInputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak controllerInteraction, weak interfaceInteraction] groupId, item, view, rect, layer in
performItemAction: { [weak controllerInteraction, weak interfaceInteraction] groupId, item, view, rect, layer, _ in
let _ = (ChatEntityKeyboardInputNode.hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: false) |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in
guard let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else {
return
@ -1918,7 +1919,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
self.clipsToBounds = true
let inputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak self] _, item, _, _, _ in
performItemAction: { [weak self] _, item, _, _, _, _ in
let _ = (ChatEntityKeyboardInputNode.hasPremium(context: context, chatPeerId: nil, premiumIfSavedMessages: false) |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in
guard let strongSelf = self else {
return

View File

@ -143,6 +143,22 @@ private final class StatusReactionNode: ASDisplayNode {
animateIdle = false
}
let placeholderColor: UIColor
switch type {
case .BubbleIncoming:
placeholderColor = theme.chat.message.incoming.mediaPlaceholderColor
case .BubbleOutgoing:
placeholderColor = theme.chat.message.incoming.mediaPlaceholderColor
case .ImageIncoming:
placeholderColor = UIColor(white: 1.0, alpha: 0.1)
case .ImageOutgoing:
placeholderColor = UIColor(white: 1.0, alpha: 0.1)
case .FreeIncoming:
placeholderColor = UIColor(white: 0.0, alpha: 0.1)
case .FreeOutgoing:
placeholderColor = UIColor(white: 0.0, alpha: 0.1)
}
self.iconView.update(
size: boundingImageSize,
context: context,
@ -150,7 +166,7 @@ private final class StatusReactionNode: ASDisplayNode {
fileId: fileId,
animationCache: animationCache,
animationRenderer: animationRenderer,
placeholderColor: .gray,
placeholderColor: placeholderColor,
animateIdle: animateIdle,
reaction: value,
transition: .immediate