diff --git a/Telegram-iOS/en.lproj/Localizable.strings b/Telegram-iOS/en.lproj/Localizable.strings index 0c58910708..07a3a4fcdf 100644 --- a/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram-iOS/en.lproj/Localizable.strings @@ -217,6 +217,8 @@ "PUSH_CHAT_MESSAGE_GAME_SCORE" = "%1$@ scored %4$@ in game %3$@ in the group %2$@"; "PUSH_CHAT_MESSAGE_VIDEOS" = "%1$@ sent %3$@ videos to the group %2$@"; +"PUSH_REMINDER_TITLE" = "🗓 Reminder"; + "LOCAL_MESSAGE_FWDS" = "%1$@ forwarded you %2$d messages"; "LOCAL_CHANNEL_MESSAGE_FWDS" = "%1$@ posted %2$d forwarded messages"; "LOCAL_CHAT_MESSAGE_FWDS" = "%1$@ forwarded %2$d messages"; @@ -4529,7 +4531,7 @@ Any member of this group will be able to see messages in the channel."; "VoiceOver.Chat.RecordModeVoiceMessage" = "Voice message"; "VoiceOver.Chat.RecordModeVoiceMessageInfo" = "Double tap and hold to record voice message. Slide up to pin recording, slide left to cancel. Double tap to switch to video."; "VoiceOver.Chat.RecordModeVideoMessage" = "Video message"; -"VoiceOver.Chat.RecordModeVideoMessageInfo" = "Double tap and hold to record voice message. Slide up to pin recording, slide left to cancel. Double tap to switch to audio."; +"VoiceOver.Chat.RecordModeVideoMessageInfo" = "Double tap and hold to record video message. Slide up to pin recording, slide left to cancel. Double tap to switch to audio."; "VoiceOver.Chat.Message" = "Message"; "VoiceOver.Chat.YourMessage" = "Your message"; "VoiceOver.Chat.ReplyFrom" = "Reply to message from: %@"; diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 6c2b964ccf..4625c2dae1 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -23,6 +23,7 @@ public enum ContextMenuActionItemTextColor { public enum ContextMenuActionResult { case `default` case dismissWithoutContent + case custom(ContainedViewLayoutTransition) } public final class ContextMenuActionItem { @@ -338,7 +339,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi if let _ = self.propertyAnimator { if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { - self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.25 * animationDurationFactor, from: 0.0, to: 1.0, update: { [weak self] value in + self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2 * animationDurationFactor, from: 0.0, to: 1.0, update: { [weak self] value in (self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value }, completion: { [weak self] in self?.didCompleteAnimationIn = true @@ -372,9 +373,27 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } func animateOut(result: ContextMenuActionResult, completion: @escaping () -> Void) { + var transitionDuration: Double = 0.2 + var transitionCurve: ContainedViewLayoutTransitionCurve = .easeInOut + + switch result { + case let .custom(value): + switch value { + case let .animated(duration, curve): + transitionDuration = duration + transitionCurve = curve + default: + break + } + default: + break + } + self.isUserInteractionEnabled = false self.isAnimatingOut = true + self.scrollNode.view.setContentOffset(self.scrollNode.view.contentOffset, animated: false) + var completedEffect = false var completedContentNode = false var completedActionsNode = false @@ -384,8 +403,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi if let putBackInfo = putBackInfo, let contentParentNode = self.contentParentNode, let parentSupernode = contentParentNode.supernode { self.originalProjectedContentViewFrame = (parentSupernode.view.convert(contentParentNode.frame, to: self.view), contentParentNode.view.convert(contentParentNode.contentRect, to: self.view)) - self.clippingNode.layer.animateFrame(from: self.clippingNode.frame, to: putBackInfo.contentAreaInScreenSpace, duration: 0.2 * animationDurationFactor, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false) - self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: putBackInfo.contentAreaInScreenSpace.minY, duration: 0.2 * animationDurationFactor, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false) + self.clippingNode.layer.animateFrame(from: self.clippingNode.frame, to: putBackInfo.contentAreaInScreenSpace, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false) + self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: putBackInfo.contentAreaInScreenSpace.minY, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false) } let contentParentNode = self.contentParentNode @@ -395,7 +414,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi let intermediateCompletion: () -> Void = { [weak contentParentNode] in if completedEffect && completedContentNode && completedActionsNode { switch result { - case .default: + case .default, .custom: if let contentParentNode = contentParentNode { contentParentNode.addSubnode(contentParentNode.contentNode) contentParentNode.isExtractedToContextPreview = false @@ -414,7 +433,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator propertyAnimator?.stopAnimation(true) } - self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2, curve: .easeInOut, animations: { [weak self] in + self.propertyAnimator = UIViewPropertyAnimator(duration: transitionDuration, curve: .easeInOut, animations: { [weak self] in self?.effectView.effect = nil }) } @@ -442,22 +461,31 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi }) } - self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2 * animationDurationFactor, removeOnCompletion: false) + self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false) self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2 * animationDurationFactor, removeOnCompletion: false, completion: { _ in completedActionsNode = true intermediateCompletion() }) self.actionsContainerNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2 * animationDurationFactor, removeOnCompletion: false) - if case .default = result, let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame, let contentParentNode = self.contentParentNode { + + let animateOutToItem: Bool + switch result { + case .default, .custom: + animateOutToItem = true + case .dismissWithoutContent: + animateOutToItem = false + } + + if animateOutToItem, let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame, let contentParentNode = self.contentParentNode { let localSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.scrollNode.view) - self.actionsContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y), duration: 0.2 * animationDurationFactor, removeOnCompletion: false, additive: true) + self.actionsContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y), duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true) let contentContainerOffset = CGPoint(x: localSourceFrame.center.x - self.contentContainerNode.frame.center.x - contentParentNode.contentRect.minX, y: localSourceFrame.center.y - self.contentContainerNode.frame.center.y - contentParentNode.contentRect.minY) - self.contentContainerNode.layer.animatePosition(from: CGPoint(), to: contentContainerOffset, duration: 0.2 * animationDurationFactor, removeOnCompletion: false, additive: true, completion: { _ in + self.contentContainerNode.layer.animatePosition(from: CGPoint(), to: contentContainerOffset, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true, completion: { _ in completedContentNode = true intermediateCompletion() }) contentParentNode.updateAbsoluteRect?(self.contentContainerNode.frame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y + contentContainerOffset.y), self.bounds.size) - contentParentNode.applyAbsoluteOffset?(-contentContainerOffset.y, .easeInOut, 0.2) + contentParentNode.applyAbsoluteOffset?(-contentContainerOffset.y, transitionCurve, transitionDuration) if let reactionContextNode = self.reactionContextNode { reactionContextNode.animateOut(to: CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: contentParentNode.contentRect.size), animatingOutToReaction: self.reactionContextNodeIsAnimatingOut) @@ -471,10 +499,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi contentParentNode.isExtractedToContextPreview = false contentParentNode.isExtractedToContextPreviewUpdated?(false) - self.contentContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2 * animationDurationFactor, removeOnCompletion: false, completion: { _ in + self.contentContainerNode.allowsGroupOpacity = true + self.contentContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false, completion: { _ in completedContentNode = true intermediateCompletion() }) + //self.contentContainerNode.layer.animateScale(from: 1.0, to: 0.1, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false) if let reactionContextNode = self.reactionContextNode { reactionContextNode.animateOut(to: nil, animatingOutToReaction: self.reactionContextNodeIsAnimatingOut) @@ -535,6 +565,10 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition, previousActionsContainerNode: ContextActionsContainerNode?) { + if self.isAnimatingOut { + return + } + self.validLayout = layout var actionsContainerTransition = transition diff --git a/submodules/Display/Display/CAAnimationUtils.swift b/submodules/Display/Display/CAAnimationUtils.swift index 81cf94009a..a1e588563b 100644 --- a/submodules/Display/Display/CAAnimationUtils.swift +++ b/submodules/Display/Display/CAAnimationUtils.swift @@ -160,7 +160,7 @@ public extension CALayer { self.add(animation, forKey: keyPath) } - public func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, initialVelocity: CGFloat = 0.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { + public func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, delay: Double = 0.0, initialVelocity: CGFloat = 0.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { let animation: CABasicAnimation if #available(iOS 9.0, *) { animation = makeSpringBounceAnimation(keyPath, initialVelocity, damping) @@ -181,6 +181,11 @@ public extension CALayer { speed = Float(1.0) / k } + if !delay.isZero { + animation.beginTime = CACurrentMediaTime() + delay + animation.fillMode = .both + } + animation.speed = speed * Float(animation.duration / duration) animation.isAdditive = additive diff --git a/submodules/Display/Display/ContextMenuActionNode.swift b/submodules/Display/Display/ContextMenuActionNode.swift index 64da141003..a26ed60144 100644 --- a/submodules/Display/Display/ContextMenuActionNode.swift +++ b/submodules/Display/Display/ContextMenuActionNode.swift @@ -54,7 +54,7 @@ final class ContextMenuActionNode: ASDisplayNode { super.init() - self.backgroundColor = UIColor(white: 0.0, alpha: 0.8) + self.backgroundColor = UIColor(rgb: 0x2f2f2f) if let textNode = self.textNode { self.addSubnode(textNode) } @@ -63,7 +63,7 @@ final class ContextMenuActionNode: ASDisplayNode { } self.button.highligthedChanged = { [weak self] highlighted in - self?.backgroundColor = highlighted ? UIColor(white: 0.0, alpha: 0.4) : UIColor(white: 0.0, alpha: 0.8) + self?.backgroundColor = highlighted ? UIColor(rgb: 0x8c8e8e) : UIColor(rgb: 0x2f2f2f) } self.view.addSubview(self.button) self.addSubnode(self.actionArea) diff --git a/submodules/Display/Display/ContextMenuContainerNode.swift b/submodules/Display/Display/ContextMenuContainerNode.swift index fe191a6f23..aea6ae2062 100644 --- a/submodules/Display/Display/ContextMenuContainerNode.swift +++ b/submodules/Display/Display/ContextMenuContainerNode.swift @@ -27,7 +27,7 @@ public final class ContextMenuContainerNode: ASDisplayNode { super.init() - self.backgroundColor = UIColor(rgb: 0xeaecec) + self.backgroundColor = UIColor(rgb: 0x8c8e8e) //self.view.addSubview(self.effectView) //self.effectView.mask = self.maskView self.view.mask = self.maskView diff --git a/submodules/Display/Display/ContextMenuNode.swift b/submodules/Display/Display/ContextMenuNode.swift index 7c3f9ffc4b..e1f37bb529 100644 --- a/submodules/Display/Display/ContextMenuNode.swift +++ b/submodules/Display/Display/ContextMenuNode.swift @@ -38,9 +38,9 @@ private final class ContextMenuContentScrollNode: ASDisplayNode { self.rightShadow.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0) self.leftOverscrollNode = ASDisplayNode() - self.leftOverscrollNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8) + //self.leftOverscrollNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8) self.rightOverscrollNode = ASDisplayNode() - self.rightOverscrollNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8) + //self.rightOverscrollNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8) super.init() @@ -55,8 +55,8 @@ private final class ContextMenuContentScrollNode: ASDisplayNode { override func didLoad() { super.didLoad() - let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) - self.view.addGestureRecognizer(panRecognizer) + //let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) + //self.view.addGestureRecognizer(panRecognizer) } @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { @@ -228,9 +228,12 @@ final class ContextMenuNode: ASDisplayNode { self.containerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: containerPosition.x, y: containerPosition.y + (self.arrowOnBottom ? 1.0 : -1.0) * self.containerNode.bounds.size.height / 2.0)), to: NSValue(cgPoint: containerPosition), keyPath: "position", duration: 0.4) } - self.containerNode.allowsGroupOpacity = true - self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak self] _ in - self?.containerNode.allowsGroupOpacity = false + self.allowsGroupOpacity = true + self.layer.rasterizationScale = UIScreen.main.scale + self.layer.shouldRasterize = true + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak self] _ in + self?.allowsGroupOpacity = false + self?.layer.shouldRasterize = false }) if let feedback = self.feedback { @@ -239,9 +242,12 @@ final class ContextMenuNode: ASDisplayNode { } func animateOut(bounce: Bool, completion: @escaping () -> Void) { - self.containerNode.allowsGroupOpacity = true - self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in - self?.containerNode.allowsGroupOpacity = false + self.allowsGroupOpacity = true + self.layer.rasterizationScale = UIScreen.main.scale + self.layer.shouldRasterize = true + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in + self?.allowsGroupOpacity = false + self?.layer.shouldRasterize = false completion() }) } diff --git a/submodules/MtProtoKit/MTAes.m b/submodules/MtProtoKit/MTAes.m index 0632586791..357d52932d 100644 --- a/submodules/MtProtoKit/MTAes.m +++ b/submodules/MtProtoKit/MTAes.m @@ -107,6 +107,9 @@ void MyAesIgeEncrypt(const void *inBytes, int length, void *outBytes, const void } void MyAesIgeDecrypt(const void *inBytes, int length, void *outBytes, const void *key, int keyLength, void *iv) { + assert(length % 16 == 0); + assert(length >= 0); + unsigned char aesIv[AES_BLOCK_SIZE]; memcpy(aesIv, iv, AES_BLOCK_SIZE); unsigned char ccIv[AES_BLOCK_SIZE]; diff --git a/submodules/MtProtoKit/MTProtoKit/MTTcpConnection.m b/submodules/MtProtoKit/MTProtoKit/MTTcpConnection.m index 677be9ee00..3dacdf6833 100644 --- a/submodules/MtProtoKit/MTProtoKit/MTTcpConnection.m +++ b/submodules/MtProtoKit/MTProtoKit/MTTcpConnection.m @@ -32,6 +32,99 @@ # import #endif +#import + +static BIGNUM *get_y2(BIGNUM *x, const BIGNUM *mod, BN_CTX *big_num_context) { + // returns y^2 = x^3 + 486662 * x^2 + x + BIGNUM *y = BN_dup(x); + assert(y != NULL); + BIGNUM *coef = BN_new(); + BN_set_word(coef, 486662); + BN_mod_add(y, y, coef, mod, big_num_context); + BN_mod_mul(y, y, x, mod, big_num_context); + BN_one(coef); + BN_mod_add(y, y, coef, mod, big_num_context); + BN_mod_mul(y, y, x, mod, big_num_context); + BN_clear_free(coef); + return y; +} + +static BIGNUM *get_double_x(BIGNUM *x, const BIGNUM *mod, BN_CTX *big_num_context) { + // returns x_2 =(x^2 - 1)^2/(4*y^2) + BIGNUM *denominator = get_y2(x, mod, big_num_context); + assert(denominator != NULL); + BIGNUM *coef = BN_new(); + BN_set_word(coef, 4); + BN_mod_mul(denominator, denominator, coef, mod, big_num_context); + + BIGNUM *numerator = BN_new(); + assert(numerator != NULL); + BN_mod_mul(numerator, x, x, mod, big_num_context); + BN_one(coef); + BN_mod_sub(numerator, numerator, coef, mod, big_num_context); + BN_mod_mul(numerator, numerator, numerator, mod, big_num_context); + + BN_mod_inverse(denominator, denominator, mod, big_num_context); + BN_mod_mul(numerator, numerator, denominator, mod, big_num_context); + + BN_clear_free(coef); + BN_clear_free(denominator); + return numerator; +} + +static void generate_public_key(unsigned char key[32]) { + BIGNUM *mod = NULL; + BN_hex2bn(&mod, "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed"); + BIGNUM *pow = NULL; + BN_hex2bn(&pow, "3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"); + BN_CTX *big_num_context = BN_CTX_new(); + assert(big_num_context != NULL); + + BIGNUM *x = BN_new(); + while (1) { + int randomResult = SecRandomCopyBytes(kSecRandomDefault, 32, key); + assert(randomResult == errSecSuccess); + + key[31] &= 127; + BN_bin2bn(key, 32, x); + assert(x != NULL); + BN_mod_mul(x, x, x, mod, big_num_context); + + BIGNUM *y = get_y2(x, mod, big_num_context); + + BIGNUM *r = BN_new(); + BN_mod_exp(r, y, pow, mod, big_num_context); + BN_clear_free(y); + if (BN_is_one(r)) { + BN_clear_free(r); + break; + } + BN_clear_free(r); + } + + int i; + for (i = 0; i < 3; i++) { + BIGNUM *x2 = get_double_x(x, mod, big_num_context); + BN_clear_free(x); + x = x2; + } + + int num_size = BN_num_bytes(x); + assert(num_size <= 32); + memset(key, '\0', 32 - num_size); + BN_bn2bin(x, key + (32 - num_size)); + for (i = 0; i < 16; i++) { + unsigned char t = key[i]; + key[i] = key[31 - i]; + key[31 - i] = t; + } + + BN_clear_free(x); + BN_CTX_free(big_num_context); + BN_clear_free(pow); + BN_clear_free(mod); +} + @interface MTTcpConnectionData : NSObject @property (nonatomic, strong, readonly) NSString *ip; @@ -506,8 +599,8 @@ struct ctr_state { [helloData appendBytes:s6 length:117]; uint8_t r2[32]; - result = SecRandomCopyBytes(nil, 32, r2); - assert(result == errSecSuccess); + generate_public_key(r2); + [helloData appendBytes:r2 length:32]; uint8_t s9[35] = { 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2b, 0x00, 0x09, 0x08, 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x15 }; diff --git a/submodules/TelegramCore/TelegramCore/ContactManagement.swift b/submodules/TelegramCore/TelegramCore/ContactManagement.swift index d590838a5b..ca6bdce912 100644 --- a/submodules/TelegramCore/TelegramCore/ContactManagement.swift +++ b/submodules/TelegramCore/TelegramCore/ContactManagement.swift @@ -81,8 +81,8 @@ func syncContactsOnce(network: Network, postbox: Postbox, accountPeerId: PeerId) if wasEmpty { var insertSignal: Signal = .complete() - for s in stride(from: 0, to: peers.count, by: 100) { - let partPeers = Array(peers[s ..< min(s + 100, peers.count)]) + for s in stride(from: 0, to: peers.count, by: 500) { + let partPeers = Array(peers[s ..< min(s + 500, peers.count)]) let partSignal = postbox.transaction { transaction -> Void in updatePeers(transaction: transaction, peers: partPeers, update: { return $1 }) var updatedIds = transaction.getContactPeerIds() diff --git a/submodules/TelegramCore/TelegramCore/ContactSyncManager.swift b/submodules/TelegramCore/TelegramCore/ContactSyncManager.swift index c6375e3b8f..325d1fd07e 100644 --- a/submodules/TelegramCore/TelegramCore/ContactSyncManager.swift +++ b/submodules/TelegramCore/TelegramCore/ContactSyncManager.swift @@ -308,7 +308,7 @@ private func pushDeviceContacts(postbox: Postbox, network: Network, importableCo |> switchToLatest } -private let importBatchCount: Int = 100 +private let importBatchCount: Int = 500 private func pushDeviceContactData(postbox: Postbox, network: Network, contacts: [(DeviceContactNormalizedPhoneNumber, ImportableDeviceContactData)]) -> Signal { var batches: Signal = .single(PushDeviceContactsResult(addedReimportAttempts: [:])) diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift index b70572f3a2..2a13eb2800 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift @@ -210,8 +210,8 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta ) let message = PresentationThemeChatMessage( - incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x262628), highlightedFill: UIColor(rgb: 0x353539), stroke: UIColor(rgb: 0x262628)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x262628), highlightedFill: UIColor(rgb: 0x353539), stroke: UIColor(rgb: 0x262628))), primaryTextColor: .white, secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5), linkTextColor: accentColor, linkHighlightColor: accentColor.withAlphaComponent(0.5), scamColor: destructiveColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: accentColor, accentControlColor: accentColor, mediaActiveControlColor: accentColor, mediaInactiveControlColor: accentColor.withAlphaComponent(0.4), pendingActivityColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileTitleColor: accentColor, fileDescriptionColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaPlaceholderColor: UIColor(rgb: 0x1f1f1f).mixedWith(.white, alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0x737373), radioProgress: accentColor, highlight: accentColor.withAlphaComponent(0.12), separator: UIColor(rgb: 0x000000), bar: accentColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff))), - outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleHighlightedFillColor, stroke: outgoingBubbleFillColor), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleHighlightedFillColor, stroke: outgoingBubbleFillColor)), primaryTextColor: outgoingPrimaryTextColor, secondaryTextColor: outgoingSecondaryTextColor, linkTextColor: outgoingLinkTextColor, linkHighlightColor: UIColor.white.withAlphaComponent(0.5), scamColor: outgoingScamColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: outgoingPrimaryTextColor, accentControlColor: outgoingPrimaryTextColor, mediaActiveControlColor: outgoingPrimaryTextColor, mediaInactiveControlColor: outgoingSecondaryTextColor, pendingActivityColor: outgoingSecondaryTextColor, fileTitleColor: outgoingPrimaryTextColor, fileDescriptionColor: outgoingSecondaryTextColor, fileDurationColor: outgoingSecondaryTextColor, mediaPlaceholderColor: UIColor(rgb: 0x313131).mixedWith(.white, alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: outgoingPrimaryTextColor, radioProgress: outgoingPrimaryTextColor, highlight: outgoingPrimaryTextColor.withAlphaComponent(0.12), separator: outgoingSecondaryTextColor, bar: outgoingPrimaryTextColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff))), + incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x262628), highlightedFill: UIColor(rgb: 0x353539), stroke: UIColor(rgb: 0x262628)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x262628), highlightedFill: UIColor(rgb: 0x353539), stroke: UIColor(rgb: 0x262628))), primaryTextColor: .white, secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5), linkTextColor: accentColor, linkHighlightColor: accentColor.withAlphaComponent(0.5), scamColor: destructiveColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: accentColor, accentControlColor: accentColor, mediaActiveControlColor: accentColor, mediaInactiveControlColor: accentColor.withAlphaComponent(0.4), pendingActivityColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileTitleColor: accentColor, fileDescriptionColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaPlaceholderColor: UIColor(rgb: 0x1f1f1f).mixedWith(.white, alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0x737373), radioProgress: accentColor, highlight: accentColor.withAlphaComponent(0.12), separator: UIColor(rgb: 0x000000), bar: accentColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff)), textSelectionColor: accentColor.withAlphaComponent(0.3), textSelectionKnobColor: accentColor), + outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleHighlightedFillColor, stroke: outgoingBubbleFillColor), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleHighlightedFillColor, stroke: outgoingBubbleFillColor)), primaryTextColor: outgoingPrimaryTextColor, secondaryTextColor: outgoingSecondaryTextColor, linkTextColor: outgoingLinkTextColor, linkHighlightColor: UIColor.white.withAlphaComponent(0.5), scamColor: outgoingScamColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: outgoingPrimaryTextColor, accentControlColor: outgoingPrimaryTextColor, mediaActiveControlColor: outgoingPrimaryTextColor, mediaInactiveControlColor: outgoingSecondaryTextColor, pendingActivityColor: outgoingSecondaryTextColor, fileTitleColor: outgoingPrimaryTextColor, fileDescriptionColor: outgoingSecondaryTextColor, fileDurationColor: outgoingSecondaryTextColor, mediaPlaceholderColor: UIColor(rgb: 0x313131).mixedWith(.white, alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: outgoingPrimaryTextColor, radioProgress: outgoingPrimaryTextColor, highlight: outgoingPrimaryTextColor.withAlphaComponent(0.12), separator: outgoingSecondaryTextColor, bar: outgoingPrimaryTextColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff)), textSelectionColor: accentColor.withAlphaComponent(0.3), textSelectionKnobColor: accentColor), freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2a2a2a), stroke: UIColor(rgb: 0x1f1f1f)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2a2a2a), stroke: UIColor(rgb: 0x1f1f1f))), infoPrimaryTextColor: .white, infoLinkTextColor: accentColor, diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift index ff7dd74cc6..34988cef6d 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift @@ -180,8 +180,8 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta let buttonStrokeColor = accentColor.withMultiplied(hue: 1.014, saturation: 0.56, brightness: 0.64).withAlphaComponent(0.15) let message = PresentationThemeChatMessage( - incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: mainBackgroundColor, highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: mainBackgroundColor, highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor)), primaryTextColor: .white, secondaryTextColor: mainSecondaryTextColor.withAlphaComponent(0.5), linkTextColor: accentColor, linkHighlightColor: accentColor.withAlphaComponent(0.5), scamColor: destructiveColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: accentColor, accentControlColor: accentColor, mediaActiveControlColor: accentColor, mediaInactiveControlColor: accentColor.withAlphaComponent(0.5), pendingActivityColor: mainSecondaryTextColor.withAlphaComponent(0.5), fileTitleColor: accentColor, fileDescriptionColor: mainSecondaryTextColor.withAlphaComponent(0.5), fileDurationColor: mainSecondaryTextColor.withAlphaComponent(0.5), mediaPlaceholderColor: accentColor.withMultiplied(hue: 1.019, saturation: 0.585, brightness: 0.23), polls: PresentationThemeChatBubblePolls(radioButton: accentColor.withMultiplied(hue: 0.995, saturation: 0.317, brightness: 0.51), radioProgress: accentColor, highlight: accentColor.withAlphaComponent(0.12), separator: mainSeparatorColor, bar: accentColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: additionalBackgroundColor.withAlphaComponent(0.5), withoutWallpaper: additionalBackgroundColor.withAlphaComponent(0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: buttonStrokeColor), actionButtonsTextColor: PresentationThemeVariableColor(color: .white)), - outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleColor, highlightedFill: highlightedOutgoingBubbleColor, stroke: outgoingBubbleColor), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleColor, highlightedFill: highlightedOutgoingBubbleColor, stroke: outgoingBubbleColor)), primaryTextColor: .white, secondaryTextColor: mainSecondaryTextColor.withAlphaComponent(0.5), linkTextColor: accentColor, linkHighlightColor: accentColor.withAlphaComponent(0.5), scamColor: destructiveColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: .white, accentControlColor: .white, mediaActiveControlColor: .white, mediaInactiveControlColor: UIColor(rgb: 0xffffff, alpha: 0.5), pendingActivityColor: mainSecondaryTextColor.withAlphaComponent(0.5), fileTitleColor: .white, fileDescriptionColor: mainSecondaryTextColor.withAlphaComponent(0.5), fileDurationColor: mainSecondaryTextColor.withAlphaComponent(0.5), mediaPlaceholderColor: accentColor.withMultiplied(hue: 1.019, saturation: 0.804, brightness: 0.51), polls: PresentationThemeChatBubblePolls(radioButton: .white, radioProgress: accentColor.withMultiplied(hue: 0.99, saturation: 0.56, brightness: 1.0), highlight: accentColor.withMultiplied(hue: 0.99, saturation: 0.56, brightness: 1.0).withAlphaComponent(0.12), separator: mainSeparatorColor, bar: .white), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: additionalBackgroundColor.withAlphaComponent(0.5), withoutWallpaper: additionalBackgroundColor.withAlphaComponent(0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: buttonStrokeColor), actionButtonsTextColor: PresentationThemeVariableColor(color: .white)), + incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: mainBackgroundColor, highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: mainBackgroundColor, highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor)), primaryTextColor: .white, secondaryTextColor: mainSecondaryTextColor.withAlphaComponent(0.5), linkTextColor: accentColor, linkHighlightColor: accentColor.withAlphaComponent(0.5), scamColor: destructiveColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: accentColor, accentControlColor: accentColor, mediaActiveControlColor: accentColor, mediaInactiveControlColor: accentColor.withAlphaComponent(0.5), pendingActivityColor: mainSecondaryTextColor.withAlphaComponent(0.5), fileTitleColor: accentColor, fileDescriptionColor: mainSecondaryTextColor.withAlphaComponent(0.5), fileDurationColor: mainSecondaryTextColor.withAlphaComponent(0.5), mediaPlaceholderColor: accentColor.withMultiplied(hue: 1.019, saturation: 0.585, brightness: 0.23), polls: PresentationThemeChatBubblePolls(radioButton: accentColor.withMultiplied(hue: 0.995, saturation: 0.317, brightness: 0.51), radioProgress: accentColor, highlight: accentColor.withAlphaComponent(0.12), separator: mainSeparatorColor, bar: accentColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: additionalBackgroundColor.withAlphaComponent(0.5), withoutWallpaper: additionalBackgroundColor.withAlphaComponent(0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: buttonStrokeColor), actionButtonsTextColor: PresentationThemeVariableColor(color: .white), textSelectionColor: accentColor.withAlphaComponent(0.3), textSelectionKnobColor: accentColor), + outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleColor, highlightedFill: highlightedOutgoingBubbleColor, stroke: outgoingBubbleColor), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleColor, highlightedFill: highlightedOutgoingBubbleColor, stroke: outgoingBubbleColor)), primaryTextColor: .white, secondaryTextColor: mainSecondaryTextColor.withAlphaComponent(0.5), linkTextColor: accentColor, linkHighlightColor: accentColor.withAlphaComponent(0.5), scamColor: destructiveColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: .white, accentControlColor: .white, mediaActiveControlColor: .white, mediaInactiveControlColor: UIColor(rgb: 0xffffff, alpha: 0.5), pendingActivityColor: mainSecondaryTextColor.withAlphaComponent(0.5), fileTitleColor: .white, fileDescriptionColor: mainSecondaryTextColor.withAlphaComponent(0.5), fileDurationColor: mainSecondaryTextColor.withAlphaComponent(0.5), mediaPlaceholderColor: accentColor.withMultiplied(hue: 1.019, saturation: 0.804, brightness: 0.51), polls: PresentationThemeChatBubblePolls(radioButton: .white, radioProgress: accentColor.withMultiplied(hue: 0.99, saturation: 0.56, brightness: 1.0), highlight: accentColor.withMultiplied(hue: 0.99, saturation: 0.56, brightness: 1.0).withAlphaComponent(0.12), separator: mainSeparatorColor, bar: .white), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: additionalBackgroundColor.withAlphaComponent(0.5), withoutWallpaper: additionalBackgroundColor.withAlphaComponent(0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: buttonStrokeColor), actionButtonsTextColor: PresentationThemeVariableColor(color: .white), textSelectionColor: accentColor.withAlphaComponent(0.3), textSelectionKnobColor: accentColor), freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: mainBackgroundColor, highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: mainBackgroundColor, highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor)), infoPrimaryTextColor: UIColor(rgb: 0xffffff), infoLinkTextColor: accentColor, diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index cda4734426..4811caa63a 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -190,8 +190,8 @@ private func makeDefaultDayPresentationTheme(accentColor: UIColor, serviceBackgr ) let message = PresentationThemeChatMessage( - incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: UIColor(rgb: 0x86A9C9, alpha: 0.5)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: UIColor(rgb: 0x86A9C9, alpha: 0.5))), primaryTextColor: .black, secondaryTextColor: UIColor(rgb: 0x525252, alpha: 0.6), linkTextColor: UIColor(rgb: 0x004bad), linkHighlightColor: accentColor.withAlphaComponent(0.3), scamColor: destructiveColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: UIColor(rgb: 0x007ee5), accentControlColor: UIColor(rgb: 0x007ee5), mediaActiveControlColor: UIColor(rgb: 0x007ee5), mediaInactiveControlColor: UIColor(rgb: 0xcacaca), pendingActivityColor: UIColor(rgb: 0x525252, alpha: 0.6), fileTitleColor: UIColor(rgb: 0x0b8bed), fileDescriptionColor: UIColor(rgb: 0x999999), fileDurationColor: UIColor(rgb: 0x525252, alpha: 0.6), mediaPlaceholderColor: UIColor(rgb: 0xe8ecf0), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0xc8c7cc), radioProgress: UIColor(rgb: 0x007ee5), highlight: UIColor(rgb: 0x007ee5).withAlphaComponent(0.08), separator: UIColor(rgb: 0xc8c7cc), bar: UIColor(rgb: 0x007ee5)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0x596e89, alpha: 0.35)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: .clear), actionButtonsTextColor: PresentationThemeVariableColor(color: .white)), - outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xE1FFC7), highlightedFill: UIColor(rgb: 0xc8ffa6), stroke: UIColor(rgb: 0x86a9c9, alpha: 0.5)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xe1ffc7), highlightedFill: UIColor(rgb: 0xc8ffa6), stroke: UIColor(rgb: 0x86a9c9, alpha: 0.5))), primaryTextColor: .black, secondaryTextColor: UIColor(rgb: 0x008c09, alpha: 0.8), linkTextColor: UIColor(rgb: 0x004bad), linkHighlightColor: accentColor.withAlphaComponent(0.3), scamColor: destructiveColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: UIColor(rgb: 0x00a700), accentControlColor: UIColor(rgb: 0x3fc33b), mediaActiveControlColor: UIColor(rgb: 0x3fc33b), mediaInactiveControlColor: UIColor(rgb: 0x93d987), pendingActivityColor: UIColor(rgb: 0x42b649), fileTitleColor: UIColor(rgb: 0x3faa3c), fileDescriptionColor: UIColor(rgb: 0x6fb26a), fileDurationColor: UIColor(rgb: 0x008c09, alpha: 0.8), mediaPlaceholderColor: UIColor(rgb: 0xd2f2b6), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0x93d987), radioProgress: UIColor(rgb: 0x3fc33b), highlight: UIColor(rgb: 0x3fc33b).withAlphaComponent(0.08), separator: UIColor(rgb: 0x93d987), bar: UIColor(rgb: 0x3fc33b)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0x596e89, alpha: 0.35)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: .clear), actionButtonsTextColor: PresentationThemeVariableColor(color: .white)), + incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: UIColor(rgb: 0x86A9C9, alpha: 0.5)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: UIColor(rgb: 0x86A9C9, alpha: 0.5))), primaryTextColor: .black, secondaryTextColor: UIColor(rgb: 0x525252, alpha: 0.6), linkTextColor: UIColor(rgb: 0x004bad), linkHighlightColor: accentColor.withAlphaComponent(0.3), scamColor: destructiveColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: UIColor(rgb: 0x007ee5), accentControlColor: UIColor(rgb: 0x007ee5), mediaActiveControlColor: UIColor(rgb: 0x007ee5), mediaInactiveControlColor: UIColor(rgb: 0xcacaca), pendingActivityColor: UIColor(rgb: 0x525252, alpha: 0.6), fileTitleColor: UIColor(rgb: 0x0b8bed), fileDescriptionColor: UIColor(rgb: 0x999999), fileDurationColor: UIColor(rgb: 0x525252, alpha: 0.6), mediaPlaceholderColor: UIColor(rgb: 0xe8ecf0), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0xc8c7cc), radioProgress: UIColor(rgb: 0x007ee5), highlight: UIColor(rgb: 0x007ee5).withAlphaComponent(0.08), separator: UIColor(rgb: 0xc8c7cc), bar: UIColor(rgb: 0x007ee5)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0x596e89, alpha: 0.35)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: .clear), actionButtonsTextColor: PresentationThemeVariableColor(color: .white), textSelectionColor: accentColor.withAlphaComponent(0.3), textSelectionKnobColor: accentColor), + outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xE1FFC7), highlightedFill: UIColor(rgb: 0xc8ffa6), stroke: UIColor(rgb: 0x86a9c9, alpha: 0.5)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xe1ffc7), highlightedFill: UIColor(rgb: 0xc8ffa6), stroke: UIColor(rgb: 0x86a9c9, alpha: 0.5))), primaryTextColor: .black, secondaryTextColor: UIColor(rgb: 0x008c09, alpha: 0.8), linkTextColor: UIColor(rgb: 0x004bad), linkHighlightColor: accentColor.withAlphaComponent(0.3), scamColor: destructiveColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: UIColor(rgb: 0x00a700), accentControlColor: UIColor(rgb: 0x3fc33b), mediaActiveControlColor: UIColor(rgb: 0x3fc33b), mediaInactiveControlColor: UIColor(rgb: 0x93d987), pendingActivityColor: UIColor(rgb: 0x42b649), fileTitleColor: UIColor(rgb: 0x3faa3c), fileDescriptionColor: UIColor(rgb: 0x6fb26a), fileDurationColor: UIColor(rgb: 0x008c09, alpha: 0.8), mediaPlaceholderColor: UIColor(rgb: 0xd2f2b6), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0x93d987), radioProgress: UIColor(rgb: 0x3fc33b), highlight: UIColor(rgb: 0x3fc33b).withAlphaComponent(0.08), separator: UIColor(rgb: 0x93d987), bar: UIColor(rgb: 0x3fc33b)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0x596e89, alpha: 0.35)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: .clear), actionButtonsTextColor: PresentationThemeVariableColor(color: .white), textSelectionColor: accentColor.withAlphaComponent(0.3), textSelectionKnobColor: accentColor), freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: UIColor(rgb: 0x86A9C9, alpha: 0.5)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: UIColor(rgb: 0x86A9C9, alpha: 0.5))), infoPrimaryTextColor: UIColor(rgb: 0x000000), infoLinkTextColor: UIColor(rgb: 0x004bad), @@ -208,8 +208,8 @@ private func makeDefaultDayPresentationTheme(accentColor: UIColor, serviceBackgr ) let messageDay = PresentationThemeChatMessage( - incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xffffff)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xf1f1f4), highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xf1f1f4))), primaryTextColor: .black, secondaryTextColor: UIColor(rgb: 0x525252, alpha: 0.6), linkTextColor: UIColor(rgb: 0x004bad), linkHighlightColor: accentColor.withAlphaComponent(0.3), scamColor: destructiveColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: accentColor, accentControlColor: accentColor, mediaActiveControlColor: accentColor, mediaInactiveControlColor: UIColor(rgb: 0xcacaca), pendingActivityColor: UIColor(rgb: 0x525252, alpha: 0.6), fileTitleColor: accentColor, fileDescriptionColor: UIColor(rgb: 0x999999), fileDurationColor: UIColor(rgb: 0x525252, alpha: 0.6), mediaPlaceholderColor: UIColor(rgb: 0xffffff).withMultipliedBrightnessBy(0.95), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0xc8c7cc), radioProgress: accentColor, highlight: accentColor.withAlphaComponent(0.12), separator: UIColor(rgb: 0xc8c7cc), bar: accentColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8)), actionButtonsStrokeColor: PresentationThemeVariableColor(withWallpaper: .clear, withoutWallpaper: accentColor), actionButtonsTextColor: PresentationThemeVariableColor(withWallpaper: .white, withoutWallpaper: accentColor)), - outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleFillColor.withMultipliedBrightnessBy(0.7), stroke: outgoingBubbleFillColor), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleFillColor.withMultipliedBrightnessBy(0.7), stroke: outgoingBubbleStrokeColor)), primaryTextColor: outgoingPrimaryTextColor, secondaryTextColor: outgoingSecondaryTextColor, linkTextColor: outgoingLinkTextColor, linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.3), scamColor: outgoingPrimaryTextColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: outgoingPrimaryTextColor, accentControlColor: outgoingPrimaryTextColor, mediaActiveControlColor: outgoingPrimaryTextColor, mediaInactiveControlColor: outgoingSecondaryTextColor, pendingActivityColor: outgoingSecondaryTextColor, fileTitleColor: outgoingPrimaryTextColor, fileDescriptionColor: outgoingSecondaryTextColor, fileDurationColor: outgoingSecondaryTextColor, mediaPlaceholderColor: accentColor.withMultipliedBrightnessBy(0.95), polls: PresentationThemeChatBubblePolls(radioButton: outgoingSecondaryTextColor, radioProgress: outgoingPrimaryTextColor, highlight: outgoingPrimaryTextColor.withAlphaComponent(0.12), separator: outgoingSecondaryTextColor, bar: outgoingPrimaryTextColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8)), actionButtonsStrokeColor: PresentationThemeVariableColor(withWallpaper: .clear, withoutWallpaper: accentColor), actionButtonsTextColor: PresentationThemeVariableColor(withWallpaper: .white, withoutWallpaper: accentColor)), + incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xffffff)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xf1f1f4), highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xf1f1f4))), primaryTextColor: .black, secondaryTextColor: UIColor(rgb: 0x525252, alpha: 0.6), linkTextColor: UIColor(rgb: 0x004bad), linkHighlightColor: accentColor.withAlphaComponent(0.3), scamColor: destructiveColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: accentColor, accentControlColor: accentColor, mediaActiveControlColor: accentColor, mediaInactiveControlColor: UIColor(rgb: 0xcacaca), pendingActivityColor: UIColor(rgb: 0x525252, alpha: 0.6), fileTitleColor: accentColor, fileDescriptionColor: UIColor(rgb: 0x999999), fileDurationColor: UIColor(rgb: 0x525252, alpha: 0.6), mediaPlaceholderColor: UIColor(rgb: 0xffffff).withMultipliedBrightnessBy(0.95), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0xc8c7cc), radioProgress: accentColor, highlight: accentColor.withAlphaComponent(0.12), separator: UIColor(rgb: 0xc8c7cc), bar: accentColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8)), actionButtonsStrokeColor: PresentationThemeVariableColor(withWallpaper: .clear, withoutWallpaper: accentColor), actionButtonsTextColor: PresentationThemeVariableColor(withWallpaper: .white, withoutWallpaper: accentColor), textSelectionColor: accentColor.withAlphaComponent(0.3), textSelectionKnobColor: accentColor), + outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleFillColor.withMultipliedBrightnessBy(0.7), stroke: outgoingBubbleFillColor), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleFillColor.withMultipliedBrightnessBy(0.7), stroke: outgoingBubbleStrokeColor)), primaryTextColor: outgoingPrimaryTextColor, secondaryTextColor: outgoingSecondaryTextColor, linkTextColor: outgoingLinkTextColor, linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.3), scamColor: outgoingPrimaryTextColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: outgoingPrimaryTextColor, accentControlColor: outgoingPrimaryTextColor, mediaActiveControlColor: outgoingPrimaryTextColor, mediaInactiveControlColor: outgoingSecondaryTextColor, pendingActivityColor: outgoingSecondaryTextColor, fileTitleColor: outgoingPrimaryTextColor, fileDescriptionColor: outgoingSecondaryTextColor, fileDurationColor: outgoingSecondaryTextColor, mediaPlaceholderColor: accentColor.withMultipliedBrightnessBy(0.95), polls: PresentationThemeChatBubblePolls(radioButton: outgoingSecondaryTextColor, radioProgress: outgoingPrimaryTextColor, highlight: outgoingPrimaryTextColor.withAlphaComponent(0.12), separator: outgoingSecondaryTextColor, bar: outgoingPrimaryTextColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8)), actionButtonsStrokeColor: PresentationThemeVariableColor(withWallpaper: .clear, withoutWallpaper: accentColor), actionButtonsTextColor: PresentationThemeVariableColor(withWallpaper: .white, withoutWallpaper: accentColor), textSelectionColor: accentColor.withAlphaComponent(0.3), textSelectionKnobColor: accentColor), freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xE5E5EA), highlightedFill: UIColor(rgb: 0xDADADE), stroke: UIColor(rgb: 0xE5E5EA)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xE5E5EA), highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xE5E5EA))), infoPrimaryTextColor: UIColor(rgb: 0x000000), infoLinkTextColor: UIColor(rgb: 0x004bad), diff --git a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift index b96fe4d53c..048f54d19c 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift @@ -541,8 +541,10 @@ public final class PresentationThemePartedColors { public let actionButtonsFillColor: PresentationThemeVariableColor public let actionButtonsStrokeColor: PresentationThemeVariableColor public let actionButtonsTextColor: PresentationThemeVariableColor + public let textSelectionColor: UIColor + public let textSelectionKnobColor: UIColor - public init(bubble: PresentationThemeBubbleColor, primaryTextColor: UIColor, secondaryTextColor: UIColor, linkTextColor: UIColor, linkHighlightColor: UIColor, scamColor: UIColor, textHighlightColor: UIColor, accentTextColor: UIColor, accentControlColor: UIColor, mediaActiveControlColor: UIColor, mediaInactiveControlColor: UIColor, pendingActivityColor: UIColor, fileTitleColor: UIColor, fileDescriptionColor: UIColor, fileDurationColor: UIColor, mediaPlaceholderColor: UIColor, polls: PresentationThemeChatBubblePolls, actionButtonsFillColor: PresentationThemeVariableColor, actionButtonsStrokeColor: PresentationThemeVariableColor, actionButtonsTextColor: PresentationThemeVariableColor) { + public init(bubble: PresentationThemeBubbleColor, primaryTextColor: UIColor, secondaryTextColor: UIColor, linkTextColor: UIColor, linkHighlightColor: UIColor, scamColor: UIColor, textHighlightColor: UIColor, accentTextColor: UIColor, accentControlColor: UIColor, mediaActiveControlColor: UIColor, mediaInactiveControlColor: UIColor, pendingActivityColor: UIColor, fileTitleColor: UIColor, fileDescriptionColor: UIColor, fileDurationColor: UIColor, mediaPlaceholderColor: UIColor, polls: PresentationThemeChatBubblePolls, actionButtonsFillColor: PresentationThemeVariableColor, actionButtonsStrokeColor: PresentationThemeVariableColor, actionButtonsTextColor: PresentationThemeVariableColor, textSelectionColor: UIColor, textSelectionKnobColor: UIColor) { self.bubble = bubble self.primaryTextColor = primaryTextColor self.secondaryTextColor = secondaryTextColor @@ -563,6 +565,8 @@ public final class PresentationThemePartedColors { self.actionButtonsFillColor = actionButtonsFillColor self.actionButtonsStrokeColor = actionButtonsStrokeColor self.actionButtonsTextColor = actionButtonsTextColor + self.textSelectionColor = textSelectionColor + self.textSelectionKnobColor = textSelectionKnobColor } } diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift index 264e2e1890..bc0078b670 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift @@ -915,33 +915,38 @@ extension PresentationThemePartedColors: Codable { case actionButtonsBg case actionButtonsStroke case actionButtonsText + case textSelection + case textSelectionKnob } public convenience init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) - self.init(bubble: try values.decode(PresentationThemeBubbleColor.self, forKey: .bubble), - primaryTextColor: try decodeColor(values, .primaryText), - secondaryTextColor: try decodeColor(values, .secondaryText), - linkTextColor: try decodeColor(values, .linkText), - linkHighlightColor: try decodeColor(values, .linkHighlight), - scamColor: try decodeColor(values, .scam), - textHighlightColor: try decodeColor(values, .textHighlight), - accentTextColor: try decodeColor(values, .accentText), - accentControlColor: try decodeColor(values, .accentControl), - mediaActiveControlColor: try decodeColor(values, .mediaActiveControl), - mediaInactiveControlColor: try decodeColor(values, .mediaInactiveControl), - pendingActivityColor: try decodeColor(values, .pendingActivity), - fileTitleColor: try decodeColor(values, .fileTitle), - fileDescriptionColor: try decodeColor(values, .fileDescription), - fileDurationColor: try decodeColor(values, .fileDuration), - mediaPlaceholderColor: try decodeColor(values, .mediaPlaceholder), - polls: try values.decode(PresentationThemeChatBubblePolls.self, forKey: .polls), - actionButtonsFillColor: try values.decode(PresentationThemeVariableColor.self, forKey: .actionButtonsBg), - actionButtonsStrokeColor: try values.decode(PresentationThemeVariableColor.self, forKey: .actionButtonsStroke), - actionButtonsTextColor: try values.decode(PresentationThemeVariableColor.self, forKey: .actionButtonsText)) + self.init( + bubble: try values.decode(PresentationThemeBubbleColor.self, forKey: .bubble), + primaryTextColor: try decodeColor(values, .primaryText), + secondaryTextColor: try decodeColor(values, .secondaryText), + linkTextColor: try decodeColor(values, .linkText), + linkHighlightColor: try decodeColor(values, .linkHighlight), + scamColor: try decodeColor(values, .scam), + textHighlightColor: try decodeColor(values, .textHighlight), + accentTextColor: try decodeColor(values, .accentText), + accentControlColor: try decodeColor(values, .accentControl), + mediaActiveControlColor: try decodeColor(values, .mediaActiveControl), + mediaInactiveControlColor: try decodeColor(values, .mediaInactiveControl), + pendingActivityColor: try decodeColor(values, .pendingActivity), + fileTitleColor: try decodeColor(values, .fileTitle), + fileDescriptionColor: try decodeColor(values, .fileDescription), + fileDurationColor: try decodeColor(values, .fileDuration), + mediaPlaceholderColor: try decodeColor(values, .mediaPlaceholder), + polls: try values.decode(PresentationThemeChatBubblePolls.self, forKey: .polls), + actionButtonsFillColor: try values.decode(PresentationThemeVariableColor.self, forKey: .actionButtonsBg), + actionButtonsStrokeColor: try values.decode(PresentationThemeVariableColor.self, forKey: .actionButtonsStroke), + actionButtonsTextColor: try values.decode(PresentationThemeVariableColor.self, forKey: .actionButtonsText), + textSelectionColor: try decodeColor(values, .textSelection), + textSelectionKnobColor: try decodeColor(values, .textSelectionKnob) + ) } - public func encode(to encoder: Encoder) throws { var values = encoder.container(keyedBy: CodingKeys.self) try values.encode(self.bubble, forKey: .bubble) @@ -964,6 +969,8 @@ extension PresentationThemePartedColors: Codable { try values.encode(self.actionButtonsFillColor, forKey: .actionButtonsBg) try values.encode(self.actionButtonsStrokeColor, forKey: .actionButtonsStroke) try values.encode(self.actionButtonsTextColor, forKey: .actionButtonsText) + try encodeColor(&values, self.textSelectionColor, .textSelection) + try encodeColor(&values, self.textSelectionKnobColor, .textSelectionKnob) } } diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unstar.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unstar.imageset/Contents.json new file mode 100644 index 0000000000..bd84ab891e --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unstar.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_lt_unstar.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unstar.imageset/ic_lt_unstar.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unstar.imageset/ic_lt_unstar.pdf new file mode 100644 index 0000000000..551690fa0e Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unstar.imageset/ic_lt_unstar.pdf differ diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index 36788365b1..361c6573c1 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -521,6 +521,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self, strongSelf.isNodeLoaded else { return } + if strongSelf.presentationInterfaceState.interfaceState.selectionState != nil { + return + } if let messages = strongSelf.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(message.id) { (strongSelf.view.window as? WindowHost)?.cancelInteractiveKeyboardGestures() var updatedMessages = messages @@ -1332,7 +1335,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, openSearch: { }, setupReply: { [weak self] messageId in - self?.interfaceInteraction?.setupReplyMessage(messageId) + self?.interfaceInteraction?.setupReplyMessage(messageId, { _ in }) }, canSetupReply: { [weak self] message in if !message.flags.contains(.Incoming) { if !message.flags.intersection([.Failed, .Sending, .Unsent]).isEmpty { @@ -2631,7 +2634,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var mappedTransition: (ChatHistoryListViewTransition, ListViewUpdateSizeAndInsets?)? let isScheduledMessages = strongSelf.presentationInterfaceState.isScheduledMessages - strongSelf.chatDisplayNode.containerLayoutUpdated(validLayout, navigationBarHeight: strongSelf.navigationHeight, transition: .animated(duration: 0.2, curve: .easeInOut), listViewTransaction: { updateSizeAndInsets, _, _ in + strongSelf.chatDisplayNode.containerLayoutUpdated(validLayout, navigationBarHeight: strongSelf.navigationHeight, transition: .animated(duration: 0.2, curve: .easeInOut), listViewTransaction: { updateSizeAndInsets, _, _, _ in var options = transition.options let _ = options.insert(.Synchronous) let _ = options.insert(.LowLatency) @@ -2860,15 +2863,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.present(actionSheet, in: .window(.root)) } - let interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { [weak self] messageId in + let interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { [weak self] messageId, completion in if let strongSelf = self, strongSelf.isNodeLoaded, canSendMessagesToChat(strongSelf.presentationInterfaceState) { if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageId(message.id) }).updatedSearch(nil) }) + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageId(message.id) }).updatedSearch(nil) }, completion: completion) strongSelf.updateItemNodesSearchTextHighlightStates() strongSelf.chatDisplayNode.ensureInputViewFocused() + } else { + completion(.immediate) } + } else { + completion(.immediate) } - }, setupEditMessage: { [weak self] messageId in + }, setupEditMessage: { [weak self] messageId, completion in if let strongSelf = self, strongSelf.isNodeLoaded { guard let messageId = messageId else { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in @@ -2878,7 +2885,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } state = state.updatedEditMessageState(nil) return state - }) + }, completion: completion) strongSelf.editMessageDisposable.set(nil) return @@ -2902,12 +2909,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) return updated - }) + }, completion: completion) } } - }, beginMessageSelection: { [weak self] messageIds in + }, beginMessageSelection: { [weak self] messageIds, completion in if let strongSelf = self, strongSelf.isNodeLoaded { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true,{ $0.updatedInterfaceState { $0.withUpdatedSelectedMessages(messageIds) } }) + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withUpdatedSelectedMessages(messageIds) } }, completion: completion) if let selectionState = strongSelf.presentationInterfaceState.interfaceState.selectionState { let count = selectionState.selectedIds.count @@ -2919,6 +2926,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } UIAccessibility.post(notification: UIAccessibility.Notification.announcement, argument: text) } + } else { + completion(.immediate) } }, deleteSelectedMessages: { [weak self] in if let strongSelf = self { @@ -4381,8 +4390,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G print() } - self.chatDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop in - self.chatDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop) + self.chatDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop, completion in + self.chatDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop, completion: completion) }) } @@ -4402,11 +4411,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - func updateChatPresentationInterfaceState(animated: Bool = true, interactive: Bool, saveInterfaceState: Bool = false, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) { - self.updateChatPresentationInterfaceState(transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate, interactive: interactive, saveInterfaceState: saveInterfaceState, f) + func updateChatPresentationInterfaceState(animated: Bool = true, interactive: Bool, saveInterfaceState: Bool = false, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) { + self.updateChatPresentationInterfaceState(transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate, interactive: interactive, saveInterfaceState: saveInterfaceState, f, completion: completion) } - func updateChatPresentationInterfaceState(transition: ContainedViewLayoutTransition, interactive: Bool, saveInterfaceState: Bool = false, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) { + func updateChatPresentationInterfaceState(transition: ContainedViewLayoutTransition, interactive: Bool, saveInterfaceState: Bool = false, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion externalCompletion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) { + var completion = externalCompletion var temporaryChatPresentationInterfaceState = f(self.presentationInterfaceState) if self.presentationInterfaceState.keyboardButtonsMessage?.visibleButtonKeyboardMarkup != temporaryChatPresentationInterfaceState.keyboardButtonsMessage?.visibleButtonKeyboardMarkup { @@ -4454,64 +4464,64 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G for (kind, update) in contextQueryUpdates { switch update { - case .remove: - if let (_, disposable) = self.contextQueryStates[kind] { - disposable.dispose() - self.contextQueryStates.removeValue(forKey: kind) - - updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedInputQueryResult(queryKind: kind, { _ in - return nil - }) - } - case let .update(query, signal): - let currentQueryAndDisposable = self.contextQueryStates[kind] - currentQueryAndDisposable?.1.dispose() + case .remove: + if let (_, disposable) = self.contextQueryStates[kind] { + disposable.dispose() + self.contextQueryStates.removeValue(forKey: kind) - var inScope = true - var inScopeResult: ((ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?)? - self.contextQueryStates[kind] = (query, (signal |> deliverOnMainQueue).start(next: { [weak self] result in - if let strongSelf = self { - if Thread.isMainThread && inScope { - inScope = false - inScopeResult = result - } else { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInputQueryResult(queryKind: kind, { previousResult in - return result(previousResult) - }) - }) - } - } - }, error: { [weak self] error in - if let strongSelf = self { - switch error { - case let .inlineBotLocationRequest(peerId): - strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_ShareInlineBotLocationConfirmation, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { - let _ = ApplicationSpecificNotice.setInlineBotLocationRequest(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peerId, value: Int32(Date().timeIntervalSince1970 + 10 * 60)).start() - }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { - let _ = ApplicationSpecificNotice.setInlineBotLocationRequest(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peerId, value: 0).start() - })]), in: .window(.root)) - } - } - })) - inScope = false - if let inScopeResult = inScopeResult { - updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedInputQueryResult(queryKind: kind, { previousResult in - return inScopeResult(previousResult) - }) - } + updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedInputQueryResult(queryKind: kind, { _ in + return nil + }) + } + case let .update(query, signal): + let currentQueryAndDisposable = self.contextQueryStates[kind] + currentQueryAndDisposable?.1.dispose() - if case let .peer(peerId) = self.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat { - if case .contextRequest = query { - let _ = (ApplicationSpecificNotice.getSecretChatInlineBotUsage(accountManager: self.context.sharedContext.accountManager) - |> deliverOnMainQueue).start(next: { [weak self] value in - if let strongSelf = self, !value { - let _ = ApplicationSpecificNotice.setSecretChatInlineBotUsage(accountManager: strongSelf.context.sharedContext.accountManager).start() - strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_SecretChatContextBotAlert, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - } + var inScope = true + var inScopeResult: ((ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?)? + self.contextQueryStates[kind] = (query, (signal |> deliverOnMainQueue).start(next: { [weak self] result in + if let strongSelf = self { + if Thread.isMainThread && inScope { + inScope = false + inScopeResult = result + } else { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInputQueryResult(queryKind: kind, { previousResult in + return result(previousResult) + }) }) } } + }, error: { [weak self] error in + if let strongSelf = self { + switch error { + case let .inlineBotLocationRequest(peerId): + strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_ShareInlineBotLocationConfirmation, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { + let _ = ApplicationSpecificNotice.setInlineBotLocationRequest(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peerId, value: Int32(Date().timeIntervalSince1970 + 10 * 60)).start() + }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { + let _ = ApplicationSpecificNotice.setInlineBotLocationRequest(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peerId, value: 0).start() + })]), in: .window(.root)) + } + } + })) + inScope = false + if let inScopeResult = inScopeResult { + updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedInputQueryResult(queryKind: kind, { previousResult in + return inScopeResult(previousResult) + }) + } + + if case let .peer(peerId) = self.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat { + if case .contextRequest = query { + let _ = (ApplicationSpecificNotice.getSecretChatInlineBotUsage(accountManager: self.context.sharedContext.accountManager) + |> deliverOnMainQueue).start(next: { [weak self] value in + if let strongSelf = self, !value { + let _ = ApplicationSpecificNotice.setSecretChatInlineBotUsage(accountManager: strongSelf.context.sharedContext.accountManager).start() + strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_SecretChatContextBotAlert, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + } + }) + } + } } } @@ -4665,7 +4675,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.updateSlowmodeStatus() if self.isNodeLoaded { - self.chatDisplayNode.updateChatPresentationInterfaceState(updatedChatPresentationInterfaceState, transition: transition, interactive: interactive) + self.chatDisplayNode.updateChatPresentationInterfaceState(updatedChatPresentationInterfaceState, transition: transition, interactive: interactive, completion: completion) + } else { + completion(.immediate) } // if let selectionState = self.presentationInterfaceState.interfaceState.selectionState, !selectionState.selectedIds.isEmpty { @@ -4705,8 +4717,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let controllerInteraction = self.controllerInteraction { if updatedChatPresentationInterfaceState.interfaceState.selectionState != controllerInteraction.selectionState { controllerInteraction.selectionState = updatedChatPresentationInterfaceState.interfaceState.selectionState + let isBlackout = controllerInteraction.selectionState != nil + completion = { [weak self] transition in + completion(transition) + (self?.navigationController as? NavigationController)?.updateMasterDetailsBlackout(isBlackout ? .master : nil, transition: transition) + } self.updateItemNodesSelectionStates(animated: transition.isAnimated) - (self.navigationController as? NavigationController)?.updateMasterDetailsBlackout(controllerInteraction.selectionState != nil ? .master : nil, transition: transition) } } @@ -7481,7 +7497,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if canEdit, let message = self.chatDisplayNode.historyNode.firstMessageForEditInCurrentHistoryView() { inputShortcuts.append(KeyShortcut(input: UIKeyCommand.inputUpArrow, action: { [weak self] in if let strongSelf = self { - strongSelf.interfaceInteraction?.setupEditMessage(message.id) + strongSelf.interfaceInteraction?.setupEditMessage(message.id, { _ in }) } })) } diff --git a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift index 9fa8299821..a37570e606 100644 --- a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift @@ -183,14 +183,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private var openStickersDisposable: Disposable? private var displayVideoUnmuteTipDisposable: Disposable? - /*override var accessibilityElements: [Any]? { - get { - var accessibilityElements: [Any] = [] - addAccessibilityChildren(of: self.historyNode, container: self.historyNode, to: &accessibilityElements) - return accessibilityElements - } set(value) { - } - }*/ + private var onLayoutCompletions: [(ContainedViewLayoutTransition) -> Void] = [] init(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, chatPresentationInterfaceState: ChatPresentationInterfaceState, automaticMediaDownloadSettings: MediaAutoDownloadSettings, navigationBar: NavigationBar?, controller: ChatControllerImpl?) { self.context = context @@ -420,7 +413,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition protoTransition: ContainedViewLayoutTransition, listViewTransaction: - (ListViewUpdateSizeAndInsets, CGFloat, Bool) -> Void) { + (ListViewUpdateSizeAndInsets, CGFloat, Bool, @escaping () -> Void) -> Void) { let transition: ContainedViewLayoutTransition if let _ = self.scheduledAnimateInAsOverlayFromNode { transition = .immediate @@ -746,7 +739,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } else if let _ = accessoryPanelNode as? ForwardAccessoryPanelNode { strongSelf.requestUpdateChatInterfaceState(true, false, { $0.withUpdatedForwardMessageIds(nil) }) } else if let _ = accessoryPanelNode as? EditAccessoryPanelNode { - strongSelf.interfaceInteraction?.setupEditMessage(nil) + strongSelf.interfaceInteraction?.setupEditMessage(nil, { _ in }) } else if let _ = accessoryPanelNode as? WebpagePreviewAccessoryPanelNode { strongSelf.dismissUrlPreview() } @@ -1019,7 +1012,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { listInsets.top = listInsets.top + messageActionSheetControllerAdditionalInset } - listViewTransaction(ListViewUpdateSizeAndInsets(size: contentBounds.size, insets: listInsets, scrollIndicatorInsets: listScrollIndicatorInsets, duration: duration, curve: listViewCurve, ensureTopInsetForOverlayHighlightedItems: ensureTopInsetForOverlayHighlightedItems), additionalScrollDistance, scrollToTop) + listViewTransaction(ListViewUpdateSizeAndInsets(size: contentBounds.size, insets: listInsets, scrollIndicatorInsets: listScrollIndicatorInsets, duration: duration, curve: listViewCurve, ensureTopInsetForOverlayHighlightedItems: ensureTopInsetForOverlayHighlightedItems), additionalScrollDistance, scrollToTop, { [weak self] in + if let strongSelf = self { + strongSelf.notifyTransitionCompletionListeners(transition: transition) + } + }) let navigateButtonsSize = self.navigateButtons.updateLayout(transition: transition) var navigateButtonsFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - navigateButtonsSize.width - 6.0, y: layout.size.height - containerInsets.bottom - inputPanelsHeight - navigateButtonsSize.height - 6.0 - bottomOverflowOffset), size: navigateButtonsSize) @@ -1285,6 +1282,16 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.derivedLayoutState = ChatControllerNodeDerivedLayoutState(inputContextPanelsFrame: inputContextPanelsFrame, inputContextPanelsOverMainPanelFrame: inputContextPanelsOverMainPanelFrame, inputNodeHeight: inputNodeHeightAndOverflow?.0, upperInputPositionBound: inputNodeHeightAndOverflow?.0 != nil ? self.upperInputPositionBound : nil) } + private func notifyTransitionCompletionListeners(transition: ContainedViewLayoutTransition) { + if !self.onLayoutCompletions.isEmpty { + let onLayoutCompletions = self.onLayoutCompletions + self.onLayoutCompletions = [] + for completion in onLayoutCompletions { + completion(transition) + } + } + } + private func chatPresentationInterfaceStateRequiresInputFocus(_ state: ChatPresentationInterfaceState) -> Bool { switch state.inputMode { case .text: @@ -1298,7 +1305,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - func updateChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, transition: ContainedViewLayoutTransition, interactive: Bool) { + func updateChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, transition: ContainedViewLayoutTransition, interactive: Bool, completion: @escaping (ContainedViewLayoutTransition) -> Void) { self.selectedMessages = chatPresentationInterfaceState.interfaceState.selectionState?.selectedIds if let textInputPanelNode = self.textInputPanelNode { @@ -1306,6 +1313,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } if self.chatPresentationInterfaceState != chatPresentationInterfaceState { + self.onLayoutCompletions.append(completion) + let themeUpdated = self.chatPresentationInterfaceState.theme !== chatPresentationInterfaceState.theme if self.chatPresentationInterfaceState.chatWallpaper != chatPresentationInterfaceState.chatWallpaper { @@ -1445,6 +1454,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } } + } else { + completion(.immediate) } if self.reactionContainerNode.supernode == nil { @@ -1572,7 +1583,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } func frameForVisibleArea() -> CGRect { - return CGRect(origin: CGPoint(x: self.visibleAreaInset.left, y: self.visibleAreaInset.top), size: CGSize(width: self.bounds.size.width - self.visibleAreaInset.left - self.visibleAreaInset.right, height: self.bounds.size.height - self.visibleAreaInset.top - self.visibleAreaInset.bottom)) + let rect = CGRect(origin: CGPoint(x: self.visibleAreaInset.left, y: self.visibleAreaInset.top), size: CGSize(width: self.bounds.size.width - self.visibleAreaInset.left - self.visibleAreaInset.right, height: self.bounds.size.height - self.visibleAreaInset.top - self.visibleAreaInset.bottom)) + if let containerNode = self.containerNode { + return containerNode.view.convert(rect, to: self.view) + } else { + return rect + } } func frameForInputPanelAccessoryButton(_ item: ChatTextInputAccessoryItem) -> CGRect? { @@ -1841,8 +1857,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } } - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop in - self.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop) + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop, completion in + self.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop, completion: completion) }) if animateIn, let controller = self.messageActionSheetController?.0 { controller.controllerNode.animateIn(transition: transition) @@ -1926,8 +1942,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private func updateLayoutInternal(transition: ContainedViewLayoutTransition) { if let (layout, navigationHeight) = self.validLayout { - self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop in - self.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop) + self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop, completion in + self.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop, completion: completion) }) } } @@ -2108,7 +2124,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } func animateOut(completion: (() -> Void)? = nil) { - self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in + self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { _ in completion?() }) } diff --git a/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift b/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift index 90d36c02c4..c69e4d0815 100644 --- a/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift @@ -1364,15 +1364,17 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) { - self.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: 0.0, scrollToTop: false) + self.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: 0.0, scrollToTop: false, completion: {}) } - public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets, additionalScrollDistance: CGFloat, scrollToTop: Bool) { + public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets, additionalScrollDistance: CGFloat, scrollToTop: Bool, completion: @escaping () -> Void) { var scrollToItem: ListViewScrollToItem? if scrollToTop, case .known = self.visibleContentOffset() { scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Spring(duration: updateSizeAndInsets.duration), directionHint: .Up) } - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: scrollToItem, additionalScrollDistance: scrollToTop ? 0.0 : additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: scrollToItem, additionalScrollDistance: scrollToTop ? 0.0 : additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in + completion() + }) if !self.dequeuedInitialTransitionOnLayout { self.dequeuedInitialTransitionOnLayout = true diff --git a/submodules/TelegramUI/TelegramUI/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/TelegramUI/ChatInterfaceStateContextMenus.swift index d1e7c1b6cd..7b56e4f7a3 100644 --- a/submodules/TelegramUI/TelegramUI/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/TelegramUI/ChatInterfaceStateContextMenus.swift @@ -365,7 +365,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: if let starStatus = data.starStatus { actions.append(.action(ContextMenuActionItem(text: starStatus ? chatPresentationInterfaceState.strings.Stickers_RemoveFromFavorites : chatPresentationInterfaceState.strings.Stickers_AddToFavorites, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.actionSheet.primaryTextColor) + return generateTintedImage(image: starStatus ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in interfaceInteraction.toggleMessageStickerStarred(messages[0].id) f(.default) @@ -376,8 +376,9 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuReply, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in - interfaceInteraction.setupReplyMessage(messages[0].id) - f(.dismissWithoutContent) + interfaceInteraction.setupReplyMessage(messages[0].id, { transition in + f(.custom(transition)) + }) }))) } @@ -403,8 +404,9 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_MessageDialogEdit, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in - interfaceInteraction.setupEditMessage(messages[0].id) - f(.dismissWithoutContent) + interfaceInteraction.setupEditMessage(messages[0].id, { transition in + f(.custom(transition)) + }) }))) } @@ -551,13 +553,13 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: let presentationData = context.sharedContext.currentPresentationData.with { $0 } if channel.addressName == nil { - controllerInteraction.presentController(OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .genericSuccess(presentationData.strings.Conversation_PrivateMessageLinkCopied, true)), nil) + controllerInteraction.presentGlobalOverlayController(OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .genericSuccess(presentationData.strings.Conversation_PrivateMessageLinkCopied, true)), nil) } else { - controllerInteraction.presentController(OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .genericSuccess(presentationData.strings.GroupInfo_InviteLink_CopyAlert_Success, false)), nil) + controllerInteraction.presentGlobalOverlayController(OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .genericSuccess(presentationData.strings.GroupInfo_InviteLink_CopyAlert_Success, false)), nil) } } }) - f(.dismissWithoutContent) + f(.default) }))) } @@ -667,8 +669,9 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuMore, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in - interfaceInteraction.beginMessageSelection(selectAll ? messages.map { $0.id } : [message.id]) - f(.default) + interfaceInteraction.beginMessageSelection(selectAll ? messages.map { $0.id } : [message.id], { transition in + f(.custom(transition)) + }) }))) } diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift index 400a5414fa..dce649cccd 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift @@ -977,12 +977,12 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let previousSubnodeTransform = self.subnodeTransform self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0); if animated { - selectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.4) + selectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2) if !incoming { let position = selectionNode.layer.position - selectionNode.layer.animatePosition(from: CGPoint(x: position.x - 42.0, y: position.y), to: position, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) + selectionNode.layer.animatePosition(from: CGPoint(x: position.x - 42.0, y: position.y), to: position, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) } } } @@ -992,13 +992,13 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let previousSubnodeTransform = self.subnodeTransform self.subnodeTransform = CATransform3DIdentity if animated { - self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.4, completion: { [weak selectionNode]_ in + self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, completion: { [weak selectionNode]_ in selectionNode?.removeFromSupernode() }) - selectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + selectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) if CGFloat(0.0).isLessThanOrEqualTo(selectionNode.frame.origin.x) { let position = selectionNode.layer.position - selectionNode.layer.animatePosition(from: position, to: CGPoint(x: position.x - 42.0, y: position.y), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + selectionNode.layer.animatePosition(from: position, to: CGPoint(x: position.x - 42.0, y: position.y), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false) } } else { selectionNode.removeFromSupernode() diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift index 8086c07788..0254aee299 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift @@ -2092,6 +2092,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode break } self.contextSourceNode.contentRect = backgroundFrame.offsetBy(dx: incomingOffset, dy: 0.0) + if !self.contextSourceNode.isExtractedToContextPreview { + if let (rect, size) = self.absoluteRect { + self.updateAbsoluteRect(rect, within: size) + } + } } self.messageAccessibilityArea.frame = backgroundFrame @@ -2615,12 +2620,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let previousSubnodeTransform = self.subnodeTransform self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0); if animated { - selectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.4) + selectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2) if !incoming { let position = selectionNode.layer.position - selectionNode.layer.animatePosition(from: CGPoint(x: position.x - 42.0, y: position.y), to: position, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) + selectionNode.layer.animatePosition(from: CGPoint(x: position.x - 42.0, y: position.y), to: position, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) } } } @@ -2630,13 +2635,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let previousSubnodeTransform = self.subnodeTransform self.subnodeTransform = CATransform3DIdentity if animated { - self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.4, completion: { [weak selectionNode]_ in + self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, completion: { [weak selectionNode]_ in selectionNode?.removeFromSupernode() }) - selectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + selectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) if CGFloat(0.0).isLessThanOrEqualTo(selectionNode.frame.origin.x) { let position = selectionNode.layer.position - selectionNode.layer.animatePosition(from: position, to: CGPoint(x: position.x - 42.0, y: position.y), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + selectionNode.layer.animatePosition(from: position, to: CGPoint(x: position.x - 42.0, y: position.y), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false) } } else { selectionNode.removeFromSupernode() diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift index f0763787e9..b49d6a8ec8 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift @@ -773,12 +773,12 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { let previousSubnodeTransform = self.subnodeTransform self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0); if animated { - selectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.4) + selectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2) if !incoming { let position = selectionNode.layer.position - selectionNode.layer.animatePosition(from: CGPoint(x: position.x - 42.0, y: position.y), to: position, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) + selectionNode.layer.animatePosition(from: CGPoint(x: position.x - 42.0, y: position.y), to: position, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) } } } @@ -788,13 +788,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { let previousSubnodeTransform = self.subnodeTransform self.subnodeTransform = CATransform3DIdentity if animated { - self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.4, completion: { [weak selectionNode]_ in + self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, completion: { [weak selectionNode]_ in selectionNode?.removeFromSupernode() }) - selectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + selectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) if CGFloat(0.0).isLessThanOrEqualTo(selectionNode.frame.origin.x) { let position = selectionNode.layer.position - selectionNode.layer.animatePosition(from: position, to: CGPoint(x: position.x - 42.0, y: position.y), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + selectionNode.layer.animatePosition(from: position, to: CGPoint(x: position.x - 42.0, y: position.y), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false) } } else { selectionNode.removeFromSupernode() diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift index eeb2a51b28..b24d2612e6 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift @@ -790,12 +790,12 @@ class ChatMessageStickerItemNode: ChatMessageItemView { let previousSubnodeTransform = self.subnodeTransform self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0); if animated { - selectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.4) + selectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2) if !incoming { let position = selectionNode.layer.position - selectionNode.layer.animatePosition(from: CGPoint(x: position.x - 42.0, y: position.y), to: position, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) + selectionNode.layer.animatePosition(from: CGPoint(x: position.x - 42.0, y: position.y), to: position, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) } } } @@ -816,13 +816,13 @@ class ChatMessageStickerItemNode: ChatMessageItemView { let previousSubnodeTransform = self.subnodeTransform self.subnodeTransform = CATransform3DIdentity if animated { - self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.4, completion: { [weak selectionNode]_ in + self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, completion: { [weak selectionNode]_ in selectionNode?.removeFromSupernode() }) - selectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + selectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) if CGFloat(0.0).isLessThanOrEqualTo(selectionNode.frame.origin.x) { let position = selectionNode.layer.position - selectionNode.layer.animatePosition(from: position, to: CGPoint(x: position.x - 42.0, y: position.y), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + selectionNode.layer.animatePosition(from: position, to: CGPoint(x: position.x - 42.0, y: position.y), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false) } } else { selectionNode.removeFromSupernode() diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift index 0573f6e787..3fec6bc3d9 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift @@ -522,7 +522,9 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { if !value { if let textSelectionNode = self.textSelectionNode { self.textSelectionNode = nil + textSelectionNode.highlightAreaNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) textSelectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak textSelectionNode] _ in + textSelectionNode?.highlightAreaNode.removeFromSupernode() textSelectionNode?.removeFromSupernode() }) } @@ -532,7 +534,17 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { override func updateIsExtractedToContextPreview(_ value: Bool) { if value { if self.textSelectionNode == nil, let item = self.item, let rootNode = item.controllerInteraction.chatControllerNode() { - let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: item.presentationData.theme.theme.list.itemAccentColor.withAlphaComponent(0.5), knob: item.presentationData.theme.theme.list.itemAccentColor), textNode: self.textNode, updateIsActive: { [weak self] value in + let selectionColor: UIColor + let knobColor: UIColor + if item.message.effectivelyIncoming(item.context.account.peerId) { + selectionColor = item.presentationData.theme.theme.chat.message.incoming.textSelectionColor + knobColor = item.presentationData.theme.theme.chat.message.incoming.textSelectionKnobColor + } else { + selectionColor = item.presentationData.theme.theme.chat.message.outgoing.textSelectionColor + knobColor = item.presentationData.theme.theme.chat.message.outgoing.textSelectionKnobColor + } + + let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: selectionColor, knob: knobColor), textNode: self.textNode, updateIsActive: { [weak self] value in self?.updateIsTextSelectionActive?(value) }, present: { [weak self] c, a in self?.item?.controllerInteraction.presentGlobalOverlayController(c, a) @@ -544,12 +556,16 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { }) self.textSelectionNode = textSelectionNode self.addSubnode(textSelectionNode) + self.insertSubnode(textSelectionNode.highlightAreaNode, belowSubnode: self.textNode) textSelectionNode.frame = self.textNode.frame + textSelectionNode.highlightAreaNode.frame = self.textNode.frame } } else if let textSelectionNode = self.textSelectionNode { self.textSelectionNode = nil self.updateIsTextSelectionActive?(false) + textSelectionNode.highlightAreaNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) textSelectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak textSelectionNode] _ in + textSelectionNode?.highlightAreaNode.removeFromSupernode() textSelectionNode?.removeFromSupernode() }) } diff --git a/submodules/TelegramUI/TelegramUI/ChatPanelInterfaceInteraction.swift b/submodules/TelegramUI/TelegramUI/ChatPanelInterfaceInteraction.swift index 701b926da3..cc9e6adde1 100644 --- a/submodules/TelegramUI/TelegramUI/ChatPanelInterfaceInteraction.swift +++ b/submodules/TelegramUI/TelegramUI/ChatPanelInterfaceInteraction.swift @@ -45,9 +45,9 @@ enum ChatPanelRestrictionInfoDisplayType { } final class ChatPanelInterfaceInteraction { - let setupReplyMessage: (MessageId) -> Void - let setupEditMessage: (MessageId?) -> Void - let beginMessageSelection: ([MessageId]) -> Void + let setupReplyMessage: (MessageId, @escaping (ContainedViewLayoutTransition) -> Void) -> Void + let setupEditMessage: (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void + let beginMessageSelection: ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void let deleteSelectedMessages: () -> Void let reportSelectedMessages: () -> Void let reportMessages: ([Message], ContextController?) -> Void @@ -112,7 +112,7 @@ final class ChatPanelInterfaceInteraction { let openScheduledMessages: () -> Void let statuses: ChatPanelInterfaceInteractionStatuses? - init(setupReplyMessage: @escaping (MessageId) -> Void, setupEditMessage: @escaping (MessageId?) -> Void, beginMessageSelection: @escaping ([MessageId]) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message], ContextController?) -> Void, deleteMessages: @escaping ([Message], ContextController?, @escaping (ContextMenuActionResult) -> Void) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, openStickers: @escaping () -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, dismissMessageSearch: @escaping () -> Void, updateMessageSearch: @escaping (String) -> Void, navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, navigateToMessage: @escaping (MessageId) -> Void, navigateToChat: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping () -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void, displayVideoUnmuteTip: @escaping (CGPoint?) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, unblockPeer: @escaping () -> Void, pinMessage: @escaping (MessageId) -> Void, unpinMessage: @escaping () -> Void, shareAccountContact: @escaping () -> Void, reportPeer: @escaping () -> Void, presentPeerContact: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping () -> Void, toggleMessageStickerStarred: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, requestUnvoteInMessage: @escaping (MessageId) -> Void, requestStopPollInMessage: @escaping (MessageId) -> Void, updateInputLanguage: @escaping ((String?) -> String?) -> Void, unarchiveChat: @escaping () -> Void, openLinkEditing: @escaping () -> Void, reportPeerIrrelevantGeoLocation: @escaping () -> Void, displaySlowmodeTooltip: @escaping (ASDisplayNode, CGRect) -> Void, displaySendMessageOptions: @escaping () -> Void, openScheduledMessages: @escaping () -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) { + init(setupReplyMessage: @escaping (MessageId, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, setupEditMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, beginMessageSelection: @escaping ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message], ContextController?) -> Void, deleteMessages: @escaping ([Message], ContextController?, @escaping (ContextMenuActionResult) -> Void) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, openStickers: @escaping () -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, dismissMessageSearch: @escaping () -> Void, updateMessageSearch: @escaping (String) -> Void, navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, navigateToMessage: @escaping (MessageId) -> Void, navigateToChat: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping () -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void, displayVideoUnmuteTip: @escaping (CGPoint?) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, unblockPeer: @escaping () -> Void, pinMessage: @escaping (MessageId) -> Void, unpinMessage: @escaping () -> Void, shareAccountContact: @escaping () -> Void, reportPeer: @escaping () -> Void, presentPeerContact: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping () -> Void, toggleMessageStickerStarred: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, requestUnvoteInMessage: @escaping (MessageId) -> Void, requestStopPollInMessage: @escaping (MessageId) -> Void, updateInputLanguage: @escaping ((String?) -> String?) -> Void, unarchiveChat: @escaping () -> Void, openLinkEditing: @escaping () -> Void, reportPeerIrrelevantGeoLocation: @escaping () -> Void, displaySlowmodeTooltip: @escaping (ASDisplayNode, CGRect) -> Void, displaySendMessageOptions: @escaping () -> Void, openScheduledMessages: @escaping () -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) { self.setupReplyMessage = setupReplyMessage self.setupEditMessage = setupEditMessage self.beginMessageSelection = beginMessageSelection diff --git a/submodules/TelegramUI/TelegramUI/ChatRecentActionsController.swift b/submodules/TelegramUI/TelegramUI/ChatRecentActionsController.swift index 5fb8c6cbab..daf1570a2f 100644 --- a/submodules/TelegramUI/TelegramUI/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatRecentActionsController.swift @@ -42,9 +42,9 @@ final class ChatRecentActionsController: TelegramBaseController { } }) - self.panelInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _ in - }, setupEditMessage: { _ in - }, beginMessageSelection: { _ in + self.panelInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _, _ in + }, setupEditMessage: { _, _ in + }, beginMessageSelection: { _, _ in }, deleteSelectedMessages: { }, reportSelectedMessages: { }, reportMessages: { _, _ in diff --git a/submodules/TelegramUI/TelegramUI/NavigateToChatController.swift b/submodules/TelegramUI/TelegramUI/NavigateToChatController.swift index f68c8f7c9b..efb449495c 100644 --- a/submodules/TelegramUI/TelegramUI/NavigateToChatController.swift +++ b/submodules/TelegramUI/TelegramUI/NavigateToChatController.swift @@ -41,9 +41,9 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam controller.activateInput() } if let botStart = params.botStart { - controller.updateChatPresentationInterfaceState(interactive: false) { state -> ChatPresentationInterfaceState in + controller.updateChatPresentationInterfaceState(interactive: false, { state -> ChatPresentationInterfaceState in return state.updatedBotStartPayload(botStart.payload) - } + }) } found = true break @@ -56,9 +56,9 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam if let chatController = params.chatController as? ChatControllerImpl { controller = chatController if let botStart = params.botStart { - controller.updateChatPresentationInterfaceState(interactive: false) { state -> ChatPresentationInterfaceState in + controller.updateChatPresentationInterfaceState(interactive: false, { state -> ChatPresentationInterfaceState in return state.updatedBotStartPayload(botStart.payload) - } + }) } } else { controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation, subject: params.subject, botStart: params.botStart) diff --git a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift index 2e17002c98..ae18dd209a 100644 --- a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift +++ b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift @@ -296,9 +296,9 @@ public class PeerMediaCollectionController: TelegramBaseController { self.controllerInteraction = controllerInteraction - self.interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _ in - }, setupEditMessage: { _ in - }, beginMessageSelection: { _ in + self.interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _, _ in + }, setupEditMessage: { _, _ in + }, beginMessageSelection: { _, _ in }, deleteSelectedMessages: { [weak self] in if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds { strongSelf.deleteMessages(messageIds) diff --git a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift index 858b521718..96fd26e427 100644 --- a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift +++ b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift @@ -197,6 +197,8 @@ public final class TextSelectionNode: ASDisplayNode { private var currentRange: (Int, Int)? private var currentRects: [CGRect]? + public let highlightAreaNode: ASDisplayNode + public init(theme: TextSelectionTheme, textNode: TextNode, updateIsActive: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void, rootNode: ASDisplayNode, performAction: @escaping (String, TextSelectionAction) -> Void) { self.theme = theme self.textNode = textNode @@ -217,6 +219,8 @@ public final class TextSelectionNode: ASDisplayNode { self.rightKnob.displayWithoutProcessing = true self.rightKnob.alpha = 0.0 + self.highlightAreaNode = ASDisplayNode() + super.init() self.setViewBlock({ @@ -262,7 +266,7 @@ public final class TextSelectionNode: ASDisplayNode { let updatedRange = NSRange(location: min(updatedMin, updatedMax), length: max(updatedMin, updatedMax) - min(updatedMin, updatedMax)) if strongSelf.currentRange?.0 != updatedMin || strongSelf.currentRange?.1 != updatedMax { strongSelf.currentRange = (updatedMin, updatedMax) - strongSelf.updateSelection(range: updatedRange) + strongSelf.updateSelection(range: updatedRange, animateIn: false) } if let scrollView = findScrollView(view: strongSelf.view) { @@ -311,7 +315,7 @@ public final class TextSelectionNode: ASDisplayNode { strongSelf.currentRange = resultRange.flatMap { ($0.lowerBound, $0.upperBound) } - strongSelf.updateSelection(range: resultRange) + strongSelf.updateSelection(range: resultRange, animateIn: true) strongSelf.displayMenu() strongSelf.updateIsActive(true) } @@ -322,7 +326,7 @@ public final class TextSelectionNode: ASDisplayNode { self.view.addGestureRecognizer(recognizer) } - private func updateSelection(range: NSRange?) { + private func updateSelection(range: NSRange?, animateIn: Bool) { var rects: [CGRect]? if let range = range { @@ -342,7 +346,7 @@ public final class TextSelectionNode: ASDisplayNode { highlightOverlay.outerRadius = 0.0 highlightOverlay.inset = 1.0 self.highlightOverlay = highlightOverlay - self.insertSubnode(highlightOverlay, at: 0) + self.highlightAreaNode.addSubnode(highlightOverlay) } highlightOverlay.frame = self.bounds highlightOverlay.updateRects(rects) @@ -351,11 +355,27 @@ public final class TextSelectionNode: ASDisplayNode { self.rightKnob.frame = CGRect(origin: CGPoint(x: floor(rects[rects.count - 1].maxX + 1.0 - image.size.width / 2.0), y: rects[rects.count - 1].maxY + 1.0 - (rects[0].height + 2.0)), size: CGSize(width: image.size.width, height: image.size.width + rects[0].height + 2.0)) } if self.leftKnob.alpha.isZero { - highlightOverlay.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18) + highlightOverlay.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) self.leftKnob.alpha = 1.0 - self.leftKnob.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18) + self.leftKnob.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.14, delay: 0.19) self.rightKnob.alpha = 1.0 - self.rightKnob.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18) + self.rightKnob.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.14, delay: 0.19) + self.leftKnob.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.2, delay: 0.25, initialVelocity: 0.0, damping: 80.0) + self.rightKnob.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.2, delay: 0.25, initialVelocity: 0.0, damping: 80.0) + + if animateIn { + var result = CGRect() + for rect in rects { + if result.isEmpty { + result = rect + } else { + result = result.union(rect) + } + } + highlightOverlay.layer.animateScale(from: 2.0, to: 1.0, duration: 0.26) + let fromResult = CGRect(origin: CGPoint(x: result.minX - result.width / 2.0, y: result.minY - result.height / 2.0), size: CGSize(width: result.width * 2.0, height: result.height * 2.0)) + highlightOverlay.layer.animatePosition(from: CGPoint(x: (-fromResult.midX + highlightOverlay.bounds.midX) / 1.0, y: (-fromResult.midY + highlightOverlay.bounds.midY) / 1.0), to: CGPoint(), duration: 0.26, additive: true) + } } } else if let highlightOverlay = self.highlightOverlay { self.highlightOverlay = nil @@ -372,7 +392,7 @@ public final class TextSelectionNode: ASDisplayNode { private func dismissSelection() { self.currentRange = nil - self.updateSelection(range: nil) + self.updateSelection(range: nil, animateIn: false) } private func displayMenu() {