diff --git a/Telegram-iOS.xcworkspace/contents.xcworkspacedata b/Telegram-iOS.xcworkspace/contents.xcworkspacedata
index b2dc8b259e..f8cbbb7c7c 100644
--- a/Telegram-iOS.xcworkspace/contents.xcworkspacedata
+++ b/Telegram-iOS.xcworkspace/contents.xcworkspacedata
@@ -372,6 +372,13 @@
location = "group:submodules/AvatarNode/AvatarNode_Xcode.xcodeproj">
+
+
+
+
diff --git a/submodules/AnimationUI/Sources/AnimatedStickerNode.swift b/submodules/AnimationUI/Sources/AnimatedStickerNode.swift
index 460ca98e2c..e6a1df697b 100644
--- a/submodules/AnimationUI/Sources/AnimatedStickerNode.swift
+++ b/submodules/AnimationUI/Sources/AnimatedStickerNode.swift
@@ -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()
@@ -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() {
diff --git a/submodules/Display/Display/ContainedViewLayoutTransition.swift b/submodules/Display/Display/ContainedViewLayoutTransition.swift
index 20c2978053..ec438b5a21 100644
--- a/submodules/Display/Display/ContainedViewLayoutTransition.swift
+++ b/submodules/Display/Display/ContainedViewLayoutTransition.swift
@@ -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)
diff --git a/submodules/LegacyComponents/LegacyComponents_Xcode.xcodeproj/project.pbxproj b/submodules/LegacyComponents/LegacyComponents_Xcode.xcodeproj/project.pbxproj
index 28f52bcca5..2b1ca8e737 100644
--- a/submodules/LegacyComponents/LegacyComponents_Xcode.xcodeproj/project.pbxproj
+++ b/submodules/LegacyComponents/LegacyComponents_Xcode.xcodeproj/project.pbxproj
@@ -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";
diff --git a/submodules/ReactionSelectionNode/Info.plist b/submodules/ReactionSelectionNode/Info.plist
new file mode 100644
index 0000000000..e1fe4cfb7b
--- /dev/null
+++ b/submodules/ReactionSelectionNode/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+
+
diff --git a/submodules/ReactionSelectionNode/ReactionSelectionNode_Xcode.xcodeproj/project.pbxproj b/submodules/ReactionSelectionNode/ReactionSelectionNode_Xcode.xcodeproj/project.pbxproj
new file mode 100644
index 0000000000..e16fea7138
--- /dev/null
+++ b/submodules/ReactionSelectionNode/ReactionSelectionNode_Xcode.xcodeproj/project.pbxproj
@@ -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 = ""; };
+ D03E45E92305E3F30049C28B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ D03E45F42305EB7C0049C28B /* ReactionSelectionNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionSelectionNode.swift; sourceTree = ""; };
+ 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 = ""; };
+ D03E46092305EF900049C28B /* ReactionSwipeGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionSwipeGestureRecognizer.swift; sourceTree = ""; };
+ D03E460B2305EFD80049C28B /* ReactionGestureItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionGestureItem.swift; sourceTree = ""; };
+/* 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 = "";
+ };
+ D03E45E62305E3F30049C28B /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ D03E45E52305E3F30049C28B /* ReactionSelectionNode.framework */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ D03E45E72305E3F30049C28B /* Sources */ = {
+ isa = PBXGroup;
+ children = (
+ D03E45F42305EB7C0049C28B /* ReactionSelectionNode.swift */,
+ D03E45E82305E3F30049C28B /* ReactionSelectionNode.h */,
+ D03E46072305EEDD0049C28B /* ReactionSelectionParentNode.swift */,
+ D03E46092305EF900049C28B /* ReactionSwipeGestureRecognizer.swift */,
+ D03E460B2305EFD80049C28B /* ReactionGestureItem.swift */,
+ );
+ path = Sources;
+ sourceTree = "";
+ };
+ 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 = "";
+ };
+/* 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 */;
+}
diff --git a/submodules/ReactionSelectionNode/Sources/ReactionGestureItem.swift b/submodules/ReactionSelectionNode/Sources/ReactionGestureItem.swift
new file mode 100644
index 0000000000..a9ec62c03f
--- /dev/null
+++ b/submodules/ReactionSelectionNode/Sources/ReactionGestureItem.swift
@@ -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
+ }
+}
diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.h b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.h
new file mode 100644
index 0000000000..69ee588b7d
--- /dev/null
+++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.h
@@ -0,0 +1,19 @@
+//
+// ReactionSelectionNode.h
+// ReactionSelectionNode
+//
+// Created by Peter on 8/15/19.
+// Copyright © 2019 Telegram Messenger LLP. All rights reserved.
+//
+
+#import
+
+//! 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
+
+
diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift
new file mode 100644
index 0000000000..e46aa7ca51
--- /dev/null
+++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift
@@ -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()
+ }
+}
diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionParentNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionParentNode.swift
new file mode 100644
index 0000000000..547cdee6c2
--- /dev/null
+++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionParentNode.swift
@@ -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
+ }
+}
diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSwipeGestureRecognizer.swift b/submodules/ReactionSelectionNode/Sources/ReactionSwipeGestureRecognizer.swift
new file mode 100644
index 0000000000..23e40513ff
--- /dev/null
+++ b/submodules/ReactionSelectionNode/Sources/ReactionSwipeGestureRecognizer.swift
@@ -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, 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, 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, with event: UIEvent) {
+ if self.validatedGesture {
+ self.completed?()
+ }
+ self.currentContainer?.dismissReactions()
+ self.state = .ended
+
+ super.touchesEnded(touches, with: event)
+ }
+}
diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift
index 6fcfac0e9c..b6386235a7 100644
--- a/submodules/TelegramUI/TelegramUI/ChatController.swift
+++ b/submodules/TelegramUI/TelegramUI/ChatController.swift
@@ -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
diff --git a/submodules/TelegramUI/TelegramUI/ChatControllerInteraction.swift b/submodules/TelegramUI/TelegramUI/ChatControllerInteraction.swift
index 7a6a586e2d..3f20eaa0fc 100644
--- a/submodules/TelegramUI/TelegramUI/ChatControllerInteraction.swift
+++ b/submodules/TelegramUI/TelegramUI/ChatControllerInteraction.swift
@@ -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()
- 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
diff --git a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift
index 13aad4715b..67793a7438 100644
--- a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift
+++ b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift
@@ -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 {
diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift
index ed99325865..9d9ae21399 100644
--- a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift
+++ b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift
@@ -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)?
diff --git a/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift
index f8436d5c2e..059c5cc92b 100644
--- a/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift
+++ b/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift
@@ -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 {
diff --git a/submodules/TelegramUI/TelegramUI/OverlayPlayerControllerNode.swift b/submodules/TelegramUI/TelegramUI/OverlayPlayerControllerNode.swift
index 249ad1b565..15bc05eb88 100644
--- a/submodules/TelegramUI/TelegramUI/OverlayPlayerControllerNode.swift
+++ b/submodules/TelegramUI/TelegramUI/OverlayPlayerControllerNode.swift
@@ -90,6 +90,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
return nil
}, chatControllerNode: {
return nil
+ }, reactionContainerNode: {
+ return nil
}, presentGlobalOverlayController: { _, _ in
}, callPeer: { _ in
}, longTap: { _, _ in
diff --git a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift
index ae332ef3f2..04d64427e7 100644
--- a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift
+++ b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift
@@ -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 {
diff --git a/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj b/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj
index 2ee0f07eba..2a6ac4b0a8 100644
--- a/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj
+++ b/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj
@@ -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 = ""; };
D04203142037162700490EA5 /* MediaInputPaneTrendingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInputPaneTrendingItem.swift; sourceTree = ""; };
D04281F3200E5AB0009DDE36 /* ChatRecentActionsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRecentActionsController.swift; sourceTree = ""; };
@@ -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 */,