Reaction animations

This commit is contained in:
Peter 2019-08-16 16:24:22 +03:00
parent 23682c7e34
commit ae165ed4a5
19 changed files with 1257 additions and 34 deletions

View File

@ -372,6 +372,13 @@
location = "group:submodules/AvatarNode/AvatarNode_Xcode.xcodeproj">
</FileRef>
</Group>
<Group
location = "container:"
name = "Chat">
<FileRef
location = "group:/Users/peter/build/telegram-temp/telegram-ios/submodules/ReactionSelectionNode/ReactionSelectionNode_Xcode.xcodeproj">
</FileRef>
</Group>
<FileRef
location = "group:submodules/LegacyComponents/LegacyComponents_Xcode.xcodeproj">
</FileRef>

View File

@ -309,6 +309,9 @@ public final class AnimatedStickerNode: ASDisplayNode {
private let fetchDisposable = MetaDisposable()
private let eventsNode: AnimatedStickerNodeDisplayEvents
public var automaticallyLoadFirstFrame: Bool = false
public var playToCompletionOnStop: Bool = false
public var started: () -> Void = {}
private var reportedStarted = false
@ -320,6 +323,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
private var renderer: (AnimationRenderer & ASDisplayNode)?
private var isPlaying: Bool = false
private var canDisplayFirstFrame: Bool = false
private var playbackMode: AnimatedStickerPlaybackMode = .loop
private let playbackStatus = Promise<AnimatedStickerStatus>()
@ -394,6 +398,8 @@ public final class AnimatedStickerNode: ASDisplayNode {
}
if strongSelf.isPlaying {
strongSelf.play()
} else if strongSelf.canDisplayFirstFrame {
strongSelf.play(firstFrame: true)
}
}))
case .cached:
@ -403,6 +409,8 @@ public final class AnimatedStickerNode: ASDisplayNode {
strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead])
if strongSelf.isPlaying {
strongSelf.play()
} else if strongSelf.canDisplayFirstFrame {
strongSelf.play(firstFrame: true)
}
}
}))
@ -424,9 +432,16 @@ public final class AnimatedStickerNode: ASDisplayNode {
self.stop()
}
}
let canDisplayFirstFrame = self.automaticallyLoadFirstFrame && self.isDisplaying
if self.canDisplayFirstFrame != canDisplayFirstFrame {
self.canDisplayFirstFrame = canDisplayFirstFrame
if canDisplayFirstFrame {
self.play(firstFrame: true)
}
}
}
public func play() {
public func play(firstFrame: Bool = false) {
let directData = self.directData
let cachedData = self.cachedData
let queue = self.queue
@ -451,7 +466,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
let duration: Double = frameSource.frameRate > 0 ? Double(frameSource.frameCount) / Double(frameSource.frameRate) : 0
let frameRate = frameSource.frameRate
let timer = SwiftSignalKit.Timer(timeout: 1.0 / Double(frameRate), repeat: true, completion: {
let timer = SwiftSignalKit.Timer(timeout: 1.0 / Double(frameRate), repeat: !firstFrame, completion: {
let maybeFrame = frameQueue.syncWith { frameQueue in
return frameQueue.take()
}
@ -492,6 +507,9 @@ public final class AnimatedStickerNode: ASDisplayNode {
public func stop() {
self.reportedStarted = false
self.timer.swap(nil)?.invalidate()
if self.playToCompletionOnStop {
self.seekToStart()
}
}
public func seekToStart() {

View File

@ -63,24 +63,29 @@ public enum ContainedViewLayoutTransition {
}
public extension ContainedViewLayoutTransition {
func updateFrame(node: ASDisplayNode, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
func updateFrame(node: ASDisplayNode, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
if node.frame.equalTo(frame) && !force {
completion?(true)
} else {
switch self {
case .immediate:
node.frame = frame
case .immediate:
node.frame = frame
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousFrame: CGRect
if beginWithCurrentState, let presentation = node.layer.presentation() {
previousFrame = presentation.frame
} else {
previousFrame = node.frame
}
node.frame = frame
node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
if let completion = completion {
completion(true)
completion(result)
}
case let .animated(duration, curve):
let previousFrame = node.frame
node.frame = frame
node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
if let completion = completion {
completion(result)
}
})
})
}
}
}
@ -147,7 +152,7 @@ public extension ContainedViewLayoutTransition {
}
}
func updatePosition(node: ASDisplayNode, position: CGPoint, completion: ((Bool) -> Void)? = nil) {
func updatePosition(node: ASDisplayNode, position: CGPoint, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
if node.position.equalTo(position) {
completion?(true)
} else {
@ -158,7 +163,12 @@ public extension ContainedViewLayoutTransition {
completion(true)
}
case let .animated(duration, curve):
let previousPosition = node.position
let previousPosition: CGPoint
if beginWithCurrentState {
previousPosition = node.layer.presentation()?.position ?? node.position
} else {
previousPosition = node.position
}
node.position = position
node.layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
@ -498,7 +508,7 @@ public extension ContainedViewLayoutTransition {
}
}
func updateTransformScale(node: ASDisplayNode, scale: CGFloat, completion: ((Bool) -> Void)? = nil) {
func updateTransformScale(node: ASDisplayNode, scale: CGFloat, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
let t = node.layer.transform
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
if currentScale.isEqual(to: scale) {
@ -515,8 +525,15 @@ public extension ContainedViewLayoutTransition {
completion(true)
}
case let .animated(duration, curve):
let previousScale: CGFloat
if beginWithCurrentState, let presentation = node.layer.presentation() {
let t = presentation.transform
previousScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
} else {
previousScale = currentScale
}
node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
node.layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
node.layer.animateScale(from: previousScale, to: scale, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
@ -581,6 +598,47 @@ public extension ContainedViewLayoutTransition {
}
}
func updateSublayerTransformScaleAndOffset(node: ASDisplayNode, scale: CGFloat, offset: CGPoint, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
if !node.isNodeLoaded {
node.subnodeTransform = CATransform3DMakeScale(scale, scale, 1.0)
return
}
let t = node.layer.sublayerTransform
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
let currentOffset = CGPoint(x: t.m41, y: t.m42)
if currentScale.isEqual(to: scale) && currentOffset == offset {
if let completion = completion {
completion(true)
}
return
}
let transform = CATransform3DTranslate(CATransform3DMakeScale(scale, scale, 1.0), offset.x, offset.y, 0.0)
switch self {
case .immediate:
node.layer.sublayerTransform = transform
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let initialTransform: CATransform3D
if beginWithCurrentState, node.isNodeLoaded {
initialTransform = node.layer.presentation()?.sublayerTransform ?? t
} else {
initialTransform = t
}
node.layer.sublayerTransform = transform
node.layer.animate(from: NSValue(caTransform3D: initialTransform), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
result in
if let completion = completion {
completion(result)
}
})
}
}
func updateSublayerTransformScale(node: ASDisplayNode, scale: CGPoint, completion: ((Bool) -> Void)? = nil) {
if !node.isNodeLoaded {
node.subnodeTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0)

View File

@ -5131,7 +5131,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -5211,7 +5211,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -5257,7 +5257,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@ -5365,7 +5365,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -5439,7 +5439,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@ -5513,7 +5513,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@ -5588,7 +5588,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -5662,7 +5662,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@ -5742,7 +5742,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -5816,7 +5816,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@ -5890,7 +5890,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>

View File

@ -0,0 +1,579 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
D03E45EA2305E3F30049C28B /* ReactionSelectionNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E45E82305E3F30049C28B /* ReactionSelectionNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
D03E45F52305EB7C0049C28B /* ReactionSelectionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E45F42305EB7C0049C28B /* ReactionSelectionNode.swift */; };
D03E45F82305EB880049C28B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03E45F72305EB880049C28B /* Foundation.framework */; };
D03E45FA2305EB8B0049C28B /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03E45F92305EB8B0049C28B /* AsyncDisplayKit.framework */; };
D03E45FC2305EB8E0049C28B /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03E45FB2305EB8E0049C28B /* UIKit.framework */; };
D03E45FE2305EB910049C28B /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03E45FD2305EB910049C28B /* Display.framework */; };
D03E46002305EE710049C28B /* AnimationUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03E45FF2305EE710049C28B /* AnimationUI.framework */; };
D03E46022305EE750049C28B /* Postbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03E46012305EE750049C28B /* Postbox.framework */; };
D03E46042305EE790049C28B /* TelegramCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03E46032305EE790049C28B /* TelegramCore.framework */; };
D03E46082305EEDD0049C28B /* ReactionSelectionParentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E46072305EEDD0049C28B /* ReactionSelectionParentNode.swift */; };
D03E460A2305EF900049C28B /* ReactionSwipeGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E46092305EF900049C28B /* ReactionSwipeGestureRecognizer.swift */; };
D03E460C2305EFD80049C28B /* ReactionGestureItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E460B2305EFD80049C28B /* ReactionGestureItem.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
D03E45E52305E3F30049C28B /* ReactionSelectionNode.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactionSelectionNode.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D03E45E82305E3F30049C28B /* ReactionSelectionNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactionSelectionNode.h; sourceTree = "<group>"; };
D03E45E92305E3F30049C28B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D03E45F42305EB7C0049C28B /* ReactionSelectionNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionSelectionNode.swift; sourceTree = "<group>"; };
D03E45F72305EB880049C28B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
D03E45F92305EB8B0049C28B /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D03E45FB2305EB8E0049C28B /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
D03E45FD2305EB910049C28B /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D03E45FF2305EE710049C28B /* AnimationUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AnimationUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D03E46012305EE750049C28B /* Postbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Postbox.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D03E46032305EE790049C28B /* TelegramCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TelegramCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D03E46072305EEDD0049C28B /* ReactionSelectionParentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionSelectionParentNode.swift; sourceTree = "<group>"; };
D03E46092305EF900049C28B /* ReactionSwipeGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionSwipeGestureRecognizer.swift; sourceTree = "<group>"; };
D03E460B2305EFD80049C28B /* ReactionGestureItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionGestureItem.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
D03E45E22305E3F30049C28B /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D03E46042305EE790049C28B /* TelegramCore.framework in Frameworks */,
D03E46022305EE750049C28B /* Postbox.framework in Frameworks */,
D03E46002305EE710049C28B /* AnimationUI.framework in Frameworks */,
D03E45FE2305EB910049C28B /* Display.framework in Frameworks */,
D03E45FC2305EB8E0049C28B /* UIKit.framework in Frameworks */,
D03E45FA2305EB8B0049C28B /* AsyncDisplayKit.framework in Frameworks */,
D03E45F82305EB880049C28B /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
D03E45DB2305E3F30049C28B = {
isa = PBXGroup;
children = (
D03E45E92305E3F30049C28B /* Info.plist */,
D03E45E72305E3F30049C28B /* Sources */,
D03E45E62305E3F30049C28B /* Products */,
D03E45F62305EB870049C28B /* Frameworks */,
);
sourceTree = "<group>";
};
D03E45E62305E3F30049C28B /* Products */ = {
isa = PBXGroup;
children = (
D03E45E52305E3F30049C28B /* ReactionSelectionNode.framework */,
);
name = Products;
sourceTree = "<group>";
};
D03E45E72305E3F30049C28B /* Sources */ = {
isa = PBXGroup;
children = (
D03E45F42305EB7C0049C28B /* ReactionSelectionNode.swift */,
D03E45E82305E3F30049C28B /* ReactionSelectionNode.h */,
D03E46072305EEDD0049C28B /* ReactionSelectionParentNode.swift */,
D03E46092305EF900049C28B /* ReactionSwipeGestureRecognizer.swift */,
D03E460B2305EFD80049C28B /* ReactionGestureItem.swift */,
);
path = Sources;
sourceTree = "<group>";
};
D03E45F62305EB870049C28B /* Frameworks */ = {
isa = PBXGroup;
children = (
D03E46032305EE790049C28B /* TelegramCore.framework */,
D03E46012305EE750049C28B /* Postbox.framework */,
D03E45FF2305EE710049C28B /* AnimationUI.framework */,
D03E45FD2305EB910049C28B /* Display.framework */,
D03E45FB2305EB8E0049C28B /* UIKit.framework */,
D03E45F92305EB8B0049C28B /* AsyncDisplayKit.framework */,
D03E45F72305EB880049C28B /* Foundation.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
D03E45E02305E3F30049C28B /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
D03E45EA2305E3F30049C28B /* ReactionSelectionNode.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
D03E45E42305E3F30049C28B /* ReactionSelectionNode */ = {
isa = PBXNativeTarget;
buildConfigurationList = D03E45ED2305E3F30049C28B /* Build configuration list for PBXNativeTarget "ReactionSelectionNode" */;
buildPhases = (
D03E45E02305E3F30049C28B /* Headers */,
D03E45E12305E3F30049C28B /* Sources */,
D03E45E22305E3F30049C28B /* Frameworks */,
D03E45E32305E3F30049C28B /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = ReactionSelectionNode;
productName = ReactionSelectionNode;
productReference = D03E45E52305E3F30049C28B /* ReactionSelectionNode.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
D03E45DC2305E3F30049C28B /* Project object */ = {
isa = PBXProject;
attributes = {
DefaultBuildSystemTypeForWorkspace = Latest;
LastUpgradeCheck = 1030;
ORGANIZATIONNAME = "Telegram Messenger LLP";
TargetAttributes = {
D03E45E42305E3F30049C28B = {
CreatedOnToolsVersion = 10.3;
LastSwiftMigration = 1030;
};
};
};
buildConfigurationList = D03E45DF2305E3F30049C28B /* Build configuration list for PBXProject "ReactionSelectionNode_Xcode" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = D03E45DB2305E3F30049C28B;
productRefGroup = D03E45E62305E3F30049C28B /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
D03E45E42305E3F30049C28B /* ReactionSelectionNode */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
D03E45E32305E3F30049C28B /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
D03E45E12305E3F30049C28B /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D03E460A2305EF900049C28B /* ReactionSwipeGestureRecognizer.swift in Sources */,
D03E46082305EEDD0049C28B /* ReactionSelectionParentNode.swift in Sources */,
D03E460C2305EFD80049C28B /* ReactionGestureItem.swift in Sources */,
D03E45F52305EB7C0049C28B /* ReactionSelectionNode.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
D03E45EB2305E3F30049C28B /* DebugAppStoreLLC */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = DebugAppStoreLLC;
};
D03E45EC2305E3F30049C28B /* ReleaseAppStoreLLC */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = ReleaseAppStoreLLC;
};
D03E45EE2305E3F30049C28B /* DebugAppStoreLLC */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACH_O_TYPE = staticlib;
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.ReactionSelectionNode;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = DebugAppStoreLLC;
};
D03E45EF2305E3F30049C28B /* ReleaseAppStoreLLC */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACH_O_TYPE = staticlib;
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.ReactionSelectionNode;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = ReleaseAppStoreLLC;
};
D03E45F02305E4200049C28B /* DebugHockeyapp */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = DebugHockeyapp;
};
D03E45F12305E4200049C28B /* DebugHockeyapp */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACH_O_TYPE = staticlib;
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.ReactionSelectionNode;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = DebugHockeyapp;
};
D03E45F22305E4290049C28B /* ReleaseHockeyappInternal */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = ReleaseHockeyappInternal;
};
D03E45F32305E4290049C28B /* ReleaseHockeyappInternal */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACH_O_TYPE = staticlib;
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.ReactionSelectionNode;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = ReleaseHockeyappInternal;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
D03E45DF2305E3F30049C28B /* Build configuration list for PBXProject "ReactionSelectionNode_Xcode" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D03E45EB2305E3F30049C28B /* DebugAppStoreLLC */,
D03E45F02305E4200049C28B /* DebugHockeyapp */,
D03E45EC2305E3F30049C28B /* ReleaseAppStoreLLC */,
D03E45F22305E4290049C28B /* ReleaseHockeyappInternal */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = ReleaseAppStoreLLC;
};
D03E45ED2305E3F30049C28B /* Build configuration list for PBXNativeTarget "ReactionSelectionNode" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D03E45EE2305E3F30049C28B /* DebugAppStoreLLC */,
D03E45F12305E4200049C28B /* DebugHockeyapp */,
D03E45EF2305E3F30049C28B /* ReleaseAppStoreLLC */,
D03E45F32305E4290049C28B /* ReleaseHockeyappInternal */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = ReleaseAppStoreLLC;
};
/* End XCConfigurationList section */
};
rootObject = D03E45DC2305E3F30049C28B /* Project object */;
}

View File

@ -0,0 +1,23 @@
import Foundation
import Postbox
import TelegramCore
public struct ReactionGestureItemValue {
public var value: String
public var text: String
public var file: TelegramMediaFile
public init(value: String, text: String, file: TelegramMediaFile) {
self.value = value
self.text = text
self.file = file
}
}
public final class ReactionGestureItem {
public let value: ReactionGestureItemValue
public init(value: ReactionGestureItemValue) {
self.value = value
}
}

View File

@ -0,0 +1,19 @@
//
// ReactionSelectionNode.h
// ReactionSelectionNode
//
// Created by Peter on 8/15/19.
// Copyright © 2019 Telegram Messenger LLP. All rights reserved.
//
#import <UIKit/UIKit.h>
//! Project version number for ReactionSelectionNode.
FOUNDATION_EXPORT double ReactionSelectionNodeVersionNumber;
//! Project version string for ReactionSelectionNode.
FOUNDATION_EXPORT const unsigned char ReactionSelectionNodeVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <ReactionSelectionNode/PublicHeader.h>

View File

@ -0,0 +1,226 @@
import Foundation
import AsyncDisplayKit
import AnimationUI
import Display
import Postbox
import TelegramCore
private let shadowBlur: CGFloat = 8.0
private let minimizedReactionSize: CGFloat = 30.0
private let maximizedReactionSize: CGFloat = 60.0
private func generateBubbleImage(foreground: UIColor, diameter: CGFloat) -> UIImage? {
return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(foreground.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
})?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0 + shadowBlur / 2.0), topCapHeight: Int(diameter / 2.0 + shadowBlur / 2.0))
}
private func generateBubbleShadowImage(shadow: UIColor, diameter: CGFloat) -> UIImage? {
return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.white.cgColor)
context.setShadow(offset: CGSize(), blur: shadowBlur, color: shadow.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
context.setShadow(offset: CGSize(), blur: 1.0, color: shadow.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
context.setFillColor(UIColor.clear.cgColor)
context.setBlendMode(.copy)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter)))
})?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0 + shadowBlur / 2.0), topCapHeight: Int(diameter / 2.0 + shadowBlur / 2.0))
}
private final class ReactionNode: ASDisplayNode {
private let reaction: ReactionGestureItem
private let animationNode: AnimatedStickerNode
var isMaximized: Bool?
private let intrinsicSize: CGSize
private let intrinsicOffset: CGPoint
init(account: Account, reaction: ReactionGestureItem) {
self.reaction = reaction
self.animationNode = AnimatedStickerNode()
self.animationNode.automaticallyLoadFirstFrame = true
self.animationNode.playToCompletionOnStop = true
//self.animationNode.backgroundColor = .lightGray
var intrinsicSize = CGSize(width: maximizedReactionSize + 18.0, height: maximizedReactionSize + 18.0)
switch reaction.value.value {
case "😳":
intrinsicSize.width += 8.0
intrinsicSize.height += 8.0
self.intrinsicOffset = CGPoint(x: 0.0, y: -4.0)
case "👍":
intrinsicSize.width += 20.0
intrinsicSize.height += 20.0
self.intrinsicOffset = CGPoint(x: 0.0, y: 4.0)
default:
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0)
}
self.intrinsicSize = intrinsicSize
super.init()
//self.backgroundColor = .green
self.addSubnode(self.animationNode)
self.animationNode.visibility = true
self.animationNode.setup(account: account, resource: reaction.value.file.resource, width: Int(self.intrinsicSize.width) * 2, height: Int(self.intrinsicSize.height) * 2, mode: .direct)
self.animationNode.updateLayout(size: self.intrinsicSize)
self.animationNode.frame = CGRect(origin: CGPoint(), size: self.intrinsicSize)
}
func updateLayout(size: CGSize, scale: CGFloat, transition: ContainedViewLayoutTransition) {
transition.updatePosition(node: self.animationNode, position: CGPoint(x: size.width / 2.0 + self.intrinsicOffset.x * scale, y: size.height / 2.0 + self.intrinsicOffset.y * scale), beginWithCurrentState: true)
transition.updateTransformScale(node: self.animationNode, scale: scale, beginWithCurrentState: true)
}
func updateIsAnimating(_ isAnimating: Bool, animated: Bool) {
if isAnimating {
self.animationNode.visibility = true
} else {
self.animationNode.visibility = false
}
}
}
final class ReactionSelectionNode: ASDisplayNode {
private let backgroundNode: ASImageNode
private let backgroundShadowNode: ASImageNode
private let bubbleNodes: [(ASImageNode, ASImageNode)]
private let reactionNodes: [ReactionNode]
public init(account: Account, reactions: [ReactionGestureItem]) {
self.backgroundNode = ASImageNode()
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.image = generateBubbleImage(foreground: .white, diameter: 42.0)
self.backgroundShadowNode = ASImageNode()
self.backgroundShadowNode.displaysAsynchronously = false
self.backgroundShadowNode.displayWithoutProcessing = true
self.backgroundShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: 42.0)
self.bubbleNodes = (0 ..< 2).map { i -> (ASImageNode, ASImageNode) in
let imageNode = ASImageNode()
imageNode.image = generateBubbleImage(foreground: .white, diameter: CGFloat(i + 1) * 8.0)
imageNode.displaysAsynchronously = false
imageNode.displayWithoutProcessing = true
let shadowNode = ASImageNode()
shadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: CGFloat(i + 1) * 8.0)
shadowNode.displaysAsynchronously = false
shadowNode.displayWithoutProcessing = true
return (imageNode, shadowNode)
}
self.reactionNodes = reactions.map { reaction -> ReactionNode in
return ReactionNode(account: account, reaction: reaction)
}
super.init()
self.bubbleNodes.forEach { _, shadow in
self.addSubnode(shadow)
}
self.addSubnode(self.backgroundShadowNode)
self.bubbleNodes.forEach { foreground, _ in
self.addSubnode(foreground)
}
self.addSubnode(self.backgroundNode)
self.reactionNodes.forEach(self.addSubnode(_:))
}
func updateLayout(constrainedSize: CGSize, startingPoint: CGPoint, offsetFromStart: CGFloat, isInitial: Bool) {
let backgroundHeight: CGFloat = 42.0
let reactionSpacing: CGFloat = 6.0
let minimizedReactionVerticalInset: CGFloat = floor((backgroundHeight - minimizedReactionSize) / 2.0)
let contentWidth: CGFloat = CGFloat(self.reactionNodes.count - 1) * (minimizedReactionSize) + maximizedReactionSize + CGFloat(self.reactionNodes.count + 1) * reactionSpacing
var backgroundFrame = CGRect(origin: CGPoint(x: -shadowBlur, y: -shadowBlur), size: CGSize(width: contentWidth + shadowBlur * 2.0, height: backgroundHeight + shadowBlur * 2.0))
backgroundFrame = backgroundFrame.offsetBy(dx: startingPoint.x - contentWidth + backgroundHeight / 2.0, dy: startingPoint.y - backgroundHeight - 16.0)
self.backgroundNode.frame = backgroundFrame
self.backgroundShadowNode.frame = backgroundFrame
let anchorMinX = backgroundFrame.minX + shadowBlur + backgroundHeight / 2.0
let anchorMaxX = backgroundFrame.maxX - shadowBlur - backgroundHeight / 2.0
let anchorX = max(anchorMinX, min(anchorMaxX, offsetFromStart))
var reactionX: CGFloat = backgroundFrame.minX + shadowBlur + reactionSpacing
var maximizedIndex = Int(((anchorX - anchorMinX) / (anchorMaxX - anchorMinX)) * CGFloat(self.reactionNodes.count))
maximizedIndex = max(0, min(self.reactionNodes.count - 1, maximizedIndex))
for i in 0 ..< self.reactionNodes.count {
let isMaximized = i == maximizedIndex
let reactionSize: CGFloat
if isMaximized {
reactionSize = maximizedReactionSize
} else {
reactionSize = minimizedReactionSize
}
let transition: ContainedViewLayoutTransition
if isInitial {
transition = .immediate
} else {
transition = .animated(duration: 0.18, curve: .easeInOut)
}
if self.reactionNodes[i].isMaximized != isMaximized {
self.reactionNodes[i].isMaximized = isMaximized
self.reactionNodes[i].updateIsAnimating(isMaximized, animated: !isInitial)
}
var reactionFrame = CGRect(origin: CGPoint(x: reactionX, y: backgroundFrame.maxY - shadowBlur - minimizedReactionVerticalInset - reactionSize), size: CGSize(width: reactionSize, height: reactionSize))
if isMaximized {
reactionFrame.origin.x -= 9.0
reactionFrame.size.width += 18.0
}
self.reactionNodes[i].updateLayout(size: reactionFrame.size, scale: reactionFrame.size.width / (maximizedReactionSize + 18.0), transition: transition)
transition.updateFrame(node: self.reactionNodes[i], frame: reactionFrame, beginWithCurrentState: true)
reactionX += reactionSize + reactionSpacing
}
let mainBubbleFrame = CGRect(origin: CGPoint(x: anchorX - 8.0 - shadowBlur, y: backgroundFrame.maxY - shadowBlur - 8.0 - shadowBlur), size: CGSize(width: 16.0 + shadowBlur * 2.0, height: 16.0 + shadowBlur * 2.0))
self.bubbleNodes[1].0.frame = mainBubbleFrame
self.bubbleNodes[1].1.frame = mainBubbleFrame
let secondaryBubbleFrame = CGRect(origin: CGPoint(x: mainBubbleFrame.midX - 9.0 - (8.0 + shadowBlur * 2.0) / 2.0, y: mainBubbleFrame.midY + 12.0 - (8.0 + shadowBlur * 2.0) / 2.0), size: CGSize(width: 8.0 + shadowBlur * 2.0, height: 8.0 + shadowBlur * 2.0))
self.bubbleNodes[0].0.frame = secondaryBubbleFrame
self.bubbleNodes[0].1.frame = secondaryBubbleFrame
}
func animateIn() {
self.bubbleNodes[1].0.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
self.bubbleNodes[1].1.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
self.bubbleNodes[0].0.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.05, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
self.bubbleNodes[0].1.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.05, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
let backgroundOffset = CGPoint(x: (self.backgroundNode.frame.width - shadowBlur) / 2.0 - 42.0, y: (self.backgroundNode.frame.height - shadowBlur) / 2.0)
let damping: CGFloat = 100.0
for i in 0 ..< self.reactionNodes.count {
let animationOffset: Double = 1.0 - Double(i) / Double(self.reactionNodes.count - 1)
let nodeOffset = CGPoint(x: self.reactionNodes[i].frame.minX - (self.backgroundNode.frame.maxX - shadowBlur) / 2.0 - 42.0, y: self.reactionNodes[i].frame.minY - self.backgroundNode.frame.maxY - shadowBlur)
self.reactionNodes[i].layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5 + animationOffset * 0.05, initialVelocity: 0.0, damping: damping)
self.reactionNodes[i].layer.animateSpring(from: NSValue(cgPoint: nodeOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true)
}
self.backgroundNode.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, damping: damping)
self.backgroundNode.layer.animateSpring(from: NSValue(cgPoint: backgroundOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true)
self.backgroundShadowNode.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, damping: damping)
self.backgroundShadowNode.layer.animateSpring(from: NSValue(cgPoint: backgroundOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true)
}
func animateOut(completion: @escaping () -> Void) {
completion()
}
}

View File

@ -0,0 +1,74 @@
import Foundation
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
public final class ReactionSelectionParentNode: ASDisplayNode {
private let account: Account
private var currentNode: ReactionSelectionNode?
private var currentLocation: (CGPoint, CGFloat)?
private var validLayout: (size: CGSize, insets: UIEdgeInsets)?
public init(account: Account) {
self.account = account
super.init()
}
func displayReactions(_ reactions: [ReactionGestureItem], at point: CGPoint) {
if let currentNode = self.currentNode {
currentNode.removeFromSupernode()
self.currentNode = nil
}
let reactionNode = ReactionSelectionNode(account: self.account, reactions: reactions)
self.addSubnode(reactionNode)
self.currentNode = reactionNode
self.currentLocation = (point, point.x)
if let (size, insets) = self.validLayout {
self.update(size: size, insets: insets, isInitial: true)
reactionNode.animateIn()
}
}
func dismissReactions() {
if let currentNode = self.currentNode {
currentNode.animateOut(completion: { [weak currentNode] in
currentNode?.removeFromSupernode()
})
self.currentNode = nil
}
}
func updateReactionsAnchor(point: CGPoint) {
if let (currentPoint, _) = self.currentLocation {
self.currentLocation = (currentPoint, point.x)
if let (size, insets) = self.validLayout {
self.update(size: size, insets: insets, isInitial: false)
}
}
}
public func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, insets)
self.update(size: size, insets: insets, isInitial: false)
}
private func update(size: CGSize, insets: UIEdgeInsets, isInitial: Bool) {
if let currentNode = self.currentNode, let (point, offset) = currentLocation {
currentNode.updateLayout(constrainedSize: size, startingPoint: point, offsetFromStart: offset, isInitial: isInitial)
currentNode.frame = CGRect(origin: CGPoint(), size: size)
}
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return nil
}
}

View File

@ -0,0 +1,103 @@
import Foundation
import UIKit
import Display
public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
private var validatedGesture = false
private var firstLocation: CGPoint = CGPoint()
private var currentReactions: [ReactionGestureItem] = []
private var isActivated = false
private weak var currentContainer: ReactionSelectionParentNode?
public var availableReactions: (() -> [ReactionGestureItem])?
public var getReactionContainer: (() -> ReactionSelectionParentNode?)?
public var updateOffset: ((CGFloat, Bool) -> Void)?
public var completed: (() -> Void)?
override public init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
self.maximumNumberOfTouches = 1
}
override public func reset() {
super.reset()
self.validatedGesture = false
self.currentReactions = []
self.isActivated = false
}
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
if let availableReactions = self.availableReactions?(), !availableReactions.isEmpty {
self.currentReactions = availableReactions
let touch = touches.first!
self.firstLocation = touch.location(in: nil)
} else {
self.state = .failed
}
}
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
guard let _ = self.view else {
return
}
guard let location = touches.first?.location(in: nil) else {
return
}
let translation = CGPoint(x: location.x - self.firstLocation.x, y: location.y - self.firstLocation.y)
let absTranslationX: CGFloat = abs(translation.x)
let absTranslationY: CGFloat = abs(translation.y)
var updatedOffset = false
if !self.validatedGesture {
if translation.x > 0.0 {
self.state = .failed
} else if absTranslationY > 2.0 && absTranslationY > absTranslationX * 2.0 {
self.state = .failed
} else if absTranslationX > 2.0 && absTranslationY * 2.0 < absTranslationX {
self.validatedGesture = true
self.updateOffset?(translation.x, true)
updatedOffset = true
}
}
if self.validatedGesture {
if !updatedOffset {
self.updateOffset?(-min(0.0, translation.x), false)
}
if !self.isActivated {
if absTranslationX > 40.0 {
self.isActivated = true
if !self.currentReactions.isEmpty, let reactionContainer = self.getReactionContainer?() {
self.currentContainer = reactionContainer
let reactionContainerLocation = reactionContainer.view.convert(location, from: nil)
reactionContainer.displayReactions(self.currentReactions, at: reactionContainerLocation)
}
}
} else {
if let reactionContainer = self.currentContainer {
let reactionContainerLocation = reactionContainer.view.convert(location, from: nil)
reactionContainer.updateReactionsAnchor(point: reactionContainerLocation)
}
}
super.touchesMoved(touches, with: event)
}
}
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
if self.validatedGesture {
self.completed?()
}
self.currentContainer?.dismissReactions()
self.state = .ended
super.touchesEnded(touches, with: event)
}
}

View File

@ -1017,6 +1017,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return self?.navigationController as? NavigationController
}, chatControllerNode: { [weak self] in
return self?.chatDisplayNode
}, reactionContainerNode: { [weak self] in
return self?.chatDisplayNode.reactionContainerNode
}, presentGlobalOverlayController: { [weak self] controller, arguments in
self?.presentInGlobalOverlay(controller, with: arguments)
}, callPeer: { [weak self] peerId in

View File

@ -7,6 +7,7 @@ import Display
import TelegramUIPreferences
import AccountContext
import TextSelectionNode
import ReactionSelectionNode
struct ChatInterfaceHighlightedState: Equatable {
let messageStableId: UInt32
@ -73,6 +74,7 @@ public final class ChatControllerInteraction {
let presentController: (ViewController, Any?) -> Void
let navigationController: () -> NavigationController?
let chatControllerNode: () -> ASDisplayNode?
let reactionContainerNode: () -> ReactionSelectionParentNode?
let presentGlobalOverlayController: (ViewController, Any?) -> Void
let callPeer: (PeerId) -> Void
let longTap: (ChatControllerInteractionLongTapAction, Message?) -> Void
@ -106,7 +108,7 @@ public final class ChatControllerInteraction {
var searchTextHighightState: String?
var seenOneTimeAnimatedMedia = Set<MessageId>()
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
self.openMessage = openMessage
self.openPeer = openPeer
self.openPeerMention = openPeerMention
@ -134,6 +136,7 @@ public final class ChatControllerInteraction {
self.presentController = presentController
self.navigationController = navigationController
self.chatControllerNode = chatControllerNode
self.reactionContainerNode = reactionContainerNode
self.presentGlobalOverlayController = presentGlobalOverlayController
self.callPeer = callPeer
self.longTap = longTap
@ -170,6 +173,8 @@ public final class ChatControllerInteraction {
return nil
}, chatControllerNode: {
return nil
}, reactionContainerNode: {
return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
}, canSetupReply: { _ in
return false

View File

@ -10,6 +10,7 @@ import TelegramUIPreferences
import TextFormat
import AccountContext
import TelegramNotices
import ReactionSelectionNode
private final class ChatControllerNodeView: UITracingLayerView, WindowInputAccessoryHeightProvider, PreviewingHostView {
var inputAccessoryHeight: (() -> CGFloat)?
@ -76,6 +77,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
let backgroundNode: WallpaperbackgroundNode
let historyNode: ChatHistoryListNode
let reactionContainerNode: ReactionSelectionParentNode
let historyNodeContainer: ASDisplayNode
let loadingNode: ChatLoadingNode
private var emptyNode: ChatEmptyNode?
@ -209,6 +211,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.historyNode.rotated = true
self.historyNodeContainer = ASDisplayNode()
self.historyNodeContainer.addSubnode(self.historyNode)
self.reactionContainerNode = ReactionSelectionParentNode(account: context.account)
self.historyNodeContainer.addSubnode(self.reactionContainerNode)
self.loadingNode = ChatLoadingNode(theme: self.chatPresentationInterfaceState.theme, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper)
self.inputPanelBackgroundNode = ASDisplayNode()
@ -839,6 +845,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
transition.updateFrame(node: emptyNode, frame: contentBounds)
}
transition.updateFrame(node: self.reactionContainerNode, frame: contentBounds)
self.reactionContainerNode.updateLayout(size: contentBounds.size, insets: UIEdgeInsets(), transition: transition)
var contentBottomInset: CGFloat = inputPanelsHeight + 4.0
if let scrollContainerNode = self.scrollContainerNode {

View File

@ -16,6 +16,7 @@ import MosaicLayout
import TextSelectionNode
import PlatformRestrictionMatching
import Emoji
import ReactionSelectionNode
private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(Message, AnyClass)] {
var result: [(Message, AnyClass)] = []
@ -365,7 +366,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
self.tapRecognizer = recognizer
self.view.addGestureRecognizer(recognizer)
let replyRecognizer = ChatSwipeToReplyRecognizer(target: self, action: #selector(self.swipeToReplyGesture(_:)))
/*let replyRecognizer = ChatSwipeToReplyRecognizer(target: self, action: #selector(self.swipeToReplyGesture(_:)))
replyRecognizer.shouldBegin = { [weak self] in
if let strongSelf = self, let item = strongSelf.item {
if strongSelf.selectionNode != nil {
@ -387,7 +388,74 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
}
return false
}
self.view.addGestureRecognizer(replyRecognizer)
self.view.addGestureRecognizer(replyRecognizer)*/
let reactionRecognizer = ReactionSwipeGestureRecognizer(target: nil, action: nil)
reactionRecognizer.availableReactions = { [weak self] in
guard let strongSelf = self, let item = strongSelf.item else {
return []
}
if strongSelf.selectionNode != nil {
return []
}
for media in item.content.firstMessage.media {
if let _ = media as? TelegramMediaExpiredContent {
return []
}
else if let media = media as? TelegramMediaAction {
if case .phoneCall = media.action {
} else {
return []
}
}
}
if !item.controllerInteraction.canSetupReply(item.message) {
return []
}
let reactions: [(String, String)] = [
("😒", "Sad"),
("😳", "Surprised"),
//("🥳", "Fun"),
("👍", "Like"),
("", "Love"),
]
var result: [ReactionGestureItem] = []
for (value, text) in reactions {
if let file = item.associatedData.animatedEmojiStickers[value]?.file {
result.append(ReactionGestureItem(value: ReactionGestureItemValue(value: value, text: text, file: file)))
}
}
return result
}
reactionRecognizer.getReactionContainer = { [weak self] in
return self?.item?.controllerInteraction.reactionContainerNode()
}
reactionRecognizer.updateOffset = { [weak self] offset, animated in
guard let strongSelf = self else {
return
}
var bounds = strongSelf.bounds
bounds.origin.x = offset
strongSelf.bounds = bounds
if animated {
strongSelf.layer.animateBoundsOriginXAdditive(from: -offset, to: 0.0, duration: 0.1, mediaTimingFunction: CAMediaTimingFunction(name: .easeOut))
}
}
reactionRecognizer.completed = { [weak self] in
guard let strongSelf = self else {
return
}
var bounds = strongSelf.bounds
let offset = bounds.origin.x
bounds.origin.x = 0.0
strongSelf.bounds = bounds
if !offset.isZero {
strongSelf.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
}
self.view.addGestureRecognizer(reactionRecognizer)
}
override func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, Bool) -> Void) {
@ -2504,7 +2572,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
}
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
switch recognizer.state {
/*switch recognizer.state {
case .began:
self.currentSwipeToReplyTranslation = 0.0
if self.swipeToReplyFeedback == nil {
@ -2563,7 +2631,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
}
default:
break
}
}*/
}
private var absoluteRect: (CGRect, CGSize)?

View File

@ -236,6 +236,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
return self?.getNavigationController()
}, chatControllerNode: { [weak self] in
return self
}, reactionContainerNode: {
return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { [weak self] action, message in
if let strongSelf = self {
switch action {

View File

@ -90,6 +90,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
return nil
}, chatControllerNode: {
return nil
}, reactionContainerNode: {
return nil
}, presentGlobalOverlayController: { _, _ in
}, callPeer: { _ in
}, longTap: { _, _ in

View File

@ -219,6 +219,8 @@ public class PeerMediaCollectionController: TelegramBaseController {
return nil
}, chatControllerNode: {
return nil
}, reactionContainerNode: {
return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in
}, longTap: { [weak self] content, _ in
if let strongSelf = self {

View File

@ -195,6 +195,7 @@
D03E449E2305B6A00049C28B /* WatchBridge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03E449D2305B6A00049C28B /* WatchBridge.framework */; };
D03E44E22305BC900049C28B /* LegacyDataImport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03E44E12305BC900049C28B /* LegacyDataImport.framework */; };
D03E45252305C07A0049C28B /* ShareItems.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03E45242305C07A0049C28B /* ShareItems.framework */; };
D03E46102305FD360049C28B /* ReactionSelectionNode.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03E460F2305FD360049C28B /* ReactionSelectionNode.framework */; };
D04203152037162700490EA5 /* MediaInputPaneTrendingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04203142037162700490EA5 /* MediaInputPaneTrendingItem.swift */; };
D04281F4200E5AB0009DDE36 /* ChatRecentActionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04281F3200E5AB0009DDE36 /* ChatRecentActionsController.swift */; };
D04281F6200E5AC2009DDE36 /* ChatRecentActionsControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04281F5200E5AC2009DDE36 /* ChatRecentActionsControllerNode.swift */; };
@ -915,6 +916,7 @@
D03E449D2305B6A00049C28B /* WatchBridge.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WatchBridge.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D03E44E12305BC900049C28B /* LegacyDataImport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LegacyDataImport.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D03E45242305C07A0049C28B /* ShareItems.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ShareItems.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D03E460F2305FD360049C28B /* ReactionSelectionNode.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ReactionSelectionNode.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D03E5E081E55C49C0029569A /* DebugAccountsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugAccountsController.swift; sourceTree = "<group>"; };
D04203142037162700490EA5 /* MediaInputPaneTrendingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInputPaneTrendingItem.swift; sourceTree = "<group>"; };
D04281F3200E5AB0009DDE36 /* ChatRecentActionsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRecentActionsController.swift; sourceTree = "<group>"; };
@ -1377,6 +1379,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D03E46102305FD360049C28B /* ReactionSelectionNode.framework in Frameworks */,
D03E45252305C07A0049C28B /* ShareItems.framework in Frameworks */,
D03E44E22305BC900049C28B /* LegacyDataImport.framework in Frameworks */,
D03E449E2305B6A00049C28B /* WatchBridge.framework in Frameworks */,
@ -2085,6 +2088,7 @@
D08D45281D5E340200A7428A /* Frameworks */ = {
isa = PBXGroup;
children = (
D03E460F2305FD360049C28B /* ReactionSelectionNode.framework */,
D03E45242305C07A0049C28B /* ShareItems.framework */,
D03E44E12305BC900049C28B /* LegacyDataImport.framework */,
D03E449D2305B6A00049C28B /* WatchBridge.framework */,