Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
@ -1122,7 +1122,7 @@ ios_extension(
|
||||
":VersionInfoPlist",
|
||||
":AppNameInfoPlist",
|
||||
],
|
||||
minimum_os_version = "9.0",
|
||||
minimum_os_version = "10.0",
|
||||
provisioning_profile = "//build-input/data/provisioning-profiles:NotificationContent.mobileprovision",
|
||||
deps = [":NotificationContentExtensionLib"],
|
||||
frameworks = [
|
||||
@ -1263,7 +1263,7 @@ ios_extension(
|
||||
":VersionInfoPlist",
|
||||
":AppNameInfoPlist",
|
||||
],
|
||||
minimum_os_version = "9.0",
|
||||
minimum_os_version = "10.0",
|
||||
provisioning_profile = "//build-input/data/provisioning-profiles:Intents.mobileprovision",
|
||||
deps = [":IntentsExtensionLib"],
|
||||
frameworks = [
|
||||
@ -1519,7 +1519,6 @@ ios_application(
|
||||
":MtProtoKitFramework",
|
||||
":SwiftSignalKitFramework",
|
||||
":PostboxFramework",
|
||||
#":TelegramApiFramework",
|
||||
":SyncCoreFramework",
|
||||
":TelegramCoreFramework",
|
||||
":AsyncDisplayKitFramework",
|
||||
|
BIN
Telegram/Telegram-iOS/Resources/voip_group_connecting.mp3
Normal file
BIN
Telegram/Telegram-iOS/Resources/voip_group_joined.mp3
Normal file
BIN
Telegram/Telegram-iOS/Resources/voip_group_left.mp3
Normal file
@ -5962,7 +5962,7 @@ Sorry for the inconvenience.";
|
||||
"VoiceChat.InvitedPeerText" = "You invited %@ to the voice chat";
|
||||
"VoiceChat.RemovedPeerText" = "You removed %@ from this group";
|
||||
|
||||
"Notification.VoiceChatStarted" = "Voice chat started";
|
||||
"Notification.VoiceChatStarted" = "%1$@ started a voice chat";
|
||||
"Notification.VoiceChatEnded" = "Voice chat ended (%@)";
|
||||
|
||||
"VoiceChat.Panel.TapToJoin" = "Tap to join";
|
||||
@ -5993,6 +5993,9 @@ Sorry for the inconvenience.";
|
||||
"VoiceChat.InviteMemberToGroupFirstText" = "%1$@ isn't a member of \"%2$@\" yet. Add them to the group?";
|
||||
"VoiceChat.InviteMemberToGroupFirstAdd" = "Add";
|
||||
|
||||
"VoiceChat.CreateNewVoiceChatText" = "Voice chat ended. Start a new one?";
|
||||
"VoiceChat.CreateNewVoiceChatStart" = "Start";
|
||||
|
||||
"CHAT_VOICECHAT_START" = "%1$@ has started voice chat in the group %2$@";
|
||||
"CHAT_VOICECHAT_INVITE" = "%1$@ has invited %3$@ in the group %2$@";
|
||||
"CHAT_VOICECHAT_INVITE_YOU" = "%1$@ has invited you to voice chat in the group %2$@";
|
||||
@ -6021,3 +6024,5 @@ Sorry for the inconvenience.";
|
||||
"Channel.AdminLog.MutedNewMembers" = "%1$@ muted new members";
|
||||
|
||||
"Group.GroupMembersHeader" = "GROUP MEMBERS";
|
||||
|
||||
"Conversation.VoiceChatMediaRecordingRestricted" = "You can't record voice and video messages during a voice chat.";
|
||||
|
@ -5,7 +5,7 @@ set -e
|
||||
BUILD_TELEGRAM_VERSION="1"
|
||||
|
||||
MACOS_VERSION="10.15"
|
||||
XCODE_VERSION="12.1"
|
||||
XCODE_VERSION="12.2"
|
||||
GUEST_SHELL="bash"
|
||||
|
||||
VM_BASE_NAME="macos$(echo $MACOS_VERSION | sed -e 's/\.'/_/g)_Xcode$(echo $XCODE_VERSION | sed -e 's/\.'/_/g)"
|
||||
|
@ -474,6 +474,8 @@ public protocol ChatController: ViewController {
|
||||
func updatePresentationMode(_ mode: ChatControllerPresentationMode)
|
||||
func beginMessageSearch(_ query: String)
|
||||
func displayPromoAnnouncement(text: String)
|
||||
|
||||
var isSendButtonVisible: Bool { get }
|
||||
}
|
||||
|
||||
public protocol ChatMessagePreviewItemNode: class {
|
||||
|
@ -7,6 +7,7 @@ public protocol ChatListController: ViewController {
|
||||
var context: AccountContext { get }
|
||||
var lockViewFrame: CGRect? { get }
|
||||
|
||||
var isSearchActive: Bool { get }
|
||||
func activateSearch()
|
||||
func deactivateSearch(animated: Bool)
|
||||
func activateCompose()
|
||||
|
@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
@ -18,9 +19,9 @@ public final class GalleryControllerActionInteraction {
|
||||
public let openBotCommand: (String) -> Void
|
||||
public let addContact: (String) -> Void
|
||||
public let storeMediaPlaybackState: (MessageId, Double?) -> Void
|
||||
public let editMedia: (MessageId) -> Void
|
||||
public let editMedia: (MessageId, [UIView], @escaping () -> Void) -> Void
|
||||
|
||||
public init(openUrl: @escaping (String, Bool) -> Void, openUrlIn: @escaping (String) -> Void, openPeerMention: @escaping (String) -> Void, openPeer: @escaping (PeerId) -> Void, openHashtag: @escaping (String?, String) -> Void, openBotCommand: @escaping (String) -> Void, addContact: @escaping (String) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void, editMedia: @escaping (MessageId) -> Void) {
|
||||
public init(openUrl: @escaping (String, Bool) -> Void, openUrlIn: @escaping (String) -> Void, openPeerMention: @escaping (String) -> Void, openPeer: @escaping (PeerId) -> Void, openHashtag: @escaping (String?, String) -> Void, openBotCommand: @escaping (String) -> Void, addContact: @escaping (String) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void, editMedia: @escaping (MessageId, [UIView], @escaping () -> Void) -> Void) {
|
||||
self.openUrl = openUrl
|
||||
self.openUrlIn = openUrlIn
|
||||
self.openPeerMention = openPeerMention
|
||||
|
@ -84,9 +84,13 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
|
||||
}
|
||||
|
||||
public func setColor(_ color: UIColor) {
|
||||
smallBlob.setColor(color)
|
||||
mediumBlob.setColor(color.withAlphaComponent(0.3))
|
||||
bigBlob.setColor(color.withAlphaComponent(0.15))
|
||||
self.setColor(color, animated: false)
|
||||
}
|
||||
|
||||
public func setColor(_ color: UIColor, animated: Bool) {
|
||||
smallBlob.setColor(color, animated: animated)
|
||||
mediumBlob.setColor(color.withAlphaComponent(0.3), animated: animated)
|
||||
bigBlob.setColor(color.withAlphaComponent(0.15), animated: animated)
|
||||
}
|
||||
|
||||
public func updateLevel(_ level: CGFloat) {
|
||||
@ -250,8 +254,12 @@ final class BlobView: UIView {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setColor(_ color: UIColor) {
|
||||
func setColor(_ color: UIColor, animated: Bool) {
|
||||
let previousColor = shapeLayer.fillColor
|
||||
shapeLayer.fillColor = color.cgColor
|
||||
if animated, let previousColor = previousColor {
|
||||
shapeLayer.animate(from: previousColor, to: color.cgColor, keyPath: "fillColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3)
|
||||
}
|
||||
}
|
||||
|
||||
func updateSpeedLevel(to newSpeedLevel: CGFloat) {
|
||||
|
@ -1678,6 +1678,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
self.present(actionSheet, in: .window(.root))
|
||||
}
|
||||
|
||||
public private(set) var isSearchActive: Bool = false
|
||||
public func activateSearch() {
|
||||
if self.displayNavigationBar {
|
||||
let _ = (combineLatest(self.chatListDisplayNode.containerNode.currentItemNode.contentsReady |> take(1), self.context.account.postbox.tailChatListView(groupId: .root, count: 16, summaryComponents: ChatListEntrySummaryComponents(tagSummary: nil, actionsSummary: nil)) |> take(1))
|
||||
@ -1729,18 +1730,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
(strongSelf.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.4, curve: .spring))
|
||||
})
|
||||
|
||||
self.isSearchActive = true
|
||||
if let navigationController = self.navigationController as? NavigationController {
|
||||
var voiceChatOverlayController: VoiceChatOverlayController?
|
||||
for controller in navigationController.globalOverlayControllers {
|
||||
if let controller = controller as? VoiceChatOverlayController {
|
||||
voiceChatOverlayController = controller
|
||||
controller.updateVisibility()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let controller = voiceChatOverlayController {
|
||||
controller.update(hidden: true, slide: true, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1785,18 +1782,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
}
|
||||
|
||||
self.isSearchActive = false
|
||||
if let navigationController = self.navigationController as? NavigationController {
|
||||
var voiceChatOverlayController: VoiceChatOverlayController?
|
||||
for controller in navigationController.globalOverlayControllers {
|
||||
if let controller = controller as? VoiceChatOverlayController {
|
||||
voiceChatOverlayController = controller
|
||||
controller.updateVisibility()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let controller = voiceChatOverlayController {
|
||||
controller.update(hidden: false, slide: true, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1319,7 +1319,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
badgeSize = max(badgeSize, reorderInset)
|
||||
|
||||
let (authorLayout, authorApply) = authorLayout(TextNodeLayoutArguments(attributedString: hideAuthor ? nil : authorAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
|
||||
let (authorLayout, authorApply) = authorLayout(TextNodeLayoutArguments(attributedString: (hideAuthor && !hasDraft) ? nil : authorAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
|
||||
|
||||
var textCutout: TextNodeCutout?
|
||||
if !textLeftCutout.isZero {
|
||||
|
@ -127,7 +127,7 @@ public extension CALayer {
|
||||
self.add(animationGroup, forKey: key)
|
||||
}
|
||||
|
||||
func animateKeyframes(values: [AnyObject], duration: Double, keyPath: String, timingFunction: String = CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
func animateKeyframes(values: [AnyObject], duration: Double, keyPath: String, timingFunction: String = CAMediaTimingFunctionName.linear.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
let k = Float(UIView.animationDurationFactor())
|
||||
var speed: Float = 1.0
|
||||
if k != 0 && k != 1 {
|
||||
@ -150,7 +150,11 @@ public extension CALayer {
|
||||
animation.speed = speed
|
||||
animation.duration = duration
|
||||
animation.isAdditive = additive
|
||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName(rawValue: timingFunction))
|
||||
if let mediaTimingFunction = mediaTimingFunction {
|
||||
animation.timingFunction = mediaTimingFunction
|
||||
} else {
|
||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName(rawValue: timingFunction))
|
||||
}
|
||||
animation.isRemovedOnCompletion = removeOnCompletion
|
||||
if let completion = completion {
|
||||
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
|
||||
|
@ -477,7 +477,7 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
func updateAlpha(node: ASDisplayNode, alpha: CGFloat, beginWithCurrentState: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
func updateAlpha(node: ASDisplayNode, alpha: CGFloat, beginWithCurrentState: Bool = false, force: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
|
||||
if node.alpha.isEqual(to: alpha) && !force {
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
@ -499,7 +499,7 @@ public extension ContainedViewLayoutTransition {
|
||||
previousAlpha = node.alpha
|
||||
}
|
||||
node.alpha = alpha
|
||||
node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
|
||||
node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
@ -670,7 +670,7 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
func updateTransformScale(node: ASDisplayNode, scale: CGFloat, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
func updateTransformScale(node: ASDisplayNode, scale: CGFloat, beginWithCurrentState: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
|
||||
let t = node.layer.transform
|
||||
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
|
||||
if currentScale.isEqual(to: scale) {
|
||||
@ -695,7 +695,7 @@ public extension ContainedViewLayoutTransition {
|
||||
previousScale = currentScale
|
||||
}
|
||||
node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||
node.layer.animateScale(from: previousScale, to: scale, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
|
||||
node.layer.animateScale(from: previousScale, to: scale, duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
@ -729,7 +729,7 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
func updateSublayerTransformScale(node: ASDisplayNode, scale: CGFloat, completion: ((Bool) -> Void)? = nil) {
|
||||
func updateSublayerTransformScale(node: ASDisplayNode, scale: CGFloat, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
|
||||
if !node.isNodeLoaded {
|
||||
node.subnodeTransform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||
completion?(true)
|
||||
@ -752,7 +752,7 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||
node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
|
||||
node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: delay, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
|
||||
result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
|
@ -611,10 +611,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layout.additionalInsets.left = max(layout.intrinsicInsets.left, additionalSideInsets.left)
|
||||
layout.additionalInsets.right = max(layout.intrinsicInsets.right, additionalSideInsets.right)
|
||||
|
||||
|
||||
if self.currentTopVisibleOverlayContainerStatusBar !== topVisibleOverlayContainerWithStatusBar {
|
||||
animateStatusBarStyleTransition = true
|
||||
self.currentTopVisibleOverlayContainerStatusBar = topVisibleOverlayContainerWithStatusBar
|
||||
@ -722,6 +719,9 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
}
|
||||
|
||||
layout.additionalInsets.left = max(layout.intrinsicInsets.left, additionalSideInsets.left)
|
||||
layout.additionalInsets.right = max(layout.intrinsicInsets.right, additionalSideInsets.right)
|
||||
|
||||
switch navigationLayout.root {
|
||||
case let .flat(controllers):
|
||||
if let rootContainer = self.rootContainer {
|
||||
|
@ -94,7 +94,7 @@ final class NavigationSplitContainer: ASDisplayNode {
|
||||
transition.updateFrame(node: self.detailContainer, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: detailWidth, height: layout.size.height)))
|
||||
transition.updateFrame(node: self.separator, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height)))
|
||||
|
||||
self.masterContainer.update(layout: ContainerViewLayout(size: CGSize(width: masterWidth, height: layout.size.height), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), canBeClosed: false, controllers: masterControllers, transition: transition)
|
||||
self.masterContainer.update(layout: ContainerViewLayout(size: CGSize(width: masterWidth, height: layout.size.height), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, additionalInsets: UIEdgeInsets(), statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), canBeClosed: false, controllers: masterControllers, transition: transition)
|
||||
self.detailContainer.update(layout: ContainerViewLayout(size: CGSize(width: detailWidth, height: layout.size.height), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), canBeClosed: true, controllers: detailControllers, transition: transition)
|
||||
|
||||
var controllersUpdated = false
|
||||
|
@ -27,6 +27,7 @@ swift_library(
|
||||
"//submodules/StickerPackPreviewUI:StickerPackPreviewUI",
|
||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/UrlEscaping:UrlEscaping",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -18,6 +18,7 @@ import OpenInExternalAppUI
|
||||
import AppBundle
|
||||
import LocalizedPeerData
|
||||
import TextSelectionNode
|
||||
import UrlEscaping
|
||||
|
||||
private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: .white)
|
||||
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: .white)
|
||||
@ -331,13 +332,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
}
|
||||
return nil
|
||||
}
|
||||
self.textNode.tapAttributeAction = { [weak self] attributes, _ in
|
||||
if let strongSelf = self, let action = strongSelf.actionForAttributes(attributes) {
|
||||
self.textNode.tapAttributeAction = { [weak self] attributes, index in
|
||||
if let strongSelf = self, let action = strongSelf.actionForAttributes(attributes, index) {
|
||||
strongSelf.performAction?(action)
|
||||
}
|
||||
}
|
||||
self.textNode.longTapAttributeAction = { [weak self] attributes, _ in
|
||||
if let strongSelf = self, let action = strongSelf.actionForAttributes(attributes) {
|
||||
self.textNode.longTapAttributeAction = { [weak self] attributes, index in
|
||||
if let strongSelf = self, let action = strongSelf.actionForAttributes(attributes, index) {
|
||||
strongSelf.openActionOptions?(action)
|
||||
}
|
||||
}
|
||||
@ -391,9 +392,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.scrollNode.view.showsVerticalScrollIndicator = false
|
||||
}
|
||||
|
||||
private func actionForAttributes(_ attributes: [NSAttributedString.Key: Any]) -> GalleryControllerInteractionTapAction? {
|
||||
private func actionForAttributes(_ attributes: [NSAttributedString.Key: Any], _ index: Int) -> GalleryControllerInteractionTapAction? {
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
return .url(url: url, concealed: false)
|
||||
var concealed = true
|
||||
if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed)
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
return .peerMention(peerMention.peerId, peerMention.mention)
|
||||
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
|
@ -913,8 +913,19 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
}
|
||||
}, editMedia: { [weak self] messageId in
|
||||
if let strongSelf = self {
|
||||
strongSelf.dismiss(forceAway: true)
|
||||
strongSelf.actionInteraction?.editMedia(messageId)
|
||||
var snapshots: [UIView] = []
|
||||
if let navigationBar = strongSelf.navigationBar, let snapshotView = navigationBar.view.snapshotContentTree() {
|
||||
snapshotView.frame = navigationBar.frame
|
||||
snapshots.append(snapshotView)
|
||||
}
|
||||
if let snapshotView = strongSelf.galleryNode.footerNode.view.snapshotContentTree() {
|
||||
snapshotView.frame = strongSelf.galleryNode.footerNode.frame
|
||||
snapshots.append(snapshotView)
|
||||
}
|
||||
|
||||
strongSelf.actionInteraction?.editMedia(messageId, snapshots, { [weak self] in
|
||||
self?.dismiss(forceAway: true)
|
||||
})
|
||||
}
|
||||
})
|
||||
self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction)
|
||||
|
@ -19,6 +19,7 @@ swift_library(
|
||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -12,6 +12,7 @@ import PresentationDataUtils
|
||||
import StickerResources
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import ShimmerEffect
|
||||
|
||||
public struct ItemListStickerPackItemEditing: Equatable {
|
||||
public var editable: Bool
|
||||
@ -149,6 +150,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
fileprivate let imageNode: TransformImageNode
|
||||
private var animationNode: AnimatedStickerNode?
|
||||
private var placeholderNode: StickerShimmerEffectNode?
|
||||
private let unreadNode: ASImageNode
|
||||
private let titleNode: TextNode
|
||||
private let statusNode: TextNode
|
||||
@ -200,6 +202,9 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
self.imageNode = TransformImageNode()
|
||||
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
|
||||
self.placeholderNode = StickerShimmerEffectNode()
|
||||
self.placeholderNode?.isUserInteractionEnabled = false
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.contentMode = .left
|
||||
@ -231,7 +236,11 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.addSubnode(placeholderNode)
|
||||
}
|
||||
self.addSubnode(self.imageNode)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.statusNode)
|
||||
self.addSubnode(self.unreadNode)
|
||||
@ -251,12 +260,50 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var firstTime = true
|
||||
self.imageNode.imageUpdated = { [weak self] image in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if image != nil {
|
||||
strongSelf.removePlaceholder(animated: !firstTime)
|
||||
if firstTime {
|
||||
strongSelf.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
firstTime = false
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.fetchDisposable.dispose()
|
||||
}
|
||||
|
||||
private func removePlaceholder(animated: Bool) {
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.placeholderNode = nil
|
||||
if !animated {
|
||||
placeholderNode.removeFromSupernode()
|
||||
} else {
|
||||
placeholderNode.allowsGroupOpacity = true
|
||||
placeholderNode.alpha = 0.0
|
||||
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in
|
||||
placeholderNode?.removeFromSupernode()
|
||||
placeholderNode?.allowsGroupOpacity = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var absoluteLocation: (CGRect, CGSize)?
|
||||
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
self.absoluteLocation = (rect, containerSize)
|
||||
if let placeholderNode = placeholderNode {
|
||||
placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + placeholderNode.frame.minX, y: rect.minY + placeholderNode.frame.minY), size: placeholderNode.frame.size), within: containerSize)
|
||||
}
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: ItemListStickerPackItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
let makeImageLayout = self.imageNode.asyncLayout()
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
@ -397,14 +444,14 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
if fileUpdated {
|
||||
imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: stillImageSize, boundingSize: stillImageSize, intrinsicInsets: UIEdgeInsets()))
|
||||
updatedImageSignal = chatMessageStickerPackThumbnail(postbox: item.account.postbox, resource: representation.resource)
|
||||
updatedImageSignal = chatMessageStickerPackThumbnail(postbox: item.account.postbox, resource: representation.resource, nilIfEmpty: true)
|
||||
}
|
||||
case let .animated(resource):
|
||||
imageSize = imageBoundingSize
|
||||
|
||||
if fileUpdated {
|
||||
imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageBoundingSize, boundingSize: imageBoundingSize, intrinsicInsets: UIEdgeInsets()))
|
||||
updatedImageSignal = chatMessageStickerPackThumbnail(postbox: item.account.postbox, resource: resource, animated: true)
|
||||
updatedImageSignal = chatMessageStickerPackThumbnail(postbox: item.account.postbox, resource: resource, animated: true, nilIfEmpty: true)
|
||||
}
|
||||
}
|
||||
if fileUpdated, let resourceReference = resourceReference {
|
||||
@ -610,6 +657,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
animationNode = AnimatedStickerNode()
|
||||
strongSelf.animationNode = animationNode
|
||||
strongSelf.addSubnode(animationNode)
|
||||
|
||||
animationNode.setup(source: AnimatedStickerResourceSource(account: item.account, resource: resource), width: 80, height: 80, mode: .cached)
|
||||
}
|
||||
animationNode.visibility = strongSelf.visibility != .none && item.playAnimatedStickers
|
||||
@ -619,6 +667,12 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
transition.updateFrame(node: animationNode, frame: imageFrame)
|
||||
}
|
||||
}
|
||||
|
||||
if let placeholderNode = strongSelf.placeholderNode {
|
||||
placeholderNode.frame = imageFrame
|
||||
|
||||
placeholderNode.update(backgroundColor: nil, foregroundColor: item.presentationData.theme.list.disclosureArrowColor.blitOver(item.presentationData.theme.list.itemBlocksBackgroundColor, alpha: 0.55), shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), data: item.packInfo.immediateThumbnailData, size: imageFrame.size, small: true)
|
||||
}
|
||||
}
|
||||
|
||||
if let updatedImageSignal = updatedImageSignal {
|
||||
|
@ -19,7 +19,8 @@ typedef enum {
|
||||
TGCameraControllerPassportIdIntent,
|
||||
TGCameraControllerPassportMultipleIntent,
|
||||
TGCameraControllerAvatarIntent,
|
||||
TGCameraControllerSignupAvatarIntent
|
||||
TGCameraControllerSignupAvatarIntent,
|
||||
TGCameraControllerGenericPhotoOnlyIntent
|
||||
} TGCameraControllerIntent;
|
||||
|
||||
@interface TGCameraControllerWindow : TGOverlayControllerWindow
|
||||
|
@ -51,6 +51,7 @@
|
||||
- (void)setAllInterfaceHidden:(bool)hidden delay:(NSTimeInterval)__unused delay animated:(bool)animated;
|
||||
- (void)setToolbarsHidden:(bool)hidden animated:(bool)animated;
|
||||
|
||||
- (void)immediateEditorTransitionIn;
|
||||
- (void)editorTransitionIn;
|
||||
- (void)editorTransitionOut;
|
||||
|
||||
|
@ -51,5 +51,6 @@
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context items:(NSArray *)items focusItem:(id<TGModernGalleryItem>)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions hasSelectionPanel:(bool)hasSelectionPanel hasCamera:(bool)hasCamera recipientName:(NSString *)recipientName;
|
||||
|
||||
- (void)presentPhotoEditorForItem:(id<TGModernGalleryEditableItem>)item tab:(TGPhotoEditorTab)tab;
|
||||
- (void)presentPhotoEditorForItem:(id<TGModernGalleryEditableItem>)item tab:(TGPhotoEditorTab)tab snapshots:(NSArray *)snapshots;
|
||||
|
||||
@end
|
||||
|
@ -4,6 +4,6 @@
|
||||
|
||||
+ (void)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo dismissed:(void (^)(void))dismissed;
|
||||
|
||||
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item paint:(bool)paint recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed;
|
||||
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item paint:(bool)paint recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed;
|
||||
|
||||
@end
|
||||
|
@ -1250,7 +1250,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
||||
}
|
||||
}];
|
||||
|
||||
bool hasCamera = !self.inhibitMultipleCapture && ((_intent == TGCameraControllerGenericIntent && !_shortcut) || (_intent == TGCameraControllerPassportMultipleIntent));
|
||||
bool hasCamera = !self.inhibitMultipleCapture && (((_intent == TGCameraControllerGenericIntent || _intent == TGCameraControllerGenericPhotoOnlyIntent) && !_shortcut) || (_intent == TGCameraControllerPassportMultipleIntent));
|
||||
TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:windowContext items:galleryItems focusItem:focusItem selectionContext:_items.count > 1 ? selectionContext : nil editingContext:editingContext hasCaptions:self.allowCaptions allowCaptionEntities:self.allowCaptionEntities hasTimer:self.hasTimer onlyCrop:_intent == TGCameraControllerPassportIntent || _intent == TGCameraControllerPassportIdIntent || _intent == TGCameraControllerPassportMultipleIntent inhibitDocumentCaptions:self.inhibitDocumentCaptions hasSelectionPanel:true hasCamera:hasCamera recipientName:self.recipientName];
|
||||
model.inhibitMute = self.inhibitMute;
|
||||
model.controller = galleryController;
|
||||
|
@ -110,7 +110,18 @@
|
||||
CGFloat shutterButtonWidth = 66.0f;
|
||||
CGSize screenSize = TGScreenSize();
|
||||
CGFloat widescreenWidth = MAX(screenSize.width, screenSize.height);
|
||||
if (widescreenWidth == 896.0f)
|
||||
if (widescreenWidth == 926.0f)
|
||||
{
|
||||
_topPanelOffset = 77.0f;
|
||||
_topPanelHeight = 77.0f;
|
||||
_bottomPanelOffset = 94.0f;
|
||||
_bottomPanelHeight = 155.0f;
|
||||
_modeControlOffset = 6.0f;
|
||||
_modeControlHeight = 66.0f;
|
||||
_counterOffset = 7.0f;
|
||||
shutterButtonWidth = 72.0f;
|
||||
}
|
||||
else if (widescreenWidth == 896.0f)
|
||||
{
|
||||
_topPanelOffset = 33.0f;
|
||||
_topPanelHeight = 44.0f;
|
||||
@ -121,6 +132,17 @@
|
||||
_counterOffset = 7.0f;
|
||||
shutterButtonWidth = 72.0f;
|
||||
}
|
||||
if (widescreenWidth == 844.0f)
|
||||
{
|
||||
_topPanelOffset = 33.0f;
|
||||
_topPanelHeight = 44.0f;
|
||||
_bottomPanelOffset = 63.0f;
|
||||
_bottomPanelHeight = 123.0f;
|
||||
_modeControlOffset = 3.0f;
|
||||
_modeControlHeight = 40.0f;
|
||||
_counterOffset = 7.0f;
|
||||
shutterButtonWidth = 70.0f;
|
||||
}
|
||||
else if (widescreenWidth == 812.0f)
|
||||
{
|
||||
_topPanelOffset = 33.0f;
|
||||
|
@ -1307,6 +1307,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)immediateEditorTransitionIn {
|
||||
[self setSelectionInterfaceHidden:true animated:false];
|
||||
_captionMixin.inputPanel.alpha = 0.0f;
|
||||
_portraitToolbarView.doneButton.alpha = 0.0f;
|
||||
_landscapeToolbarView.doneButton.alpha = 0.0f;
|
||||
|
||||
_portraitToolbarView.hidden = true;
|
||||
_landscapeToolbarView.hidden = true;
|
||||
|
||||
TGDispatchAfter(0.5, dispatch_get_main_queue(), ^
|
||||
{
|
||||
_portraitToolbarView.hidden = false;
|
||||
_landscapeToolbarView.hidden = false;
|
||||
});
|
||||
}
|
||||
|
||||
- (void)editorTransitionIn
|
||||
{
|
||||
[self setSelectionInterfaceHidden:true animated:true];
|
||||
|
@ -344,6 +344,11 @@
|
||||
}
|
||||
|
||||
- (void)presentPhotoEditorForItem:(id<TGModernGalleryEditableItem>)item tab:(TGPhotoEditorTab)tab
|
||||
{
|
||||
[self presentPhotoEditorForItem:item tab:tab snapshots:@[]];
|
||||
}
|
||||
|
||||
- (void)presentPhotoEditorForItem:(id<TGModernGalleryEditableItem>)item tab:(TGPhotoEditorTab)tab snapshots:(NSArray *)snapshots
|
||||
{
|
||||
__weak TGMediaPickerGalleryModel *weakSelf = self;
|
||||
|
||||
@ -604,6 +609,15 @@
|
||||
|
||||
[self.controller addChildViewController:controller];
|
||||
[self.controller.view addSubview:controller.view];
|
||||
|
||||
for (UIView *view in snapshots) {
|
||||
[self.controller.view addSubview:view];
|
||||
[UIView animateWithDuration:0.3 animations:^{
|
||||
view.alpha = 0.0;
|
||||
} completion:^(__unused BOOL finished) {
|
||||
[view removeFromSuperview];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_replaceItems:(NSArray *)items focusingOnItem:(id<TGModernGalleryItem>)item
|
||||
|
@ -102,7 +102,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item paint:(bool)paint recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed
|
||||
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item paint:(bool)paint recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed
|
||||
{
|
||||
id<LegacyComponentsOverlayWindowManager> windowManager = [context makeOverlayWindowManager];
|
||||
id<LegacyComponentsContext> windowContext = [windowManager context];
|
||||
@ -112,6 +112,10 @@
|
||||
|
||||
TGModernGalleryController *galleryController = [[TGModernGalleryController alloc] initWithContext:windowContext];
|
||||
galleryController.adjustsStatusBarVisibility = true;
|
||||
galleryController.animateTransition = !immediate;
|
||||
galleryController.finishedTransitionIn = ^(id<TGModernGalleryItem> item, TGModernGalleryItemView *itemView) {
|
||||
appeared();
|
||||
};
|
||||
//galleryController.hasFadeOutTransition = true;
|
||||
|
||||
id<TGModernGalleryEditableItem> galleryItem = nil;
|
||||
@ -199,14 +203,20 @@
|
||||
dismissed();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
[model.interfaceView immediateEditorTransitionIn];
|
||||
|
||||
for (UIView *view in snapshots) {
|
||||
[galleryController.view addSubview:view];
|
||||
}
|
||||
|
||||
TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:controller contentController:galleryController];
|
||||
controllerWindow.hidden = false;
|
||||
galleryController.view.clipsToBounds = true;
|
||||
|
||||
if (paint) {
|
||||
TGDispatchAfter(0.05, dispatch_get_main_queue(), ^{
|
||||
[model presentPhotoEditorForItem:galleryItem tab:TGPhotoEditorPaintTab];
|
||||
[model presentPhotoEditorForItem:galleryItem tab:TGPhotoEditorPaintTab snapshots:snapshots];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ public enum LegacyAttachmentMenuMediaEditing {
|
||||
case file
|
||||
}
|
||||
|
||||
public func legacyMediaEditor(context: AccountContext, peer: Peer, media: AnyMediaReference, initialCaption: String, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||
public func legacyMediaEditor(context: AccountContext, peer: Peer, media: AnyMediaReference, initialCaption: String, snapshots: [UIView], transitionCompletion: (() -> Void)?, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||
let _ = (fetchMediaData(context: context, postbox: context.account.postbox, mediaReference: media)
|
||||
|> deliverOnMainQueue).start(next: { (value, isImage) in
|
||||
guard case let .data(data) = value, data.complete else {
|
||||
@ -103,8 +103,9 @@ public func legacyMediaEditor(context: AccountContext, peer: Peer, media: AnyMed
|
||||
|
||||
present(legacyController, nil)
|
||||
|
||||
TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: initialCaption, entities: [], withItem: item, paint: true, recipientName: recipientName, stickersContext: paintStickersContext, completion: { result, editingContext in
|
||||
let intent: TGMediaAssetsControllerIntent = TGMediaAssetsControllerSendMediaIntent
|
||||
TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: initialCaption, entities: [], withItem: item, paint: true, recipientName: recipientName, stickersContext: paintStickersContext, snapshots: snapshots as? [Any], immediate: transitionCompletion != nil, appeared: {
|
||||
transitionCompletion?()
|
||||
}, completion: { result, editingContext in
|
||||
let signals = TGCameraController.resultSignals(for: nil, editingContext: editingContext, currentItem: result as! TGMediaSelectableItem, storeAssets: false, saveEditedPhotos: false, descriptionGenerator: legacyAssetPickerItemGenerator())
|
||||
sendMessagesWithSignals(signals, false, 0)
|
||||
}, dismissed: { [weak legacyController] in
|
||||
@ -294,16 +295,8 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocati
|
||||
navigationController.setNavigationBarHidden(true, animated: false)
|
||||
legacyController.bind(controller: navigationController)
|
||||
|
||||
var hasTimer = false
|
||||
var hasSilentPosting = false
|
||||
if peer.id != context.account.peerId {
|
||||
if peer is TelegramUser {
|
||||
hasTimer = true
|
||||
}
|
||||
hasSilentPosting = true
|
||||
}
|
||||
let recipientName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
|
||||
|
||||
legacyController.enableSizeClassSignal = true
|
||||
|
||||
let presentationDisposable = context.sharedContext.presentationData.start(next: { [weak legacyController] presentationData in
|
||||
@ -315,8 +308,8 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocati
|
||||
|
||||
present(legacyController, nil)
|
||||
|
||||
TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: "", entities: [], withItem: item, paint: false, recipientName: recipientName, stickersContext: paintStickersContext, completion: { result, editingContext in
|
||||
let intent: TGMediaAssetsControllerIntent = TGMediaAssetsControllerSendMediaIntent
|
||||
TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: "", entities: [], withItem: item, paint: false, recipientName: recipientName, stickersContext: paintStickersContext, snapshots: [], immediate: false, appeared: {
|
||||
}, completion: { result, editingContext in
|
||||
let signals = TGCameraController.resultSignals(for: nil, editingContext: editingContext, currentItem: result as! TGMediaSelectableItem, storeAssets: false, saveEditedPhotos: false, descriptionGenerator: legacyAssetPickerItemGenerator())
|
||||
sendMessagesWithSignals(signals, false, 0)
|
||||
}, dismissed: { [weak legacyController] in
|
||||
|
@ -218,6 +218,7 @@ private final class AudioPlayerRendererContext {
|
||||
let lowWaterSizeInSeconds: Int = 2
|
||||
|
||||
let audioSession: MediaPlayerAudioSessionControl
|
||||
let useVoiceProcessingMode: Bool
|
||||
let controlTimebase: CMTimebase
|
||||
let updatedRate: () -> Void
|
||||
let audioPaused: () -> Void
|
||||
@ -250,7 +251,7 @@ private final class AudioPlayerRendererContext {
|
||||
}
|
||||
}
|
||||
|
||||
init(controlTimebase: CMTimebase, audioSession: MediaPlayerAudioSessionControl, playAndRecord: Bool, ambient: Bool, forceAudioToSpeaker: Bool, baseRate: Double, audioLevelPipe: ValuePipe<Float>, updatedRate: @escaping () -> Void, audioPaused: @escaping () -> Void) {
|
||||
init(controlTimebase: CMTimebase, audioSession: MediaPlayerAudioSessionControl, playAndRecord: Bool, useVoiceProcessingMode: Bool, ambient: Bool, forceAudioToSpeaker: Bool, baseRate: Double, audioLevelPipe: ValuePipe<Float>, updatedRate: @escaping () -> Void, audioPaused: @escaping () -> Void) {
|
||||
assert(audioPlayerRendererQueue.isCurrent())
|
||||
|
||||
self.audioSession = audioSession
|
||||
@ -263,6 +264,7 @@ private final class AudioPlayerRendererContext {
|
||||
self.audioPaused = audioPaused
|
||||
|
||||
self.playAndRecord = playAndRecord
|
||||
self.useVoiceProcessingMode = useVoiceProcessingMode
|
||||
self.ambient = ambient
|
||||
|
||||
self.audioStreamDescription = audioRendererNativeStreamDescription()
|
||||
@ -407,7 +409,11 @@ private final class AudioPlayerRendererContext {
|
||||
var outputNode: AUNode = 0
|
||||
var outputDesc = AudioComponentDescription()
|
||||
outputDesc.componentType = kAudioUnitType_Output
|
||||
outputDesc.componentSubType = kAudioUnitSubType_RemoteIO
|
||||
if self.useVoiceProcessingMode {
|
||||
outputDesc.componentSubType = kAudioUnitSubType_VoiceProcessingIO
|
||||
} else {
|
||||
outputDesc.componentSubType = kAudioUnitSubType_RemoteIO
|
||||
}
|
||||
outputDesc.componentFlags = 0
|
||||
outputDesc.componentFlagsMask = 0
|
||||
outputDesc.componentManufacturer = kAudioUnitManufacturer_Apple
|
||||
@ -753,7 +759,7 @@ public final class MediaPlayerAudioRenderer {
|
||||
private let audioClock: CMClock
|
||||
public let audioTimebase: CMTimebase
|
||||
|
||||
public init(audioSession: MediaPlayerAudioSessionControl, playAndRecord: Bool, ambient: Bool, forceAudioToSpeaker: Bool, baseRate: Double, audioLevelPipe: ValuePipe<Float>, updatedRate: @escaping () -> Void, audioPaused: @escaping () -> Void) {
|
||||
public init(audioSession: MediaPlayerAudioSessionControl, playAndRecord: Bool, useVoiceProcessingMode: Bool = false, ambient: Bool, forceAudioToSpeaker: Bool, baseRate: Double, audioLevelPipe: ValuePipe<Float>, updatedRate: @escaping () -> Void, audioPaused: @escaping () -> Void) {
|
||||
var audioClock: CMClock?
|
||||
CMAudioClockCreate(allocator: nil, clockOut: &audioClock)
|
||||
if audioClock == nil {
|
||||
@ -766,7 +772,7 @@ public final class MediaPlayerAudioRenderer {
|
||||
self.audioTimebase = audioTimebase!
|
||||
|
||||
audioPlayerRendererQueue.async {
|
||||
let context = AudioPlayerRendererContext(controlTimebase: audioTimebase!, audioSession: audioSession, playAndRecord: playAndRecord, ambient: ambient, forceAudioToSpeaker: forceAudioToSpeaker, baseRate: baseRate, audioLevelPipe: audioLevelPipe, updatedRate: updatedRate, audioPaused: audioPaused)
|
||||
let context = AudioPlayerRendererContext(controlTimebase: audioTimebase!, audioSession: audioSession, playAndRecord: playAndRecord, useVoiceProcessingMode: useVoiceProcessingMode, ambient: ambient, forceAudioToSpeaker: forceAudioToSpeaker, baseRate: baseRate, audioLevelPipe: audioLevelPipe, updatedRate: updatedRate, audioPaused: audioPaused)
|
||||
self.contextRef = Unmanaged.passRetained(context)
|
||||
}
|
||||
}
|
||||
|
@ -263,6 +263,16 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
}
|
||||
var entries: [ChannelMembersSearchEntry] = []
|
||||
|
||||
if case .inviteToCall = mode, !filters.contains(where: { filter in
|
||||
if case .excludeNonMembers = filter {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
entries.append(.copyInviteLink)
|
||||
}
|
||||
|
||||
var index = 0
|
||||
for participant in participants.participants {
|
||||
guard let peer = peerView.peers[participant.peerId] else {
|
||||
@ -484,7 +494,13 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
index += 1
|
||||
}
|
||||
|
||||
if case .inviteToCall = mode {
|
||||
if case .inviteToCall = mode, !filters.contains(where: { filter in
|
||||
if case .excludeNonMembers = filter {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
for peer in contactsView.peers {
|
||||
entries.append(ChannelMembersSearchEntry.contact(index, peer, contactsView.peerPresences[peer.id] as? TelegramUserPresence))
|
||||
index += 1
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
public func useSpecialTabBarIcons() -> Bool {
|
||||
return (Date(timeIntervalSince1970: 1581638400)...Date(timeIntervalSince1970: 1581724799)).contains(Date())
|
||||
return (Date(timeIntervalSince1970: 1608800400)...Date(timeIntervalSince1970: 1609545600)).contains(Date())
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ objc_library(
|
||||
"MobileCoreServices",
|
||||
"AddressBook",
|
||||
"AVFoundation",
|
||||
],
|
||||
weak_sdk_frameworks = [
|
||||
"PassKit",
|
||||
],
|
||||
visibility = [
|
||||
|
@ -168,7 +168,7 @@ public class StickerShimmerEffectNode: ASDisplayNode {
|
||||
self.effectNode.updateAbsoluteRect(rect, within: containerSize)
|
||||
}
|
||||
|
||||
public func update(backgroundColor: UIColor?, foregroundColor: UIColor, shimmeringColor: UIColor, data: Data?, size: CGSize) {
|
||||
public func update(backgroundColor: UIColor?, foregroundColor: UIColor, shimmeringColor: UIColor, data: Data?, size: CGSize, small: Bool = false) {
|
||||
if data == nil {
|
||||
return
|
||||
}
|
||||
@ -205,7 +205,7 @@ public class StickerShimmerEffectNode: ASDisplayNode {
|
||||
let reader = PathDataReader(input: path)
|
||||
let segments = reader.read()
|
||||
|
||||
let scale = size.width / 512.0
|
||||
let scale = size.width / (small ? 100.0 : 512.0)
|
||||
context.scaleBy(x: scale, y: scale)
|
||||
renderPath(segments, context: context)
|
||||
} else {
|
||||
|
@ -402,6 +402,10 @@ public func chatMessageSticker(postbox: Postbox, file: TelegramMediaFile, small:
|
||||
return nil
|
||||
}
|
||||
|
||||
if file.immediateThumbnailData != nil && fullSizeData == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil)
|
||||
|
||||
let drawingRect = arguments.drawingRect
|
||||
|
3
submodules/Stripe/BUILD
vendored
@ -19,6 +19,9 @@ objc_library(
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
"UIKit",
|
||||
"AddressBook",
|
||||
],
|
||||
weak_sdk_frameworks = [
|
||||
"PassKit",
|
||||
],
|
||||
visibility = [
|
||||
|
@ -55,6 +55,8 @@ public final class CachedGroupData: CachedPeerData {
|
||||
public let messageIds: Set<MessageId>
|
||||
public let associatedHistoryMessageId: MessageId? = nil
|
||||
|
||||
public let activeCall: CachedChannelData.ActiveCall?
|
||||
|
||||
public init() {
|
||||
self.participants = nil
|
||||
self.exportedInvitation = nil
|
||||
@ -68,9 +70,11 @@ public final class CachedGroupData: CachedPeerData {
|
||||
self.hasScheduledMessages = false
|
||||
self.invitedBy = nil
|
||||
self.photo = nil
|
||||
|
||||
self.activeCall = nil
|
||||
}
|
||||
|
||||
public init(participants: CachedGroupParticipants?, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, about: String?, flags: CachedGroupFlags, hasScheduledMessages: Bool, invitedBy: PeerId?, photo: TelegramMediaImage?) {
|
||||
public init(participants: CachedGroupParticipants?, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, about: String?, flags: CachedGroupFlags, hasScheduledMessages: Bool, invitedBy: PeerId?, photo: TelegramMediaImage?, activeCall: CachedChannelData.ActiveCall?) {
|
||||
self.participants = participants
|
||||
self.exportedInvitation = exportedInvitation
|
||||
self.botInfos = botInfos
|
||||
@ -81,6 +85,7 @@ public final class CachedGroupData: CachedPeerData {
|
||||
self.hasScheduledMessages = hasScheduledMessages
|
||||
self.invitedBy = invitedBy
|
||||
self.photo = photo
|
||||
self.activeCall = activeCall
|
||||
|
||||
var messageIds = Set<MessageId>()
|
||||
if let pinnedMessageId = self.pinnedMessageId {
|
||||
@ -132,6 +137,12 @@ public final class CachedGroupData: CachedPeerData {
|
||||
self.photo = nil
|
||||
}
|
||||
|
||||
if let activeCall = decoder.decodeObjectForKey("activeCall", decoder: { CachedChannelData.ActiveCall(decoder: $0) }) as? CachedChannelData.ActiveCall {
|
||||
self.activeCall = activeCall
|
||||
} else {
|
||||
self.activeCall = nil
|
||||
}
|
||||
|
||||
var messageIds = Set<MessageId>()
|
||||
if let pinnedMessageId = self.pinnedMessageId {
|
||||
messageIds.insert(pinnedMessageId)
|
||||
@ -196,6 +207,12 @@ public final class CachedGroupData: CachedPeerData {
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "ph")
|
||||
}
|
||||
|
||||
if let activeCall = self.activeCall {
|
||||
encoder.encodeObject(activeCall, forKey: "activeCall")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "activeCall")
|
||||
}
|
||||
}
|
||||
|
||||
public func isEqual(to: CachedPeerData) -> Bool {
|
||||
@ -203,46 +220,54 @@ public final class CachedGroupData: CachedPeerData {
|
||||
return false
|
||||
}
|
||||
|
||||
if self.activeCall != other.activeCall {
|
||||
return false
|
||||
}
|
||||
|
||||
return self.participants == other.participants && self.exportedInvitation == other.exportedInvitation && self.botInfos == other.botInfos && self.peerStatusSettings == other.peerStatusSettings && self.pinnedMessageId == other.pinnedMessageId && self.about == other.about && self.flags == other.flags && self.hasScheduledMessages == other.hasScheduledMessages && self.invitedBy == other.invitedBy
|
||||
}
|
||||
|
||||
public func withUpdatedParticipants(_ participants: CachedGroupParticipants?) -> CachedGroupData {
|
||||
return CachedGroupData(participants: participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo)
|
||||
return CachedGroupData(participants: participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
|
||||
}
|
||||
|
||||
public func withUpdatedExportedInvitation(_ exportedInvitation: ExportedInvitation?) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo)
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
|
||||
}
|
||||
|
||||
public func withUpdatedBotInfos(_ botInfos: [CachedPeerBotInfo]) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo)
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
|
||||
}
|
||||
|
||||
public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings?) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo)
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
|
||||
}
|
||||
|
||||
public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo)
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
|
||||
}
|
||||
|
||||
public func withUpdatedAbout(_ about: String?) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo)
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
|
||||
}
|
||||
|
||||
public func withUpdatedFlags(_ flags: CachedGroupFlags) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo)
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
|
||||
}
|
||||
|
||||
public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo)
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
|
||||
}
|
||||
|
||||
public func withUpdatedInvitedBy(_ invitedBy: PeerId?) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: invitedBy, photo: self.photo)
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: invitedBy, photo: self.photo, activeCall: self.activeCall)
|
||||
}
|
||||
|
||||
public func withUpdatedPhoto(_ photo: TelegramMediaImage?) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: photo)
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: photo, activeCall: self.activeCall)
|
||||
}
|
||||
|
||||
public func withUpdatedActiveCall(_ activeCall: CachedChannelData.ActiveCall?) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: activeCall)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
|
||||
public class EmojiSearchQueryMessageAttribute: MessageAttribute {
|
||||
public let query: String
|
||||
|
||||
public var associatedMessageIds: [MessageId] = []
|
||||
|
||||
public init(query: String) {
|
||||
self.query = query
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.query = decoder.decodeStringForKey("q", orElse: "")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeString(self.query, forKey: "q")
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
|
||||
public struct StickerPackCollectionInfoFlags: OptionSet {
|
||||
@ -40,16 +41,18 @@ public final class StickerPackCollectionInfo: ItemCollectionInfo, Equatable {
|
||||
public let title: String
|
||||
public let shortName: String
|
||||
public let thumbnail: TelegramMediaImageRepresentation?
|
||||
public let immediateThumbnailData: Data?
|
||||
public let hash: Int32
|
||||
public let count: Int32
|
||||
|
||||
public init(id: ItemCollectionId, flags: StickerPackCollectionInfoFlags, accessHash: Int64, title: String, shortName: String, thumbnail: TelegramMediaImageRepresentation?, hash: Int32, count: Int32) {
|
||||
public init(id: ItemCollectionId, flags: StickerPackCollectionInfoFlags, accessHash: Int64, title: String, shortName: String, thumbnail: TelegramMediaImageRepresentation?, immediateThumbnailData: Data?, hash: Int32, count: Int32) {
|
||||
self.id = id
|
||||
self.flags = flags
|
||||
self.accessHash = accessHash
|
||||
self.title = title
|
||||
self.shortName = shortName
|
||||
self.thumbnail = thumbnail
|
||||
self.immediateThumbnailData = immediateThumbnailData
|
||||
self.hash = hash
|
||||
self.count = count
|
||||
}
|
||||
@ -60,6 +63,7 @@ public final class StickerPackCollectionInfo: ItemCollectionInfo, Equatable {
|
||||
self.title = decoder.decodeStringForKey("t", orElse: "")
|
||||
self.shortName = decoder.decodeStringForKey("s", orElse: "")
|
||||
self.thumbnail = decoder.decodeObjectForKey("th", decoder: { TelegramMediaImageRepresentation(decoder: $0) }) as? TelegramMediaImageRepresentation
|
||||
self.immediateThumbnailData = decoder.decodeDataForKey("itd")
|
||||
self.hash = decoder.decodeInt32ForKey("h", orElse: 0)
|
||||
self.flags = StickerPackCollectionInfoFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0))
|
||||
self.count = decoder.decodeInt32ForKey("n", orElse: 0)
|
||||
@ -76,6 +80,11 @@ public final class StickerPackCollectionInfo: ItemCollectionInfo, Equatable {
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "th")
|
||||
}
|
||||
if let immediateThumbnailData = self.immediateThumbnailData {
|
||||
encoder.encodeData(immediateThumbnailData, forKey: "itd")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "itd")
|
||||
}
|
||||
encoder.encodeInt32(self.hash, forKey: "h")
|
||||
encoder.encodeInt32(self.flags.rawValue, forKey: "f")
|
||||
encoder.encodeInt32(self.count, forKey: "n")
|
||||
@ -98,6 +107,10 @@ public final class StickerPackCollectionInfo: ItemCollectionInfo, Equatable {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.immediateThumbnailData != rhs.immediateThumbnailData {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.flags != rhs.flags {
|
||||
return false
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) }
|
||||
dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) }
|
||||
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
|
||||
dict[461151667] = { return Api.ChatFull.parse_chatFull($0) }
|
||||
dict[-281384243] = { return Api.ChatFull.parse_channelFull($0) }
|
||||
dict[231260545] = { return Api.ChatFull.parse_chatFull($0) }
|
||||
dict[-1159937629] = { return Api.PollResults.parse_pollResults($0) }
|
||||
dict[-925415106] = { return Api.ChatParticipant.parse_chatParticipant($0) }
|
||||
dict[-636267638] = { return Api.ChatParticipant.parse_chatParticipantCreator($0) }
|
||||
@ -266,8 +266,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-13975905] = { return Api.Update.parse_updateChannelUserTyping($0) }
|
||||
dict[-309990731] = { return Api.Update.parse_updatePinnedMessages($0) }
|
||||
dict[-2054649973] = { return Api.Update.parse_updatePinnedChannelMessages($0) }
|
||||
dict[321954198] = { return Api.Update.parse_updateChat($0) }
|
||||
dict[-219423922] = { return Api.Update.parse_updateGroupCallParticipants($0) }
|
||||
dict[1462009966] = { return Api.Update.parse_updateGroupCall($0) }
|
||||
dict[-1537295973] = { return Api.Update.parse_updateGroupCall($0) }
|
||||
dict[1059076315] = { return Api.Update.parse_updateBotInlineQuery($0) }
|
||||
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
|
||||
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
|
||||
@ -346,7 +347,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1494368259] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaContact($0) }
|
||||
dict[-1768777083] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaGeo($0) }
|
||||
dict[2002815875] = { return Api.KeyboardButtonRow.parse_keyboardButtonRow($0) }
|
||||
dict[-290164953] = { return Api.StickerSet.parse_stickerSet($0) }
|
||||
dict[1088567208] = { return Api.StickerSet.parse_stickerSet($0) }
|
||||
dict[354925740] = { return Api.SecureSecretSettings.parse_secureSecretSettings($0) }
|
||||
dict[539045032] = { return Api.photos.Photo.parse_photo($0) }
|
||||
dict[-208488460] = { return Api.InputContact.parse_inputPhoneContact($0) }
|
||||
@ -379,13 +380,13 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[505969924] = { return Api.InputMedia.parse_inputMediaUploadedPhoto($0) }
|
||||
dict[1530447553] = { return Api.InputMedia.parse_inputMediaUploadedDocument($0) }
|
||||
dict[-1279654347] = { return Api.InputMedia.parse_inputMediaPhoto($0) }
|
||||
dict[598418386] = { return Api.InputMedia.parse_inputMediaDocument($0) }
|
||||
dict[-440664550] = { return Api.InputMedia.parse_inputMediaPhotoExternal($0) }
|
||||
dict[-78455655] = { return Api.InputMedia.parse_inputMediaDocumentExternal($0) }
|
||||
dict[-122978821] = { return Api.InputMedia.parse_inputMediaContact($0) }
|
||||
dict[261416433] = { return Api.InputMedia.parse_inputMediaPoll($0) }
|
||||
dict[-428884101] = { return Api.InputMedia.parse_inputMediaDice($0) }
|
||||
dict[-1759532989] = { return Api.InputMedia.parse_inputMediaGeoLive($0) }
|
||||
dict[860303448] = { return Api.InputMedia.parse_inputMediaDocument($0) }
|
||||
dict[2134579434] = { return Api.InputPeer.parse_inputPeerEmpty($0) }
|
||||
dict[2107670217] = { return Api.InputPeer.parse_inputPeerSelf($0) }
|
||||
dict[396093539] = { return Api.InputPeer.parse_inputPeerChat($0) }
|
||||
|
@ -2052,30 +2052,11 @@ public extension Api {
|
||||
|
||||
}
|
||||
public enum ChatFull: TypeConstructorDescription {
|
||||
case chatFull(flags: Int32, id: Int32, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?)
|
||||
case channelFull(flags: Int32, id: Int32, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite, botInfo: [Api.BotInfo], migratedFromChatId: Int32?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int32?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?)
|
||||
case chatFull(flags: Int32, id: Int32, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId):
|
||||
if boxed {
|
||||
buffer.appendInt32(461151667)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
serializeString(about, buffer: buffer, boxed: false)
|
||||
participants.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 2) != 0 {chatPhoto!.serialize(buffer, true)}
|
||||
notifySettings.serialize(buffer, true)
|
||||
exportedInvite.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(botInfo!.count))
|
||||
for item in botInfo! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
if Int(flags) & Int(1 << 6) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 11) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call):
|
||||
if boxed {
|
||||
buffer.appendInt32(-281384243)
|
||||
@ -2113,66 +2094,38 @@ public extension Api {
|
||||
serializeInt32(pts, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 21) != 0 {call!.serialize(buffer, true)}
|
||||
break
|
||||
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call):
|
||||
if boxed {
|
||||
buffer.appendInt32(231260545)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
serializeString(about, buffer: buffer, boxed: false)
|
||||
participants.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 2) != 0 {chatPhoto!.serialize(buffer, true)}
|
||||
notifySettings.serialize(buffer, true)
|
||||
exportedInvite.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(botInfo!.count))
|
||||
for item in botInfo! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
if Int(flags) & Int(1 << 6) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 11) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 12) != 0 {call!.serialize(buffer, true)}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId):
|
||||
return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId)])
|
||||
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call):
|
||||
return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts), ("call", call)])
|
||||
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call):
|
||||
return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId), ("call", call)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_chatFull(_ reader: BufferReader) -> ChatFull? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
var _4: Api.ChatParticipants?
|
||||
if let signature = reader.readInt32() {
|
||||
_4 = Api.parse(reader, signature: signature) as? Api.ChatParticipants
|
||||
}
|
||||
var _5: Api.Photo?
|
||||
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
|
||||
_5 = Api.parse(reader, signature: signature) as? Api.Photo
|
||||
} }
|
||||
var _6: Api.PeerNotifySettings?
|
||||
if let signature = reader.readInt32() {
|
||||
_6 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings
|
||||
}
|
||||
var _7: Api.ExportedChatInvite?
|
||||
if let signature = reader.readInt32() {
|
||||
_7 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
|
||||
}
|
||||
var _8: [Api.BotInfo]?
|
||||
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
|
||||
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInfo.self)
|
||||
} }
|
||||
var _9: Int32?
|
||||
if Int(_1!) & Int(1 << 6) != 0 {_9 = reader.readInt32() }
|
||||
var _10: Int32?
|
||||
if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt32() }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil
|
||||
let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil
|
||||
let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 {
|
||||
return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7!, botInfo: _8, pinnedMsgId: _9, folderId: _10)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_channelFull(_ reader: BufferReader) -> ChatFull? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
@ -2279,6 +2232,59 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_chatFull(_ reader: BufferReader) -> ChatFull? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
var _4: Api.ChatParticipants?
|
||||
if let signature = reader.readInt32() {
|
||||
_4 = Api.parse(reader, signature: signature) as? Api.ChatParticipants
|
||||
}
|
||||
var _5: Api.Photo?
|
||||
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
|
||||
_5 = Api.parse(reader, signature: signature) as? Api.Photo
|
||||
} }
|
||||
var _6: Api.PeerNotifySettings?
|
||||
if let signature = reader.readInt32() {
|
||||
_6 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings
|
||||
}
|
||||
var _7: Api.ExportedChatInvite?
|
||||
if let signature = reader.readInt32() {
|
||||
_7 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
|
||||
}
|
||||
var _8: [Api.BotInfo]?
|
||||
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
|
||||
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInfo.self)
|
||||
} }
|
||||
var _9: Int32?
|
||||
if Int(_1!) & Int(1 << 6) != 0 {_9 = reader.readInt32() }
|
||||
var _10: Int32?
|
||||
if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt32() }
|
||||
var _11: Api.InputGroupCall?
|
||||
if Int(_1!) & Int(1 << 12) != 0 {if let signature = reader.readInt32() {
|
||||
_11 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil
|
||||
let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil
|
||||
let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil
|
||||
let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
|
||||
return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7!, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum PollResults: TypeConstructorDescription {
|
||||
@ -6418,8 +6424,9 @@ public extension Api {
|
||||
case updateChannelUserTyping(flags: Int32, channelId: Int32, topMsgId: Int32?, userId: Int32, action: Api.SendMessageAction)
|
||||
case updatePinnedMessages(flags: Int32, peer: Api.Peer, messages: [Int32], pts: Int32, ptsCount: Int32)
|
||||
case updatePinnedChannelMessages(flags: Int32, channelId: Int32, messages: [Int32], pts: Int32, ptsCount: Int32)
|
||||
case updateChat(chatId: Int32)
|
||||
case updateGroupCallParticipants(call: Api.InputGroupCall, participants: [Api.GroupCallParticipant], version: Int32)
|
||||
case updateGroupCall(channelId: Int32, call: Api.GroupCall)
|
||||
case updateGroupCall(chatId: Int32, call: Api.GroupCall)
|
||||
case updateBotInlineQuery(flags: Int32, queryId: Int64, userId: Int32, query: String, geo: Api.GeoPoint?, peerType: Api.InlineQueryPeerType?, offset: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
@ -7145,6 +7152,12 @@ public extension Api {
|
||||
serializeInt32(pts, buffer: buffer, boxed: false)
|
||||
serializeInt32(ptsCount, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .updateChat(let chatId):
|
||||
if boxed {
|
||||
buffer.appendInt32(321954198)
|
||||
}
|
||||
serializeInt32(chatId, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .updateGroupCallParticipants(let call, let participants, let version):
|
||||
if boxed {
|
||||
buffer.appendInt32(-219423922)
|
||||
@ -7157,11 +7170,11 @@ public extension Api {
|
||||
}
|
||||
serializeInt32(version, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .updateGroupCall(let channelId, let call):
|
||||
case .updateGroupCall(let chatId, let call):
|
||||
if boxed {
|
||||
buffer.appendInt32(1462009966)
|
||||
buffer.appendInt32(-1537295973)
|
||||
}
|
||||
serializeInt32(channelId, buffer: buffer, boxed: false)
|
||||
serializeInt32(chatId, buffer: buffer, boxed: false)
|
||||
call.serialize(buffer, true)
|
||||
break
|
||||
case .updateBotInlineQuery(let flags, let queryId, let userId, let query, let geo, let peerType, let offset):
|
||||
@ -7349,10 +7362,12 @@ public extension Api {
|
||||
return ("updatePinnedMessages", [("flags", flags), ("peer", peer), ("messages", messages), ("pts", pts), ("ptsCount", ptsCount)])
|
||||
case .updatePinnedChannelMessages(let flags, let channelId, let messages, let pts, let ptsCount):
|
||||
return ("updatePinnedChannelMessages", [("flags", flags), ("channelId", channelId), ("messages", messages), ("pts", pts), ("ptsCount", ptsCount)])
|
||||
case .updateChat(let chatId):
|
||||
return ("updateChat", [("chatId", chatId)])
|
||||
case .updateGroupCallParticipants(let call, let participants, let version):
|
||||
return ("updateGroupCallParticipants", [("call", call), ("participants", participants), ("version", version)])
|
||||
case .updateGroupCall(let channelId, let call):
|
||||
return ("updateGroupCall", [("channelId", channelId), ("call", call)])
|
||||
case .updateGroupCall(let chatId, let call):
|
||||
return ("updateGroupCall", [("chatId", chatId), ("call", call)])
|
||||
case .updateBotInlineQuery(let flags, let queryId, let userId, let query, let geo, let peerType, let offset):
|
||||
return ("updateBotInlineQuery", [("flags", flags), ("queryId", queryId), ("userId", userId), ("query", query), ("geo", geo), ("peerType", peerType), ("offset", offset)])
|
||||
}
|
||||
@ -8799,6 +8814,17 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_updateChat(_ reader: BufferReader) -> Update? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.Update.updateChat(chatId: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_updateGroupCallParticipants(_ reader: BufferReader) -> Update? {
|
||||
var _1: Api.InputGroupCall?
|
||||
if let signature = reader.readInt32() {
|
||||
@ -8830,7 +8856,7 @@ public extension Api {
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.Update.updateGroupCall(channelId: _1!, call: _2!)
|
||||
return Api.Update.updateGroupCall(chatId: _1!, call: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
@ -10990,13 +11016,13 @@ public extension Api {
|
||||
|
||||
}
|
||||
public enum StickerSet: TypeConstructorDescription {
|
||||
case stickerSet(flags: Int32, installedDate: Int32?, id: Int64, accessHash: Int64, title: String, shortName: String, thumb: Api.PhotoSize?, thumbDcId: Int32?, count: Int32, hash: Int32)
|
||||
case stickerSet(flags: Int32, installedDate: Int32?, id: Int64, accessHash: Int64, title: String, shortName: String, thumbs: [Api.PhotoSize]?, thumbDcId: Int32?, count: Int32, hash: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .stickerSet(let flags, let installedDate, let id, let accessHash, let title, let shortName, let thumb, let thumbDcId, let count, let hash):
|
||||
case .stickerSet(let flags, let installedDate, let id, let accessHash, let title, let shortName, let thumbs, let thumbDcId, let count, let hash):
|
||||
if boxed {
|
||||
buffer.appendInt32(-290164953)
|
||||
buffer.appendInt32(1088567208)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(installedDate!, buffer: buffer, boxed: false)}
|
||||
@ -11004,7 +11030,11 @@ public extension Api {
|
||||
serializeInt64(accessHash, buffer: buffer, boxed: false)
|
||||
serializeString(title, buffer: buffer, boxed: false)
|
||||
serializeString(shortName, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 4) != 0 {thumb!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(thumbs!.count))
|
||||
for item in thumbs! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
if Int(flags) & Int(1 << 4) != 0 {serializeInt32(thumbDcId!, buffer: buffer, boxed: false)}
|
||||
serializeInt32(count, buffer: buffer, boxed: false)
|
||||
serializeInt32(hash, buffer: buffer, boxed: false)
|
||||
@ -11014,8 +11044,8 @@ public extension Api {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .stickerSet(let flags, let installedDate, let id, let accessHash, let title, let shortName, let thumb, let thumbDcId, let count, let hash):
|
||||
return ("stickerSet", [("flags", flags), ("installedDate", installedDate), ("id", id), ("accessHash", accessHash), ("title", title), ("shortName", shortName), ("thumb", thumb), ("thumbDcId", thumbDcId), ("count", count), ("hash", hash)])
|
||||
case .stickerSet(let flags, let installedDate, let id, let accessHash, let title, let shortName, let thumbs, let thumbDcId, let count, let hash):
|
||||
return ("stickerSet", [("flags", flags), ("installedDate", installedDate), ("id", id), ("accessHash", accessHash), ("title", title), ("shortName", shortName), ("thumbs", thumbs), ("thumbDcId", thumbDcId), ("count", count), ("hash", hash)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -11032,9 +11062,9 @@ public extension Api {
|
||||
_5 = parseString(reader)
|
||||
var _6: String?
|
||||
_6 = parseString(reader)
|
||||
var _7: Api.PhotoSize?
|
||||
if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() {
|
||||
_7 = Api.parse(reader, signature: signature) as? Api.PhotoSize
|
||||
var _7: [Api.PhotoSize]?
|
||||
if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() {
|
||||
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self)
|
||||
} }
|
||||
var _8: Int32?
|
||||
if Int(_1!) & Int(1 << 4) != 0 {_8 = reader.readInt32() }
|
||||
@ -11053,7 +11083,7 @@ public extension Api {
|
||||
let _c9 = _9 != nil
|
||||
let _c10 = _10 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 {
|
||||
return Api.StickerSet.stickerSet(flags: _1!, installedDate: _2, id: _3!, accessHash: _4!, title: _5!, shortName: _6!, thumb: _7, thumbDcId: _8, count: _9!, hash: _10!)
|
||||
return Api.StickerSet.stickerSet(flags: _1!, installedDate: _2, id: _3!, accessHash: _4!, title: _5!, shortName: _6!, thumbs: _7, thumbDcId: _8, count: _9!, hash: _10!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
@ -11501,13 +11531,13 @@ public extension Api {
|
||||
case inputMediaUploadedPhoto(flags: Int32, file: Api.InputFile, stickers: [Api.InputDocument]?, ttlSeconds: Int32?)
|
||||
case inputMediaUploadedDocument(flags: Int32, file: Api.InputFile, thumb: Api.InputFile?, mimeType: String, attributes: [Api.DocumentAttribute], stickers: [Api.InputDocument]?, ttlSeconds: Int32?)
|
||||
case inputMediaPhoto(flags: Int32, id: Api.InputPhoto, ttlSeconds: Int32?)
|
||||
case inputMediaDocument(flags: Int32, id: Api.InputDocument, ttlSeconds: Int32?)
|
||||
case inputMediaPhotoExternal(flags: Int32, url: String, ttlSeconds: Int32?)
|
||||
case inputMediaDocumentExternal(flags: Int32, url: String, ttlSeconds: Int32?)
|
||||
case inputMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String)
|
||||
case inputMediaPoll(flags: Int32, poll: Api.Poll, correctAnswers: [Buffer]?, solution: String?, solutionEntities: [Api.MessageEntity]?)
|
||||
case inputMediaDice(emoticon: String)
|
||||
case inputMediaGeoLive(flags: Int32, geoPoint: Api.InputGeoPoint, heading: Int32?, period: Int32?, proximityNotificationRadius: Int32?)
|
||||
case inputMediaDocument(flags: Int32, id: Api.InputDocument, ttlSeconds: Int32?, query: String?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -11602,14 +11632,6 @@ public extension Api {
|
||||
id.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .inputMediaDocument(let flags, let id, let ttlSeconds):
|
||||
if boxed {
|
||||
buffer.appendInt32(598418386)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
id.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .inputMediaPhotoExternal(let flags, let url, let ttlSeconds):
|
||||
if boxed {
|
||||
buffer.appendInt32(-440664550)
|
||||
@ -11669,6 +11691,15 @@ public extension Api {
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(period!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {serializeInt32(proximityNotificationRadius!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .inputMediaDocument(let flags, let id, let ttlSeconds, let query):
|
||||
if boxed {
|
||||
buffer.appendInt32(860303448)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
id.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeString(query!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -11692,8 +11723,6 @@ public extension Api {
|
||||
return ("inputMediaUploadedDocument", [("flags", flags), ("file", file), ("thumb", thumb), ("mimeType", mimeType), ("attributes", attributes), ("stickers", stickers), ("ttlSeconds", ttlSeconds)])
|
||||
case .inputMediaPhoto(let flags, let id, let ttlSeconds):
|
||||
return ("inputMediaPhoto", [("flags", flags), ("id", id), ("ttlSeconds", ttlSeconds)])
|
||||
case .inputMediaDocument(let flags, let id, let ttlSeconds):
|
||||
return ("inputMediaDocument", [("flags", flags), ("id", id), ("ttlSeconds", ttlSeconds)])
|
||||
case .inputMediaPhotoExternal(let flags, let url, let ttlSeconds):
|
||||
return ("inputMediaPhotoExternal", [("flags", flags), ("url", url), ("ttlSeconds", ttlSeconds)])
|
||||
case .inputMediaDocumentExternal(let flags, let url, let ttlSeconds):
|
||||
@ -11706,6 +11735,8 @@ public extension Api {
|
||||
return ("inputMediaDice", [("emoticon", emoticon)])
|
||||
case .inputMediaGeoLive(let flags, let geoPoint, let heading, let period, let proximityNotificationRadius):
|
||||
return ("inputMediaGeoLive", [("flags", flags), ("geoPoint", geoPoint), ("heading", heading), ("period", period), ("proximityNotificationRadius", proximityNotificationRadius)])
|
||||
case .inputMediaDocument(let flags, let id, let ttlSeconds, let query):
|
||||
return ("inputMediaDocument", [("flags", flags), ("id", id), ("ttlSeconds", ttlSeconds), ("query", query)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -11901,25 +11932,6 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_inputMediaDocument(_ reader: BufferReader) -> InputMedia? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Api.InputDocument?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.InputDocument
|
||||
}
|
||||
var _3: Int32?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.InputMedia.inputMediaDocument(flags: _1!, id: _2!, ttlSeconds: _3)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_inputMediaPhotoExternal(_ reader: BufferReader) -> InputMedia? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
@ -12039,6 +12051,28 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_inputMediaDocument(_ reader: BufferReader) -> InputMedia? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Api.InputDocument?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.InputDocument
|
||||
}
|
||||
var _3: Int32?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
|
||||
var _4: String?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.InputMedia.inputMediaDocument(flags: _1!, id: _2!, ttlSeconds: _3, query: _4)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum InputPeer: TypeConstructorDescription {
|
||||
|
@ -7241,12 +7241,12 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func createGroupCall(channel: Api.InputChannel, randomId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
public static func createGroupCall(peer: Api.InputPeer, randomId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-467076606)
|
||||
channel.serialize(buffer, true)
|
||||
buffer.appendInt32(-1120031776)
|
||||
peer.serialize(buffer, true)
|
||||
serializeInt32(randomId, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "phone.createGroupCall", parameters: [("channel", channel), ("randomId", randomId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
return (FunctionDescription(name: "phone.createGroupCall", parameters: [("peer", peer), ("randomId", randomId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
|
@ -280,10 +280,13 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
if case let .peer(peerId) = groupCallPanelSource {
|
||||
availableGroupCall = context.account.viewTracker.peerView(peerId)
|
||||
|> map { peerView -> CachedChannelData.ActiveCall? in
|
||||
guard let cachedData = peerView.cachedData as? CachedChannelData else {
|
||||
if let cachedData = peerView.cachedData as? CachedChannelData {
|
||||
return cachedData.activeCall
|
||||
} else if let cachedData = peerView.cachedData as? CachedGroupData {
|
||||
return cachedData.activeCall
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return cachedData.activeCall
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { activeCall -> Signal<GroupCallPanelData?, NoError> in
|
||||
|
@ -14,7 +14,7 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
||||
enum Color: Equatable {
|
||||
case red
|
||||
case green
|
||||
case custom(UInt32)
|
||||
case custom(UInt32, CGFloat)
|
||||
}
|
||||
|
||||
case blurred(isFilled: Bool)
|
||||
@ -37,6 +37,7 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
||||
case speaker
|
||||
case airpods
|
||||
case airpodsPro
|
||||
case headphones
|
||||
case accept
|
||||
case end
|
||||
}
|
||||
@ -196,8 +197,8 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
||||
fillColor = UIColor(rgb: 0xd92326)
|
||||
case .green:
|
||||
fillColor = UIColor(rgb: 0x74db58)
|
||||
case let .custom(color):
|
||||
fillColor = UIColor(rgb: color)
|
||||
case let .custom(color, alpha):
|
||||
fillColor = UIColor(rgb: color, alpha: alpha)
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,6 +222,8 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
||||
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallAirpodsButton"), color: imageColor)
|
||||
case .airpodsPro:
|
||||
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallAirpodsProButton"), color: imageColor)
|
||||
case .headphones:
|
||||
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallHeadphonesButton"), color: imageColor)
|
||||
case .accept:
|
||||
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallAcceptButton"), color: imageColor)
|
||||
case .end:
|
||||
@ -290,7 +293,7 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
||||
fillColor = UIColor(rgb: 0xd92326).withMultipliedBrightnessBy(0.2).withAlphaComponent(0.2)
|
||||
case .green:
|
||||
fillColor = UIColor(rgb: 0x74db58).withMultipliedBrightnessBy(0.2).withAlphaComponent(0.2)
|
||||
case let .custom(color):
|
||||
case let .custom(color, alpha):
|
||||
fillColor = UIColor(rgb: color).withMultipliedBrightnessBy(0.2).withAlphaComponent(0.2)
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ private enum ButtonDescription: Equatable {
|
||||
case bluetooth
|
||||
case airpods
|
||||
case airpodsPro
|
||||
case headphones
|
||||
}
|
||||
|
||||
enum EndType {
|
||||
@ -205,7 +206,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
case .speaker:
|
||||
soundOutput = .speaker
|
||||
case .headphones:
|
||||
soundOutput = .bluetooth
|
||||
soundOutput = .headphones
|
||||
case let .bluetooth(type):
|
||||
switch type {
|
||||
case .generic:
|
||||
@ -296,7 +297,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
case .speaker:
|
||||
soundOutput = .speaker
|
||||
case .headphones:
|
||||
soundOutput = .builtin
|
||||
soundOutput = .headphones
|
||||
case let .bluetooth(type):
|
||||
switch type {
|
||||
case .generic:
|
||||
@ -467,6 +468,9 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
case .airpodsPro:
|
||||
image = .airpodsPro
|
||||
title = strings.Call_Audio
|
||||
case .headphones:
|
||||
image = .headphones
|
||||
title = strings.Call_Audio
|
||||
}
|
||||
buttonContent = CallControllerButtonItemNode.Content(
|
||||
appearance: .blurred(isFilled: isFilled),
|
||||
|
@ -411,9 +411,9 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||
}
|
||||
|
||||
let spacing: CGFloat = 5.0
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: size.height))
|
||||
let subtitleSize = self.subtitleNode.updateLayout(size: CGSize(width: 160.0, height: size.height), animated: true)
|
||||
let speakerSize = self.speakerNode.updateLayout(CGSize(width: 160.0, height: size.height))
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: 150.0, height: size.height))
|
||||
let subtitleSize = self.subtitleNode.updateLayout(size: CGSize(width: 150.0, height: size.height), animated: true)
|
||||
let speakerSize = self.speakerNode.updateLayout(CGSize(width: 150.0, height: size.height))
|
||||
|
||||
let totalWidth = titleSize.width + spacing + subtitleSize.width
|
||||
let horizontalOrigin: CGFloat = floor((size.width - totalWidth) / 2.0)
|
||||
|
@ -296,7 +296,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount))
|
||||
}
|
||||
|
||||
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }.filter { $0.id != self.context.account.peerId }, animated: false)
|
||||
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }, animated: false)
|
||||
|
||||
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)
|
||||
|
||||
@ -321,7 +321,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
|
||||
strongSelf.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: strongSelf.theme.chat.inputPanel.secondaryTextColor)
|
||||
|
||||
strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.map { $0.peer }.filter { $0.id != strongSelf.context.account.peerId }, animated: false)
|
||||
strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.map { $0.peer }, animated: false)
|
||||
|
||||
if let (size, leftInset, rightInset) = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
@ -400,7 +400,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
|
||||
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)
|
||||
|
||||
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }.filter { $0.id != self.context.account.peerId }, animated: false)
|
||||
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }, animated: false)
|
||||
|
||||
updateAudioLevels = true
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ final class PresentationCallToneRenderer {
|
||||
|
||||
self.toneRenderer = MediaPlayerAudioRenderer(audioSession: .custom({ control in
|
||||
return controlImpl?(control) ?? EmptyDisposable
|
||||
}), playAndRecord: false, ambient: false, forceAudioToSpeaker: false, baseRate: 1.0, audioLevelPipe: self.audioLevelPipe, updatedRate: {}, audioPaused: {})
|
||||
}), playAndRecord: false, useVoiceProcessingMode: true, ambient: false, forceAudioToSpeaker: false, baseRate: 1.0, audioLevelPipe: self.audioLevelPipe, updatedRate: {}, audioPaused: {})
|
||||
|
||||
controlImpl = { [weak self] control in
|
||||
queue.async {
|
||||
|
@ -82,8 +82,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
|> distinctUntilChanged
|
||||
}
|
||||
|
||||
public var hasActiveGroupCall: Bool {
|
||||
return self.currentGroupCall != nil
|
||||
public var hasActiveCall: Bool {
|
||||
return self.currentCall != nil || self.currentGroupCall != nil
|
||||
}
|
||||
|
||||
private let currentCallPromise = Promise<PresentationCall?>(nil)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
private func loadToneData(name: String) -> Data? {
|
||||
private func loadToneData(name: String, addSilenceDuration: Double = 0.0) -> Data? {
|
||||
let outputSettings: [String: Any] = [
|
||||
AVFormatIDKey: kAudioFormatLinearPCM as NSNumber,
|
||||
AVSampleRateKey: 44100.0 as NSNumber,
|
||||
@ -62,6 +62,15 @@ private func loadToneData(name: String) -> Data? {
|
||||
}
|
||||
}
|
||||
|
||||
if !addSilenceDuration.isZero {
|
||||
let sampleRate = 44100
|
||||
let numberOfSamples = Int(Double(sampleRate) * addSilenceDuration)
|
||||
let numberOfChannels = 2
|
||||
let numberOfBytes = numberOfSamples * 2 * numberOfChannels
|
||||
|
||||
data.append(Data(count: numberOfBytes))
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
@ -73,6 +82,7 @@ enum PresentationCallTone {
|
||||
case ended
|
||||
case groupJoined
|
||||
case groupLeft
|
||||
case groupConnecting
|
||||
|
||||
var loopCount: Int? {
|
||||
switch self {
|
||||
@ -84,6 +94,8 @@ enum PresentationCallTone {
|
||||
return 1
|
||||
case .groupJoined, .groupLeft:
|
||||
return 1
|
||||
case .groupConnecting:
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@ -103,8 +115,10 @@ func presentationCallToneData(_ tone: PresentationCallTone) -> Data? {
|
||||
case .ended:
|
||||
return loadToneData(name: "voip_end.caf")
|
||||
case .groupJoined:
|
||||
return loadToneData(name: "voip_group_joined.wav")
|
||||
return loadToneData(name: "voip_group_joined.mp3")
|
||||
case .groupLeft:
|
||||
return loadToneData(name: "voip_group_left.wav")
|
||||
return loadToneData(name: "voip_group_left.mp3")
|
||||
case .groupConnecting:
|
||||
return loadToneData(name: "voip_group_connecting.mp3", addSilenceDuration: 2.0)
|
||||
}
|
||||
}
|
||||
|
@ -278,7 +278,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
var audioLevels: [(PeerId, Float, Bool)] = []
|
||||
for (peerId, level, hasVoice) in levels {
|
||||
if level > 0.1 {
|
||||
if level > 0.001 {
|
||||
audioLevels.append((peerId, level, hasVoice))
|
||||
}
|
||||
}
|
||||
@ -444,6 +444,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
private var removedChannelMembersDisposable: Disposable?
|
||||
|
||||
private var didStartConnectingOnce: Bool = false
|
||||
private var didConnectOnce: Bool = false
|
||||
private var toneRenderer: PresentationCallToneRenderer?
|
||||
|
||||
@ -831,10 +832,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
case .connected:
|
||||
mappedState = .connected
|
||||
}
|
||||
let wasConnecting = strongSelf.stateValue.networkState == .connecting
|
||||
if strongSelf.stateValue.networkState != mappedState {
|
||||
strongSelf.stateValue.networkState = mappedState
|
||||
}
|
||||
|
||||
let isConnecting = mappedState == .connecting
|
||||
|
||||
if strongSelf.isCurrentlyConnecting != isConnecting {
|
||||
@ -847,12 +848,28 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
}
|
||||
|
||||
if case .connected = state, !strongSelf.didConnectOnce {
|
||||
strongSelf.didConnectOnce = true
|
||||
|
||||
let toneRenderer = PresentationCallToneRenderer(tone: .groupJoined)
|
||||
strongSelf.toneRenderer = toneRenderer
|
||||
toneRenderer.setAudioSessionActive(strongSelf.isAudioSessionActive)
|
||||
if (wasConnecting != isConnecting && strongSelf.didConnectOnce) { //|| !strongSelf.didStartConnectingOnce {
|
||||
if isConnecting {
|
||||
let toneRenderer = PresentationCallToneRenderer(tone: .groupConnecting)
|
||||
strongSelf.toneRenderer = toneRenderer
|
||||
toneRenderer.setAudioSessionActive(strongSelf.isAudioSessionActive)
|
||||
} else {
|
||||
strongSelf.toneRenderer = nil
|
||||
}
|
||||
}
|
||||
|
||||
if isConnecting {
|
||||
strongSelf.didStartConnectingOnce = true
|
||||
}
|
||||
|
||||
if case .connected = state {
|
||||
if !strongSelf.didConnectOnce {
|
||||
strongSelf.didConnectOnce = true
|
||||
|
||||
let toneRenderer = PresentationCallToneRenderer(tone: .groupJoined)
|
||||
strongSelf.toneRenderer = toneRenderer
|
||||
toneRenderer.setAudioSessionActive(strongSelf.isAudioSessionActive)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
@ -1099,16 +1116,31 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
private func markAsCanBeRemoved() {
|
||||
self.callContext?.stop()
|
||||
self.callContext = nil
|
||||
self._canBeRemoved.set(.single(true))
|
||||
|
||||
let toneRenderer = PresentationCallToneRenderer(tone: .groupLeft)
|
||||
self.toneRenderer = toneRenderer
|
||||
toneRenderer.setAudioSessionActive(self.isAudioSessionActive)
|
||||
|
||||
Queue.mainQueue().after(0.5, {
|
||||
self.wasRemoved.set(.single(true))
|
||||
})
|
||||
if self.didConnectOnce {
|
||||
if let callManager = self.accountContext.sharedContext.callManager {
|
||||
let _ = (callManager.currentGroupCallSignal
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] call in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let call = call, call !== strongSelf {
|
||||
strongSelf.wasRemoved.set(.single(true))
|
||||
return
|
||||
}
|
||||
|
||||
let toneRenderer = PresentationCallToneRenderer(tone: .groupLeft)
|
||||
strongSelf.toneRenderer = toneRenderer
|
||||
toneRenderer.setAudioSessionActive(strongSelf.isAudioSessionActive)
|
||||
|
||||
Queue.mainQueue().after(1.0, {
|
||||
strongSelf.wasRemoved.set(.single(true))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func leave(terminateIfPossible: Bool) -> Signal<Bool, NoError> {
|
||||
|
@ -20,8 +20,8 @@ private let areaSize = CGSize(width: 440.0, height: 440.0)
|
||||
private let blobSize = CGSize(width: 244.0, height: 244.0)
|
||||
|
||||
final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
enum State {
|
||||
enum ActiveState {
|
||||
enum State: Equatable {
|
||||
enum ActiveState: Equatable {
|
||||
case cantSpeak
|
||||
case muted
|
||||
case on
|
||||
@ -31,6 +31,15 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
case active(state: ActiveState)
|
||||
}
|
||||
|
||||
var stateValue: State {
|
||||
return self.currentParams?.state ?? .connecting
|
||||
}
|
||||
var statePromise = ValuePromise<State>()
|
||||
var state: Signal<State, NoError> {
|
||||
return self.statePromise.get()
|
||||
}
|
||||
|
||||
let bottomNode: ASDisplayNode
|
||||
private let containerNode: ASDisplayNode
|
||||
private let backgroundNode: VoiceChatActionButtonBackgroundNode
|
||||
private let iconNode: VoiceChatMicrophoneNode
|
||||
@ -55,6 +64,14 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
|
||||
var isDisabled: Bool = false
|
||||
|
||||
var ignoreHierarchyChanges: Bool {
|
||||
get {
|
||||
return self.backgroundNode.ignoreHierarchyChanges
|
||||
} set {
|
||||
self.backgroundNode.ignoreHierarchyChanges = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var wasActiveWhenPressed = false
|
||||
var pressing: Bool = false {
|
||||
didSet {
|
||||
@ -85,6 +102,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
}
|
||||
|
||||
init() {
|
||||
self.bottomNode = ASDisplayNode()
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.backgroundNode = VoiceChatActionButtonBackgroundNode()
|
||||
self.iconNode = VoiceChatMicrophoneNode()
|
||||
@ -94,6 +112,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.bottomNode)
|
||||
self.addSubnode(self.titleLabel)
|
||||
self.addSubnode(self.subtitleLabel)
|
||||
|
||||
@ -139,7 +158,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
}
|
||||
|
||||
let updatedTitle = self.titleLabel.attributedText?.string != title
|
||||
let updatedSubtitle = self.subtitleLabel.attributedText?.string != title
|
||||
let updatedSubtitle = self.subtitleLabel.attributedText?.string != subtitle
|
||||
|
||||
self.titleLabel.attributedText = NSAttributedString(string: title, font: titleFont, textColor: .white)
|
||||
self.subtitleLabel.attributedText = NSAttributedString(string: subtitle, font: subtitleFont, textColor: .white)
|
||||
@ -167,9 +186,10 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
||||
let totalHeight = titleSize.height + subtitleSize.height + 1.0
|
||||
|
||||
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor(size.height - totalHeight / 2.0) - 110.0), size: titleSize)
|
||||
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor(size.height - totalHeight / 2.0) - 112.0), size: titleSize)
|
||||
self.subtitleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: self.titleLabel.frame.maxY + 1.0), size: subtitleSize)
|
||||
|
||||
self.bottomNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
self.backgroundNode.bounds = CGRect(origin: CGPoint(), size: size)
|
||||
@ -188,18 +208,20 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
break
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
|
||||
|
||||
if snap {
|
||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
|
||||
transition.updateTransformScale(node: self.backgroundNode, scale: active ? 0.75 : 0.5)
|
||||
transition.updateTransformScale(node: self.iconNode, scale: 0.5)
|
||||
transition.updateAlpha(node: self.titleLabel, alpha: 0.0)
|
||||
transition.updateAlpha(node: self.subtitleLabel, alpha: 0.0)
|
||||
transition.updateAlpha(layer: self.backgroundNode.maskProgressLayer, alpha: 0.0)
|
||||
} else {
|
||||
transition.updateTransformScale(node: self.backgroundNode, scale: small ? 0.85 : 1.0)
|
||||
transition.updateTransformScale(node: self.iconNode, scale: self.pressing ? 0.9 : 1.0)
|
||||
transition.updateAlpha(node: self.titleLabel, alpha: 1.0)
|
||||
transition.updateAlpha(node: self.subtitleLabel, alpha: 1.0)
|
||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
|
||||
transition.updateTransformScale(node: self.backgroundNode, scale: small ? 0.85 : 1.0, delay: 0.05)
|
||||
transition.updateTransformScale(node: self.iconNode, scale: self.pressing ? 0.9 : 1.0, delay: 0.05)
|
||||
transition.updateAlpha(node: self.titleLabel, alpha: 1.0, delay: 0.05)
|
||||
transition.updateAlpha(node: self.subtitleLabel, alpha: 1.0, delay: 0.05)
|
||||
transition.updateAlpha(layer: self.backgroundNode.maskProgressLayer, alpha: 1.0)
|
||||
}
|
||||
|
||||
@ -209,7 +231,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
}
|
||||
|
||||
private func applyIconParams() {
|
||||
guard let (size, _, state, _, small, title, subtitle, snap) = self.currentParams else {
|
||||
guard let (_, _, state, _, _, _, _, snap) = self.currentParams else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -250,6 +272,8 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
let previousState = previous?.state
|
||||
self.currentParams = (size, buttonSize, state, dark, small, title, subtitle, previous?.snap ?? false)
|
||||
|
||||
self.statePromise.set(state)
|
||||
|
||||
var backgroundState: VoiceChatActionButtonBackgroundNode.State
|
||||
switch state {
|
||||
case let .active(state):
|
||||
@ -392,6 +416,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
|
||||
private var state: State
|
||||
private var hasState = false
|
||||
|
||||
private var transition: State?
|
||||
|
||||
var audioLevel: CGFloat = 0.0 {
|
||||
@ -422,6 +447,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
|
||||
private let hierarchyTrackingNode: HierarchyTrackingNode
|
||||
private var isCurrentlyInHierarchy = false
|
||||
var ignoreHierarchyChanges = false
|
||||
|
||||
override init() {
|
||||
self.state = .connecting
|
||||
@ -483,7 +509,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
self.maskCircleLayer.isHidden = true
|
||||
|
||||
updateInHierarchy = { [weak self] value in
|
||||
if let strongSelf = self {
|
||||
if let strongSelf = self, !strongSelf.ignoreHierarchyChanges {
|
||||
strongSelf.isCurrentlyInHierarchy = value
|
||||
strongSelf.updateAnimations()
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ class VoiceChatActionItemNode: ListViewItemNode {
|
||||
updatedTheme = item.presentationData.theme
|
||||
}
|
||||
|
||||
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
||||
let titleFont = Font.regular(17.0)
|
||||
|
||||
var leftInset: CGFloat = 16.0 + params.leftInset
|
||||
if case .generic = item.icon {
|
||||
|
@ -39,10 +39,16 @@ public final class VoiceChatOverlayController: ViewController {
|
||||
return
|
||||
}
|
||||
|
||||
if self.isButtonHidden == hidden || (!slide && self.isSlidOffscreen) {
|
||||
if self.isButtonHidden == hidden {
|
||||
return
|
||||
}
|
||||
self.isButtonHidden = hidden
|
||||
|
||||
var slide = slide
|
||||
if self.isSlidOffscreen && !hidden {
|
||||
slide = true
|
||||
}
|
||||
|
||||
self.isSlidOffscreen = hidden && slide
|
||||
|
||||
guard actionButton.supernode === self else {
|
||||
@ -53,6 +59,7 @@ public final class VoiceChatOverlayController: ViewController {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
|
||||
if hidden {
|
||||
if slide {
|
||||
actionButton.isHidden = false
|
||||
transition.updateSublayerTransformOffset(layer: actionButton.layer, offset: CGPoint(x: slideOffset, y: 0.0))
|
||||
} else {
|
||||
actionButton.layer.removeAllAnimations()
|
||||
@ -64,10 +71,10 @@ public final class VoiceChatOverlayController: ViewController {
|
||||
}
|
||||
} else {
|
||||
actionButton.isHidden = false
|
||||
actionButton.layer.removeAllAnimations()
|
||||
if slide {
|
||||
transition.updateSublayerTransformOffset(layer: actionButton.layer, offset: CGPoint())
|
||||
} else {
|
||||
actionButton.layer.removeAllAnimations()
|
||||
actionButton.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
|
||||
}
|
||||
}
|
||||
@ -85,21 +92,50 @@ public final class VoiceChatOverlayController: ViewController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var initialLeftButtonPosition: CGPoint?
|
||||
private var initialRightButtonPosition: CGPoint?
|
||||
|
||||
func animateIn(from: CGRect) {
|
||||
guard let actionButton = self.controller?.actionButton else {
|
||||
guard let actionButton = self.controller?.actionButton, let leftButton = self.controller?.audioOutputNode, let rightButton = self.controller?.leaveNode else {
|
||||
return
|
||||
}
|
||||
|
||||
actionButton.update(snap: true, animated: !self.isSlidOffscreen)
|
||||
self.initialLeftButtonPosition = leftButton.position
|
||||
self.initialRightButtonPosition = rightButton.position
|
||||
|
||||
actionButton.update(snap: true, animated: !self.isSlidOffscreen && !self.isButtonHidden)
|
||||
if self.isSlidOffscreen {
|
||||
leftButton.isHidden = true
|
||||
rightButton.isHidden = true
|
||||
actionButton.layer.sublayerTransform = CATransform3DMakeTranslation(slideOffset, 0.0, 0.0)
|
||||
return
|
||||
} else if self.isButtonHidden {
|
||||
leftButton.isHidden = true
|
||||
rightButton.isHidden = true
|
||||
actionButton.isHidden = true
|
||||
return
|
||||
}
|
||||
|
||||
let center = CGPoint(x: actionButton.frame.width / 2.0, y: actionButton.frame.height / 2.0)
|
||||
leftButton.layer.animatePosition(from: leftButton.position, to: center, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak leftButton] _ in
|
||||
leftButton?.isHidden = true
|
||||
leftButton?.textNode.layer.removeAllAnimations()
|
||||
leftButton?.layer.removeAllAnimations()
|
||||
})
|
||||
leftButton.layer.animateScale(from: 1.0, to: 0.5, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
rightButton.layer.animatePosition(from: rightButton.position, to: center, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak rightButton] _ in
|
||||
rightButton?.isHidden = true
|
||||
rightButton?.textNode.layer.removeAllAnimations()
|
||||
rightButton?.layer.removeAllAnimations()
|
||||
})
|
||||
rightButton.layer.animateScale(from: 1.0, to: 0.5, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
leftButton.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false)
|
||||
rightButton.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false)
|
||||
|
||||
let targetPosition = actionButton.position
|
||||
let sourcePoint = CGPoint(x: from.midX, y: from.midY)
|
||||
let midPoint = CGPoint(x: (sourcePoint.x + targetPosition.x) / 2.0, y: sourcePoint.y + 120.0)
|
||||
let midPoint = CGPoint(x: (sourcePoint.x + targetPosition.x) / 2.0, y: sourcePoint.y + 90.0)
|
||||
|
||||
let x1 = sourcePoint.x
|
||||
let y1 = sourcePoint.y
|
||||
@ -125,12 +161,14 @@ public final class VoiceChatOverlayController: ViewController {
|
||||
}
|
||||
|
||||
private var animating = false
|
||||
private var dismissed = false
|
||||
func animateOut(reclaim: Bool, completion: @escaping (Bool) -> Void) {
|
||||
guard let actionButton = self.controller?.actionButton, let layout = self.validLayout else {
|
||||
guard let actionButton = self.controller?.actionButton, let leftButton = self.controller?.audioOutputNode, let rightButton = self.controller?.leaveNode, let layout = self.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
if reclaim {
|
||||
self.dismissed = true
|
||||
let targetPosition = CGPoint(x: layout.size.width / 2.0, y: layout.size.height - layout.intrinsicInsets.bottom - 268.0 / 2.0)
|
||||
if self.isSlidOffscreen {
|
||||
self.isSlidOffscreen = false
|
||||
@ -138,38 +176,67 @@ public final class VoiceChatOverlayController: ViewController {
|
||||
actionButton.layer.sublayerTransform = CATransform3DIdentity
|
||||
actionButton.update(snap: false, animated: false)
|
||||
actionButton.position = CGPoint(x: targetPosition.x, y: 268.0 / 2.0)
|
||||
|
||||
leftButton.isHidden = false
|
||||
rightButton.isHidden = false
|
||||
if let leftButtonPosition = self.initialLeftButtonPosition {
|
||||
leftButton.position = CGPoint(x: actionButton.position.x + leftButtonPosition.x, y: actionButton.position.y)
|
||||
}
|
||||
if let rightButtonPosition = self.initialRightButtonPosition {
|
||||
rightButton.position = CGPoint(x: actionButton.position.x + rightButtonPosition.x, y: actionButton.position.y)
|
||||
}
|
||||
completion(true)
|
||||
} else if self.isButtonHidden {
|
||||
actionButton.isHidden = false
|
||||
actionButton.layer.removeAllAnimations()
|
||||
actionButton.layer.sublayerTransform = CATransform3DIdentity
|
||||
actionButton.update(snap: false, animated: false)
|
||||
actionButton.position = CGPoint(x: targetPosition.x, y: 268.0 / 2.0)
|
||||
|
||||
leftButton.isHidden = false
|
||||
rightButton.isHidden = false
|
||||
if let leftButtonPosition = self.initialLeftButtonPosition {
|
||||
leftButton.position = CGPoint(x: actionButton.position.x + leftButtonPosition.x, y: actionButton.position.y)
|
||||
}
|
||||
if let rightButtonPosition = self.initialRightButtonPosition {
|
||||
rightButton.position = CGPoint(x: actionButton.position.x + rightButtonPosition.x, y: actionButton.position.y)
|
||||
}
|
||||
completion(true)
|
||||
} else {
|
||||
self.animating = true
|
||||
|
||||
let sourcePoint = actionButton.position
|
||||
var midPoint = CGPoint(x: (sourcePoint.x + targetPosition.x) / 2.0 - 60.0, y: sourcePoint.y)
|
||||
if sourcePoint.y < layout.size.height - 100.0 {
|
||||
midPoint.x = (sourcePoint.x + targetPosition.x) / 2.0 + 30.0
|
||||
midPoint.y = (sourcePoint.y + targetPosition.y) / 2.0 + 40.0
|
||||
}
|
||||
|
||||
let x1 = sourcePoint.x
|
||||
let y1 = sourcePoint.y
|
||||
let x2 = midPoint.x
|
||||
let y2 = midPoint.y
|
||||
let x3 = targetPosition.x
|
||||
let y3 = targetPosition.y
|
||||
|
||||
let a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
|
||||
let b = (x1 * x1 * (y2 - y3) + x3 * x3 * (y1 - y2) + x2 * x2 * (y3 - y1)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
|
||||
let c = (x2 * x2 * (x3 * y1 - x1 * y3) + x2 * (x1 * x1 * y3 - x3 * x3 * y1) + x1 * x3 * (x3 - x1) * y2) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
|
||||
|
||||
var keyframes: [AnyObject] = []
|
||||
for i in 0 ..< 10 {
|
||||
let k = CGFloat(i) / CGFloat(10 - 1)
|
||||
let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
|
||||
let y = a * x * x + b * x + c
|
||||
keyframes.append(NSValue(cgPoint: CGPoint(x: x, y: y)))
|
||||
let transitionNode = ASDisplayNode()
|
||||
transitionNode.position = sourcePoint
|
||||
transitionNode.addSubnode(actionButton)
|
||||
actionButton.position = CGPoint()
|
||||
self.addSubnode(transitionNode)
|
||||
|
||||
if let leftButtonPosition = self.initialLeftButtonPosition, let rightButtonPosition = self.initialRightButtonPosition {
|
||||
let center = CGPoint(x: actionButton.frame.width / 2.0, y: actionButton.frame.height / 2.0)
|
||||
|
||||
leftButton.isHidden = false
|
||||
rightButton.isHidden = false
|
||||
|
||||
leftButton.layer.animatePosition(from: center, to: leftButtonPosition, duration: 0.26, delay: 0.07, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false)
|
||||
rightButton.layer.animatePosition(from: center, to: rightButtonPosition, duration: 0.26, delay: 0.07, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false)
|
||||
|
||||
leftButton.layer.animateScale(from: 0.55, to: 1.0, duration: 0.26, delay: 0.06, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
rightButton.layer.animateScale(from: 0.55, to: 1.0, duration: 0.26, delay: 0.06, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
|
||||
leftButton.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, delay: 0.05)
|
||||
rightButton.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, delay: 0.05)
|
||||
}
|
||||
|
||||
actionButton.update(snap: false, animated: true)
|
||||
actionButton.position = targetPosition
|
||||
actionButton.layer.animateKeyframes(values: keyframes, duration: 0.34, keyPath: "position", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, completion: { _ in
|
||||
actionButton.position = CGPoint(x: targetPosition.x - sourcePoint.x, y: 80.0)
|
||||
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||
transition.animateView {
|
||||
transitionNode.position = CGPoint(x: transitionNode.position.x, y: targetPosition.y - 80.0)
|
||||
}
|
||||
|
||||
actionButton.layer.animatePosition(from: CGPoint(), to: actionButton.position, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, completion: { _ in
|
||||
self.animating = false
|
||||
completion(false)
|
||||
})
|
||||
@ -193,76 +260,79 @@ public final class VoiceChatOverlayController: ViewController {
|
||||
return nil
|
||||
}
|
||||
|
||||
private var didAnimateIn = false
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = layout
|
||||
|
||||
if let actionButton = self.controller?.actionButton, !self.animating {
|
||||
if let actionButton = self.controller?.actionButton, let leftButton = self.controller?.audioOutputNode, let rightButton = self.controller?.leaveNode, !self.animating && !self.dismissed {
|
||||
let convertedRect = actionButton.view.convert(actionButton.bounds, to: self.view)
|
||||
let insets = layout.insets(options: [.input])
|
||||
let insets = layout.insets(options: [.input])
|
||||
|
||||
if !self.didAnimateIn {
|
||||
let leftButtonFrame = leftButton.view.convert(leftButton.bounds, to: actionButton.bottomNode.view)
|
||||
actionButton.bottomNode.addSubnode(leftButton)
|
||||
leftButton.frame = leftButtonFrame
|
||||
|
||||
let rightButtonFrame = rightButton.view.convert(rightButton.bounds, to: actionButton.bottomNode.view)
|
||||
actionButton.bottomNode.addSubnode(rightButton)
|
||||
rightButton.frame = rightButtonFrame
|
||||
}
|
||||
|
||||
transition.updatePosition(node: actionButton, position: CGPoint(x: layout.size.width - layout.safeInsets.right - 21.0, y: layout.size.height - insets.bottom - 22.0))
|
||||
|
||||
if actionButton.supernode !== self {
|
||||
if actionButton.supernode !== self && !self.didAnimateIn {
|
||||
self.didAnimateIn = true
|
||||
actionButton.ignoreHierarchyChanges = true
|
||||
self.addSubnode(actionButton)
|
||||
|
||||
var hidden = false
|
||||
if let initiallyHidden = self.controller?.initiallyHidden, initiallyHidden {
|
||||
hidden = initiallyHidden
|
||||
}
|
||||
if hidden {
|
||||
self.update(hidden: true, slide: true, animated: false)
|
||||
}
|
||||
self.animateIn(from: convertedRect)
|
||||
if hidden {
|
||||
self.controller?.setupVisibilityUpdates()
|
||||
}
|
||||
actionButton.ignoreHierarchyChanges = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private weak var actionButton: VoiceChatActionButton?
|
||||
private weak var audioOutputNode: CallControllerButtonItemNode?
|
||||
private weak var leaveNode: CallControllerButtonItemNode?
|
||||
|
||||
private var controllerNode: Node {
|
||||
return self.displayNode as! Node
|
||||
}
|
||||
|
||||
private var disposable: Disposable?
|
||||
|
||||
init(actionButton: VoiceChatActionButton, navigationController: NavigationController?) {
|
||||
|
||||
private weak var parentNavigationController: NavigationController?
|
||||
private var currentParams: ([UIViewController], [UIViewController], VoiceChatActionButton.State)?
|
||||
fileprivate var initiallyHidden: Bool
|
||||
|
||||
init(actionButton: VoiceChatActionButton, audioOutputNode: CallControllerButtonItemNode, leaveNode: CallControllerButtonItemNode, navigationController: NavigationController?, initiallyHidden: Bool) {
|
||||
self.actionButton = actionButton
|
||||
self.audioOutputNode = audioOutputNode
|
||||
self.leaveNode = leaveNode
|
||||
self.parentNavigationController = navigationController
|
||||
self.initiallyHidden = initiallyHidden
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
self.additionalSideInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 75.0)
|
||||
|
||||
if let navigationController = navigationController {
|
||||
let controllers: Signal<[UIViewController], NoError> = .single([])
|
||||
|> then(navigationController.viewControllersSignal)
|
||||
let overlayControllers: Signal<[UIViewController], NoError> = .single([])
|
||||
|> then(navigationController.overlayControllersSignal)
|
||||
|
||||
self.disposable = (combineLatest(queue: Queue.mainQueue(), controllers, overlayControllers)).start(next: { [weak self] controllers, overlayControllers in
|
||||
if let strongSelf = self {
|
||||
var hasVoiceChatController = false
|
||||
var overlayControllersCount = 0
|
||||
for controller in controllers {
|
||||
if controller is VoiceChatController {
|
||||
hasVoiceChatController = true
|
||||
}
|
||||
}
|
||||
for controller in overlayControllers {
|
||||
if controller is TooltipController || controller is TooltipScreen || controller is AlertController {
|
||||
} else {
|
||||
overlayControllersCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
var hidden = true
|
||||
var animated = true
|
||||
if controllers.count == 1 || controllers.last is ChatController {
|
||||
hidden = false
|
||||
}
|
||||
if overlayControllersCount > 0 {
|
||||
hidden = true
|
||||
}
|
||||
if hasVoiceChatController {
|
||||
hidden = false
|
||||
animated = false
|
||||
}
|
||||
strongSelf.controllerNode.update(hidden: hidden, slide: true, animated: animated)
|
||||
}
|
||||
})
|
||||
if case .active(.cantSpeak) = actionButton.stateValue {
|
||||
} else if !initiallyHidden {
|
||||
self.additionalSideInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 75.0)
|
||||
}
|
||||
|
||||
if !self.initiallyHidden {
|
||||
self.setupVisibilityUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,6 +349,22 @@ public final class VoiceChatOverlayController: ViewController {
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
private func setupVisibilityUpdates() {
|
||||
if let navigationController = self.parentNavigationController, let actionButton = self.actionButton {
|
||||
let controllers: Signal<[UIViewController], NoError> = .single([])
|
||||
|> then(navigationController.viewControllersSignal)
|
||||
let overlayControllers: Signal<[UIViewController], NoError> = .single([])
|
||||
|> then(navigationController.overlayControllersSignal)
|
||||
|
||||
self.disposable = (combineLatest(queue: Queue.mainQueue(), controllers, overlayControllers, actionButton.state)).start(next: { [weak self] controllers, overlayControllers, state in
|
||||
if let strongSelf = self {
|
||||
strongSelf.currentParams = (controllers, overlayControllers, state)
|
||||
strongSelf.updateVisibility()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public override func dismiss(completion: (() -> Void)? = nil) {
|
||||
super.dismiss(completion: completion)
|
||||
self.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
@ -289,7 +375,69 @@ public final class VoiceChatOverlayController: ViewController {
|
||||
self.controllerNode.animateOut(reclaim: reclaim, completion: completion)
|
||||
}
|
||||
|
||||
public func updateVisibility() {
|
||||
guard let (controllers, overlayControllers, state) = self.currentParams else {
|
||||
return
|
||||
}
|
||||
var hasVoiceChatController = false
|
||||
var overlayControllersCount = 0
|
||||
for controller in controllers {
|
||||
if controller is VoiceChatController {
|
||||
hasVoiceChatController = true
|
||||
}
|
||||
}
|
||||
for controller in overlayControllers {
|
||||
if controller is TooltipController || controller is TooltipScreen || controller is AlertController {
|
||||
} else {
|
||||
overlayControllersCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
var slide = true
|
||||
var hidden = true
|
||||
var animated = true
|
||||
var animateInsets = true
|
||||
if controllers.count == 1 || controllers.last is ChatController {
|
||||
if let chatController = controllers.last as? ChatController {
|
||||
slide = false
|
||||
if !chatController.isSendButtonVisible {
|
||||
hidden = false
|
||||
}
|
||||
} else {
|
||||
hidden = false
|
||||
}
|
||||
}
|
||||
if let tabBarController = controllers.last as? TabBarController {
|
||||
if let chatListController = tabBarController.controllers[tabBarController.selectedIndex] as? ChatListController, chatListController.isSearchActive {
|
||||
hidden = true
|
||||
}
|
||||
}
|
||||
if overlayControllersCount > 0 {
|
||||
hidden = true
|
||||
}
|
||||
|
||||
if case .active(.cantSpeak) = state {
|
||||
hidden = true
|
||||
}
|
||||
if hasVoiceChatController {
|
||||
hidden = false
|
||||
animated = self.initiallyHidden
|
||||
self.initiallyHidden = false
|
||||
}
|
||||
|
||||
self.controllerNode.update(hidden: hidden, slide: slide, animated: animated)
|
||||
|
||||
let previousInsets = self.additionalSideInsets
|
||||
self.additionalSideInsets = hidden ? UIEdgeInsets() : UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 75.0)
|
||||
|
||||
if previousInsets != self.additionalSideInsets {
|
||||
self.parentNavigationController?.requestLayout(transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
private let hiddenPromise = ValuePromise<Bool>()
|
||||
public func update(hidden: Bool, slide: Bool, animated: Bool) {
|
||||
self.hiddenPromise.set(hidden)
|
||||
self.controllerNode.update(hidden: hidden, slide: slide, animated: animated)
|
||||
}
|
||||
|
||||
|
@ -169,7 +169,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
private var peerPresenceManager: PeerPresenceStatusManager?
|
||||
private var layoutParams: (VoiceChatParticipantItem, ListViewItemLayoutParams, Bool, Bool)?
|
||||
|
||||
private var wavesColor: UIColor?
|
||||
|
||||
init() {
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
@ -296,8 +297,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
let statusFontSize: CGFloat = floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)
|
||||
|
||||
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
||||
let statusFont = Font.regular(statusFontSize)
|
||||
let titleFont = Font.regular(17.0)
|
||||
let statusFont = Font.regular(14.0)
|
||||
|
||||
var titleAttributedString: NSAttributedString?
|
||||
var statusAttributedString: NSAttributedString?
|
||||
@ -334,6 +335,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
titleAttributedString = NSAttributedString(string: channel.title, font: currentBoldFont, textColor: titleColor)
|
||||
}
|
||||
|
||||
var wavesColor = UIColor(rgb: 0x34c759)
|
||||
switch item.text {
|
||||
case .presence:
|
||||
if let user = item.peer as? TelegramUser, let botInfo = user.botInfo {
|
||||
@ -358,6 +360,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
textColorValue = item.presentationData.theme.list.itemSecondaryTextColor
|
||||
case .accent:
|
||||
textColorValue = item.presentationData.theme.list.itemAccentColor
|
||||
wavesColor = textColorValue
|
||||
case .constructive:
|
||||
textColorValue = UIColor(rgb: 0x34c759)
|
||||
}
|
||||
@ -435,6 +438,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
return (layout, { [weak self] synchronousLoad, animated in
|
||||
if let strongSelf = self {
|
||||
strongSelf.layoutParams = (item, params, first, last)
|
||||
strongSelf.wavesColor = wavesColor
|
||||
|
||||
let nonExtractedRect = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width - 16.0, height: layout.contentSize.height))
|
||||
let extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: 16.0 + params.leftInset, dy: 0.0)
|
||||
@ -561,7 +565,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
playbackMaskLayer.path = maskPath.cgPath
|
||||
audioLevelView.layer.mask = playbackMaskLayer
|
||||
|
||||
audioLevelView.setColor(UIColor(rgb: 0x34c759))
|
||||
audioLevelView.setColor(wavesColor)
|
||||
strongSelf.audioLevelView = audioLevelView
|
||||
strongSelf.offsetContainerNode.view.insertSubview(audioLevelView, at: 0)
|
||||
}
|
||||
@ -574,6 +578,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
if value > 0.0 {
|
||||
audioLevelView.startAnimating()
|
||||
avatarScale = 1.03 + level * 0.13
|
||||
if let wavesColor = strongSelf.wavesColor {
|
||||
audioLevelView.setColor(wavesColor, animated: true)
|
||||
}
|
||||
} else {
|
||||
audioLevelView.stopAnimating(duration: 0.5)
|
||||
avatarScale = 1.0
|
||||
|
@ -168,6 +168,7 @@ private var declaredEncodables: Void = {
|
||||
declareEncodable(Country.CountryCode.self, f: { Country.CountryCode(decoder: $0) })
|
||||
declareEncodable(CountriesList.self, f: { CountriesList(decoder: $0) })
|
||||
declareEncodable(ValidationMessageAttribute.self, f: { ValidationMessageAttribute(decoder: $0) })
|
||||
declareEncodable(EmojiSearchQueryMessageAttribute.self, f: { EmojiSearchQueryMessageAttribute(decoder: $0) })
|
||||
|
||||
return
|
||||
}()
|
||||
|
@ -1001,6 +1001,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
||||
updatedState.readOutbox(MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), namespace: Namespaces.Message.Cloud, id: maxId), timestamp: nil)
|
||||
case let .updateChannel(channelId):
|
||||
updatedState.addExternallyUpdatedPeerId(PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId))
|
||||
case let .updateChat(chatId):
|
||||
updatedState.addExternallyUpdatedPeerId(PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId))
|
||||
case let .updateReadHistoryInbox(_, folderId, peer, maxId, stillUnreadCount, pts, _):
|
||||
updatedState.resetIncomingReadState(groupId: PeerGroupId(rawValue: folderId ?? 0), peerId: peer.peerId, namespace: Namespaces.Message.Cloud, maxIncomingReadId: maxId, count: stillUnreadCount, pts: pts)
|
||||
case let .updateReadHistoryOutbox(peer, maxId, _, _):
|
||||
@ -1334,6 +1336,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
||||
}
|
||||
case let .updateGroupCall(channelId, call):
|
||||
updatedState.updateGroupCall(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), call: call)
|
||||
updatedState.updateGroupCall(peerId: PeerId(namespace: Namespaces.Peer.CloudGroup, id: channelId), call: call)
|
||||
case let .updateLangPackTooLong(langCode):
|
||||
updatedState.updateLangPack(langCode: langCode, difference: nil)
|
||||
case let .updateLangPack(difference):
|
||||
@ -2966,6 +2969,8 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
if let current = current as? CachedChannelData {
|
||||
return current.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash))
|
||||
} else if let current = current as? CachedGroupData {
|
||||
return current.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash))
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
@ -2997,6 +3002,12 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
} else if let current = current as? CachedGroupData {
|
||||
if let activeCall = current.activeCall, activeCall.id == callId {
|
||||
return current.withUpdatedActiveCall(nil)
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
|
@ -224,10 +224,10 @@ public func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: Pe
|
||||
action = .endGroupCall
|
||||
case let .channelAdminLogEventActionParticipantMute(participant):
|
||||
let parsedParticipant = GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(participant)
|
||||
action = .groupCallUpdateParticipantMuteStatus(peerId: parsedParticipant.peerId, isMuted: parsedParticipant.muteState != nil)
|
||||
action = .groupCallUpdateParticipantMuteStatus(peerId: parsedParticipant.peerId, isMuted: true)
|
||||
case let .channelAdminLogEventActionParticipantUnmute(participant):
|
||||
let parsedParticipant = GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(participant)
|
||||
action = .groupCallUpdateParticipantMuteStatus(peerId: parsedParticipant.peerId, isMuted: parsedParticipant.muteState != nil)
|
||||
action = .groupCallUpdateParticipantMuteStatus(peerId: parsedParticipant.peerId, isMuted: false)
|
||||
case let .channelAdminLogEventActionToggleGroupCallSetting(joinMuted):
|
||||
action = .updateGroupCallSettings(joinMuted: joinMuted == .boolTrue)
|
||||
}
|
||||
|
@ -93,6 +93,8 @@ private func filterMessageAttributesForOutgoingMessage(_ attributes: [MessageAtt
|
||||
return true
|
||||
case _ as EmbeddedMediaStickersMessageAttribute:
|
||||
return true
|
||||
case _ as EmojiSearchQueryMessageAttribute:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
@ -126,8 +126,8 @@ public enum CreateGroupCallError {
|
||||
}
|
||||
|
||||
public func createGroupCall(account: Account, peerId: PeerId) -> Signal<GroupCallInfo, CreateGroupCallError> {
|
||||
return account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputChannel)
|
||||
return account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
}
|
||||
|> castError(CreateGroupCallError.self)
|
||||
|> mapToSignal { inputPeer -> Signal<GroupCallInfo, CreateGroupCallError> in
|
||||
@ -135,7 +135,7 @@ public func createGroupCall(account: Account, peerId: PeerId) -> Signal<GroupCal
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.phone.createGroupCall(channel: inputPeer, randomId: Int32.random(in: Int32.min ... Int32.max)))
|
||||
return account.network.request(Api.functions.phone.createGroupCall(peer: inputPeer, randomId: Int32.random(in: Int32.min ... Int32.max)))
|
||||
|> mapError { error -> CreateGroupCallError in
|
||||
if error.errorDescription == "ANONYMOUS_CALLS_DISABLED" {
|
||||
return .anonymousNotAllowed
|
||||
@ -162,6 +162,8 @@ public func createGroupCall(account: Account, peerId: PeerId) -> Signal<GroupCal
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
|
||||
if let cachedData = cachedData as? CachedChannelData {
|
||||
return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: callInfo.id, accessHash: callInfo.accessHash))
|
||||
} else if let cachedData = cachedData as? CachedGroupData {
|
||||
return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: callInfo.id, accessHash: callInfo.accessHash))
|
||||
} else {
|
||||
return cachedData
|
||||
}
|
||||
@ -283,23 +285,71 @@ public func joinGroupCall(account: Account, peerId: PeerId, callId: Int64, acces
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<JoinGroupCallResult, JoinGroupCallError> in
|
||||
let admins = account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputChannel)
|
||||
}
|
||||
|> castError(JoinGroupCallError.self)
|
||||
|> mapToSignal { inputChannel -> Signal<Api.channels.ChannelParticipants, JoinGroupCallError> in
|
||||
guard let inputChannel = inputChannel else {
|
||||
return .fail(.generic)
|
||||
|
||||
let admins: Signal<(Set<PeerId>, [Api.User]), JoinGroupCallError>
|
||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
admins = account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputChannel)
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: .channelParticipantsAdmins, offset: 0, limit: 100, hash: 0))
|
||||
|> mapError { _ -> JoinGroupCallError in
|
||||
return .generic
|
||||
|> castError(JoinGroupCallError.self)
|
||||
|> mapToSignal { inputChannel -> Signal<Api.channels.ChannelParticipants, JoinGroupCallError> in
|
||||
guard let inputChannel = inputChannel else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: .channelParticipantsAdmins, offset: 0, limit: 100, hash: 0))
|
||||
|> mapError { _ -> JoinGroupCallError in
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> map { admins -> (Set<PeerId>, [Api.User]) in
|
||||
var adminIds = Set<PeerId>()
|
||||
var apiUsers: [Api.User] = []
|
||||
|
||||
switch admins {
|
||||
case let .channelParticipants(_, participants, users):
|
||||
apiUsers.append(contentsOf: users)
|
||||
|
||||
for participant in participants {
|
||||
let parsedParticipant = ChannelParticipant(apiParticipant: participant)
|
||||
switch parsedParticipant {
|
||||
case .creator:
|
||||
adminIds.insert(parsedParticipant.peerId)
|
||||
case let .member(_, _, adminInfo, _, _):
|
||||
if let adminInfo = adminInfo, adminInfo.rights.flags.contains(.canManageCalls) {
|
||||
adminIds.insert(parsedParticipant.peerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return (adminIds, apiUsers)
|
||||
}
|
||||
} else if peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||
admins = account.postbox.transaction { transaction -> (Set<PeerId>, [Api.User]) in
|
||||
var result = Set<PeerId>()
|
||||
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedGroupData {
|
||||
if let participants = cachedData.participants {
|
||||
for participant in participants.participants {
|
||||
if case .creator = participant {
|
||||
result.insert(participant.peerId)
|
||||
} else if case .admin = participant {
|
||||
result.insert(participant.peerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (result, [])
|
||||
}
|
||||
|> castError(JoinGroupCallError.self)
|
||||
} else {
|
||||
admins = .fail(.generic)
|
||||
}
|
||||
|
||||
let channel = account.postbox.transaction { transaction -> TelegramChannel? in
|
||||
return transaction.getPeer(peerId) as? TelegramChannel
|
||||
let peer = account.postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(peerId)
|
||||
}
|
||||
|> castError(JoinGroupCallError.self)
|
||||
|
||||
@ -313,15 +363,23 @@ public func joinGroupCall(account: Account, peerId: PeerId, callId: Int64, acces
|
||||
return .generic
|
||||
},
|
||||
admins,
|
||||
channel
|
||||
peer
|
||||
)
|
||||
|> mapToSignal { result, state, admins, channel -> Signal<JoinGroupCallResult, JoinGroupCallError> in
|
||||
guard let channel = channel else {
|
||||
|> mapToSignal { result, state, admins, peer -> Signal<JoinGroupCallResult, JoinGroupCallError> in
|
||||
guard let peer = peer else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
var state = state
|
||||
state.isCreator = channel.flags.contains(.isCreator)
|
||||
if let channel = peer as? TelegramChannel {
|
||||
state.isCreator = channel.flags.contains(.isCreator)
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
if case .creator = group.role {
|
||||
state.isCreator = true
|
||||
} else {
|
||||
state.isCreator = false
|
||||
}
|
||||
}
|
||||
|
||||
account.stateManager.addUpdates(updates)
|
||||
|
||||
@ -351,26 +409,9 @@ public func joinGroupCall(account: Account, peerId: PeerId, callId: Int64, acces
|
||||
}
|
||||
|
||||
var apiUsers: [Api.User] = []
|
||||
var adminIds = Set<PeerId>()
|
||||
|
||||
switch admins {
|
||||
case let .channelParticipants(_, participants, users):
|
||||
apiUsers.append(contentsOf: users)
|
||||
|
||||
for participant in participants {
|
||||
let parsedParticipant = ChannelParticipant(apiParticipant: participant)
|
||||
switch parsedParticipant {
|
||||
case .creator:
|
||||
adminIds.insert(parsedParticipant.peerId)
|
||||
case let .member(_, _, adminInfo, _, _):
|
||||
if let adminInfo = adminInfo, adminInfo.rights.flags.contains(.canManageCalls) {
|
||||
adminIds.insert(parsedParticipant.peerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
let (adminIds, adminUsers) = admins
|
||||
apiUsers.append(contentsOf: adminUsers)
|
||||
|
||||
state.adminIds = adminIds
|
||||
|
||||
@ -440,6 +481,8 @@ public func stopGroupCall(account: Account, peerId: PeerId, callId: Int64, acces
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
|
||||
if let cachedData = cachedData as? CachedChannelData {
|
||||
return cachedData.withUpdatedActiveCall(nil)
|
||||
} else if let cachedData = cachedData as? CachedGroupData {
|
||||
return cachedData.withUpdatedActiveCall(nil)
|
||||
} else {
|
||||
return cachedData
|
||||
}
|
||||
@ -955,6 +998,8 @@ public final class GroupCallParticipantsContext {
|
||||
}
|
||||
}
|
||||
|
||||
updatedTotalCount = max(updatedTotalCount, updatedParticipants.count)
|
||||
|
||||
var updatedOverlayState = strongSelf.stateValue.overlayState
|
||||
for peerId in update.removePendingMuteStates {
|
||||
updatedOverlayState.pendingMuteStateChanges.removeValue(forKey: peerId)
|
||||
|
@ -126,13 +126,23 @@ func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods:
|
||||
}
|
||||
|> mapToSignal { validatedResource -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
if let validatedResource = validatedResource.updatedResource as? TelegramCloudMediaResourceWithFileReference, let reference = validatedResource.fileReference {
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: 0, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: reference)), ttlSeconds: nil), text), reuploadInfo: nil)))
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: 0, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: reference)), ttlSeconds: nil, query: nil), text), reuploadInfo: nil)))
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: 0, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), ttlSeconds: nil), text), reuploadInfo: nil)))
|
||||
|
||||
var flags: Int32 = 0
|
||||
var emojiSearchQuery: String?
|
||||
for attribute in attributes {
|
||||
if let attribute = attribute as? EmojiSearchQueryMessageAttribute {
|
||||
emojiSearchQuery = attribute.query
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
}
|
||||
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), ttlSeconds: nil, query: emojiSearchQuery), text), reuploadInfo: nil)))
|
||||
}
|
||||
} else {
|
||||
return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: forceReupload, isGrouped: isGrouped, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file)
|
||||
@ -587,7 +597,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
||||
if !forceReupload, let file = media as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference {
|
||||
return .single(.progress(1.0))
|
||||
|> then(
|
||||
.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: 0, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil), text), reuploadInfo: nil)))
|
||||
.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: 0, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil, query: nil), text), reuploadInfo: nil)))
|
||||
)
|
||||
}
|
||||
case let .localReference(key):
|
||||
@ -771,7 +781,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
||||
switch result {
|
||||
case let .messageMediaDocument(_, document, _):
|
||||
if let document = document, let mediaFile = telegramMediaFileFromApiDocument(document), let resource = mediaFile.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference {
|
||||
return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaDocument(flags: 0, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil), text), reuploadInfo: nil)), media: mediaFile)
|
||||
return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaDocument(flags: 0, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil, query: nil), text), reuploadInfo: nil)), media: mediaFile)
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
@ -5,39 +5,44 @@ import SwiftSignalKit
|
||||
import SyncCore
|
||||
import MtProtoKit
|
||||
|
||||
func telegramStickerPackThumbnailRepresentationFromApiSize(datacenterId: Int32, size: Api.PhotoSize) -> TelegramMediaImageRepresentation? {
|
||||
switch size {
|
||||
case let .photoCachedSize(_, location, w, h, _):
|
||||
switch location {
|
||||
case let .fileLocationToBeDeprecated(volumeId, localId):
|
||||
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, volumeId: volumeId, localId: localId)
|
||||
return TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [])
|
||||
}
|
||||
case let .photoSize(_, location, w, h, _):
|
||||
switch location {
|
||||
case let .fileLocationToBeDeprecated(volumeId, localId):
|
||||
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, volumeId: volumeId, localId: localId)
|
||||
return TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [])
|
||||
}
|
||||
case let .photoSizeProgressive(_, location, w, h, sizes):
|
||||
switch location {
|
||||
case let .fileLocationToBeDeprecated(volumeId, localId):
|
||||
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, volumeId: volumeId, localId: localId)
|
||||
return TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: sizes)
|
||||
}
|
||||
case let .photoPathSize(_, data):
|
||||
return nil
|
||||
case .photoStrippedSize:
|
||||
return nil
|
||||
case .photoSizeEmpty:
|
||||
return nil
|
||||
func telegramStickerPackThumbnailRepresentationFromApiSizes(datacenterId: Int32, sizes: [Api.PhotoSize]) -> (immediateThumbnail: Data?, representations: [TelegramMediaImageRepresentation]) {
|
||||
var immediateThumbnailData: Data?
|
||||
var representations: [TelegramMediaImageRepresentation] = []
|
||||
for size in sizes {
|
||||
switch size {
|
||||
case let .photoCachedSize(_, location, w, h, _):
|
||||
switch location {
|
||||
case let .fileLocationToBeDeprecated(volumeId, localId):
|
||||
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, volumeId: volumeId, localId: localId)
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: []))
|
||||
}
|
||||
case let .photoSize(_, location, w, h, _):
|
||||
switch location {
|
||||
case let .fileLocationToBeDeprecated(volumeId, localId):
|
||||
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, volumeId: volumeId, localId: localId)
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: []))
|
||||
}
|
||||
case let .photoSizeProgressive(_, location, w, h, sizes):
|
||||
switch location {
|
||||
case let .fileLocationToBeDeprecated(volumeId, localId):
|
||||
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, volumeId: volumeId, localId: localId)
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: sizes))
|
||||
}
|
||||
case let .photoPathSize(_, data):
|
||||
immediateThumbnailData = data.makeData()
|
||||
case .photoStrippedSize:
|
||||
break
|
||||
case .photoSizeEmpty:
|
||||
break
|
||||
}
|
||||
}
|
||||
return (immediateThumbnailData, representations)
|
||||
}
|
||||
|
||||
extension StickerPackCollectionInfo {
|
||||
convenience init(apiSet: Api.StickerSet, namespace: ItemCollectionId.Namespace) {
|
||||
switch apiSet {
|
||||
case let .stickerSet(flags, _, id, accessHash, title, shortName, thumb, thumbDcId, count, nHash):
|
||||
case let .stickerSet(flags, _, id, accessHash, title, shortName, thumbs, thumbDcId, count, nHash):
|
||||
var setFlags: StickerPackCollectionInfoFlags = StickerPackCollectionInfoFlags()
|
||||
if (flags & (1 << 2)) != 0 {
|
||||
setFlags.insert(.isOfficial)
|
||||
@ -50,11 +55,14 @@ extension StickerPackCollectionInfo {
|
||||
}
|
||||
|
||||
var thumbnailRepresentation: TelegramMediaImageRepresentation?
|
||||
if let thumb = thumb, let thumbDcId = thumbDcId {
|
||||
thumbnailRepresentation = telegramStickerPackThumbnailRepresentationFromApiSize(datacenterId: thumbDcId, size: thumb)
|
||||
var immediateThumbnailData: Data?
|
||||
if let thumbs = thumbs, let thumbDcId = thumbDcId {
|
||||
let (data, representations) = telegramStickerPackThumbnailRepresentationFromApiSizes(datacenterId: thumbDcId, sizes: thumbs)
|
||||
thumbnailRepresentation = representations.first
|
||||
immediateThumbnailData = data
|
||||
}
|
||||
|
||||
self.init(id: ItemCollectionId(namespace: namespace, id: id), flags: setFlags, accessHash: accessHash, title: title, shortName: shortName, thumbnail: thumbnailRepresentation, hash: nHash, count: count)
|
||||
self.init(id: ItemCollectionId(namespace: namespace, id: id), flags: setFlags, accessHash: accessHash, title: title, shortName: shortName, thumbnail: thumbnailRepresentation, immediateThumbnailData: immediateThumbnailData, hash: nHash, count: count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ public func addStickerPackInteractively(postbox: Postbox, info: StickerPackColle
|
||||
if let namespace = namespace {
|
||||
var mappedInfo = info
|
||||
if items.isEmpty {
|
||||
mappedInfo = StickerPackCollectionInfo(id: info.id, flags: info.flags, accessHash: info.accessHash, title: info.title, shortName: info.shortName, thumbnail: info.thumbnail, hash: Int32(bitPattern: arc4random()), count: info.count)
|
||||
mappedInfo = StickerPackCollectionInfo(id: info.id, flags: info.flags, accessHash: info.accessHash, title: info.title, shortName: info.shortName, thumbnail: info.thumbnail, immediateThumbnailData: info.immediateThumbnailData, hash: Int32(bitPattern: arc4random()), count: info.count)
|
||||
}
|
||||
addSynchronizeInstalledStickerPacksOperation(transaction: transaction, namespace: namespace, content: .add([mappedInfo.id]), noDelay: items.isEmpty)
|
||||
var updatedInfos = transaction.getItemCollectionsInfos(namespace: mappedInfo.id.namespace).map { $0.1 as! StickerPackCollectionInfo }
|
||||
|
@ -292,6 +292,14 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
||||
hasScheduledMessages = true
|
||||
}
|
||||
|
||||
var updatedActiveCall: CachedChannelData.ActiveCall?
|
||||
if let inputCall = chatFull.call {
|
||||
switch inputCall {
|
||||
case let .inputGroupCall(id, accessHash):
|
||||
updatedActiveCall = CachedChannelData.ActiveCall(id: id, accessHash: accessHash)
|
||||
}
|
||||
}
|
||||
|
||||
transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current in
|
||||
let previous: CachedGroupData
|
||||
if let current = current as? CachedGroupData {
|
||||
@ -309,6 +317,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
||||
.withUpdatedHasScheduledMessages(hasScheduledMessages)
|
||||
.withUpdatedInvitedBy(invitedBy)
|
||||
.withUpdatedPhoto(photo)
|
||||
.withUpdatedActiveCall(updatedActiveCall)
|
||||
})
|
||||
case .channelFull:
|
||||
break
|
||||
|
@ -251,6 +251,8 @@ extension Api.Update {
|
||||
switch self {
|
||||
case let .updateChannel(channelId):
|
||||
return [PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)]
|
||||
case let .updateChat(chatId):
|
||||
return [PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId)]
|
||||
case let .updateChannelTooLong(_, channelId, _):
|
||||
return [PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)]
|
||||
case let .updateChatParticipantAdd(chatId, userId, inviterId, _, _):
|
||||
|
@ -413,8 +413,8 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
||||
selectionControlColors: PresentationThemeFillStrokeForeground(fillColor: UIColor(rgb: 0xffffff), strokeColor: UIColor(rgb: 0xffffff), foregroundColor: UIColor(rgb: 0x000000)),
|
||||
deliveryFailedColors: PresentationThemeFillForeground(fillColor: UIColor(rgb: 0xeb5545), foregroundColor: UIColor(rgb: 0xffffff)),
|
||||
mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.6),
|
||||
stickerPlaceholderColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)),
|
||||
stickerPlaceholderShimmerColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5))
|
||||
stickerPlaceholderColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xffffff, alpha: 0.1), withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.1)),
|
||||
stickerPlaceholderShimmerColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xffffff, alpha: 0.1), withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.1))
|
||||
)
|
||||
|
||||
let serviceMessage = PresentationThemeServiceMessage(
|
||||
|
@ -668,8 +668,8 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres
|
||||
selectionControlColors: PresentationThemeFillStrokeForeground(fillColor: accentColor, strokeColor: .white, foregroundColor: .white),
|
||||
deliveryFailedColors: PresentationThemeFillForeground(fillColor: UIColor(rgb: 0xff6767), foregroundColor: .white),
|
||||
mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.6),
|
||||
stickerPlaceholderColor: PresentationThemeVariableColor(color: additionalBackgroundColor.withAlphaComponent(0.5)),
|
||||
stickerPlaceholderShimmerColor: PresentationThemeVariableColor(color: additionalBackgroundColor.withAlphaComponent(0.5))
|
||||
stickerPlaceholderColor: PresentationThemeVariableColor(color: mainBackgroundColor.withAlphaComponent(0.5)),
|
||||
stickerPlaceholderShimmerColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff, alpha: 0.05))
|
||||
)
|
||||
|
||||
let serviceMessage = PresentationThemeServiceMessage(
|
||||
|
@ -549,8 +549,8 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio
|
||||
selectionControlColors: PresentationThemeFillStrokeForeground(fillColor: UIColor(rgb: 0x007ee5), strokeColor: UIColor(rgb: 0xc7c7cc), foregroundColor: UIColor(rgb: 0xffffff)),
|
||||
deliveryFailedColors: PresentationThemeFillForeground(fillColor: UIColor(rgb: 0xff3b30), foregroundColor: UIColor(rgb: 0xffffff)),
|
||||
mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.6),
|
||||
stickerPlaceholderColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0x748391, alpha: 0.45)),
|
||||
stickerPlaceholderShimmerColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0x748391, alpha: 0.45))
|
||||
stickerPlaceholderColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor.withAlphaComponent(0.3), withoutWallpaper: UIColor(rgb: 0x748391, alpha: 0.25)),
|
||||
stickerPlaceholderShimmerColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xffffff, alpha: 0.2), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.1))
|
||||
)
|
||||
|
||||
let messageDay = PresentationThemeChatMessage(
|
||||
@ -617,8 +617,8 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio
|
||||
selectionControlColors: PresentationThemeFillStrokeForeground(fillColor: UIColor(rgb: 0x007ee5), strokeColor: UIColor(rgb: 0xc7c7cc), foregroundColor: UIColor(rgb: 0xffffff)),
|
||||
deliveryFailedColors: PresentationThemeFillForeground(fillColor: UIColor(rgb: 0xff3b30), foregroundColor: UIColor(rgb: 0xffffff)),
|
||||
mediaHighlightOverlayColor: UIColor(rgb: 0xffffff, alpha: 0.6),
|
||||
stickerPlaceholderColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0x748391, alpha: 0.45)),
|
||||
stickerPlaceholderShimmerColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0x748391, alpha: 0.45))
|
||||
stickerPlaceholderColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor.withAlphaComponent(0.3), withoutWallpaper: UIColor(rgb: 0xf7f7f7)),
|
||||
stickerPlaceholderShimmerColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xffffff, alpha: 0.2), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.1))
|
||||
)
|
||||
|
||||
let serviceMessage = PresentationThemeServiceMessage(
|
||||
|
@ -405,13 +405,14 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||
}
|
||||
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
|
||||
case let .groupPhoneCall(_, _, duration):
|
||||
let titleString: String
|
||||
if let duration = duration {
|
||||
titleString = strings.Notification_VoiceChatEnded(callDurationString(strings: strings, value: duration)).0
|
||||
let titleString = strings.Notification_VoiceChatEnded(callDurationString(strings: strings, value: duration)).0
|
||||
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
|
||||
} else {
|
||||
titleString = strings.Notification_VoiceChatStarted
|
||||
var attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
|
||||
let titleString = strings.Notification_VoiceChatStarted(authorName)
|
||||
attributedString = addAttributesToStringWithRanges(titleString, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
|
||||
}
|
||||
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
|
||||
case let .customText(text, entities):
|
||||
attributedString = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false)
|
||||
case let .botDomainAccessGranted(domain):
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Call/CallHeadphonesButton.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_call_headphones.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Call/CallHeadphonesButton.imageset/ic_call_headphones.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconCalls.imageset/Calls@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconCalls.imageset/Calls@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.8 KiB |
@ -1,7 +1,18 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Calls@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Calls@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
@ -1,7 +1,18 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Messages@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Messages@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
BIN
submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconChats.imageset/Messages@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconChats.imageset/Messages@3x.png
vendored
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconContacts.imageset/Contacts@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconContacts.imageset/Contacts@3x.png
vendored
Normal file
After Width: | Height: | Size: 3.3 KiB |
@ -1,7 +1,18 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Contacts@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Contacts@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
@ -1,7 +1,18 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Settings@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Settings@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
BIN
submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconSettings.imageset/Settings@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconSettings.imageset/Settings@3x.png
vendored
Normal file
After Width: | Height: | Size: 3.4 KiB |
@ -318,7 +318,7 @@ public final class AccountContextImpl: AccountContext {
|
||||
}
|
||||
let presentationData = strongSelf.sharedContext.currentPresentationData.with { $0 }
|
||||
if let current = current {
|
||||
if current is TelegramChannel {
|
||||
if current is TelegramChannel || current is TelegramGroup {
|
||||
strongSelf.sharedContext.mainWindow?.present(textAlertController(context: strongSelf, title: presentationData.strings.Call_VoiceChatInProgressTitle, text: presentationData.strings.Call_VoiceChatInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -364,7 +364,7 @@ public final class AccountContextImpl: AccountContext {
|
||||
}
|
||||
let presentationData = strongSelf.sharedContext.currentPresentationData.with { $0 }
|
||||
if let current = current {
|
||||
if current is TelegramChannel {
|
||||
if current is TelegramChannel || current is TelegramGroup {
|
||||
strongSelf.sharedContext.mainWindow?.present(textAlertController(context: strongSelf, title: presentationData.strings.Call_VoiceChatInProgressTitle, text: presentationData.strings.Call_VoiceChatInProgressCallMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
@ -229,6 +229,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
private var shareStatusDisposable: MetaDisposable?
|
||||
private var clearCacheDisposable: MetaDisposable?
|
||||
private var bankCardDisposable: MetaDisposable?
|
||||
private var hasActiveGroupCallDisposable: Disposable?
|
||||
|
||||
private let editingMessage = ValuePromise<Float?>(nil, ignoreRepeated: true)
|
||||
private let startingBot = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
@ -362,6 +363,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
private let peekData: ChatPeekTimeout?
|
||||
private let peekTimerDisposable = MetaDisposable()
|
||||
|
||||
private let createVoiceChatDisposable = MetaDisposable()
|
||||
|
||||
private var shouldDisplayDownButton = false
|
||||
|
||||
private var hasEmbeddedTitleContent = false
|
||||
@ -429,7 +432,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
self.stickerSettings = ChatInterfaceStickerSettings(loopAnimatedStickers: false)
|
||||
|
||||
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, subject: subject, peerNearbyData: peerNearbyData, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil)
|
||||
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, subject: subject, peerNearbyData: peerNearbyData, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false)
|
||||
|
||||
var mediaAccessoryPanelVisibility = MediaAccessoryPanelVisibility.none
|
||||
if case .standard = mode {
|
||||
@ -501,14 +504,66 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
break
|
||||
}
|
||||
}
|
||||
case let .groupPhoneCall(callId, accessHash, nil), let .inviteToGroupPhoneCall(callId, accessHash, _):
|
||||
guard strongSelf.presentationInterfaceState.activeGroupCallInfo?.activeCall.id == callId else {
|
||||
return true
|
||||
case let .groupPhoneCall(callId, accessHash, _), let .inviteToGroupPhoneCall(callId, accessHash, _):
|
||||
if let activeCall = strongSelf.presentationInterfaceState.activeGroupCallInfo?.activeCall {
|
||||
strongSelf.context.joinGroupCall(peerId: message.id.peerId, activeCall: CachedChannelData.ActiveCall(id: activeCall.id, accessHash: activeCall.accessHash))
|
||||
} else {
|
||||
var canManageGroupCalls = false
|
||||
if let channel = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramChannel {
|
||||
if case .group = channel.info, channel.flags.contains(.isCreator) || channel.hasPermission(.manageCalls) {
|
||||
canManageGroupCalls = true
|
||||
}
|
||||
} else if let group = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramGroup {
|
||||
if case .creator = group.role {
|
||||
canManageGroupCalls = true
|
||||
} else if case let .admin(rights, _) = group.role {
|
||||
if rights.flags.contains(.canManageCalls) {
|
||||
canManageGroupCalls = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if canManageGroupCalls {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.VoiceChat_CreateNewVoiceChatText, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.VoiceChat_CreateNewVoiceChatStart, action: {
|
||||
if let strongSelf = self {
|
||||
var dismissStatus: (() -> Void)?
|
||||
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: {
|
||||
dismissStatus?()
|
||||
}))
|
||||
dismissStatus = { [weak self, weak statusController] in
|
||||
self?.createVoiceChatDisposable.set(nil)
|
||||
statusController?.dismiss()
|
||||
}
|
||||
strongSelf.present(statusController, in: .window(.root))
|
||||
strongSelf.createVoiceChatDisposable.set((createGroupCall(account: strongSelf.context.account, peerId: message.id.peerId)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] info in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.context.joinGroupCall(peerId: message.id.peerId, activeCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash))
|
||||
}, error: { [weak self] error in
|
||||
dismissStatus?()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let text: String
|
||||
switch error {
|
||||
case .generic:
|
||||
text = strongSelf.presentationData.strings.Login_UnknownError
|
||||
case .anonymousNotAllowed:
|
||||
text = strongSelf.presentationData.strings.VoiceChat_AnonymousDisabledAlertText
|
||||
}
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}, completed: { [weak self] in
|
||||
dismissStatus?()
|
||||
}))
|
||||
}
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
}
|
||||
|
||||
let peerId = message.id.peerId
|
||||
|
||||
strongSelf.context.joinGroupCall(peerId: peerId, activeCall: CachedChannelData.ActiveCall(id: callId, accessHash: accessHash))
|
||||
return true
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -555,7 +610,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}, enqueueMessage: { message in
|
||||
self?.sendMessages([message])
|
||||
}, sendSticker: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { fileReference, sourceNode, sourceRect in
|
||||
return self?.controllerInteraction?.sendSticker(fileReference, false, sourceNode, sourceRect) ?? false
|
||||
return self?.controllerInteraction?.sendSticker(fileReference, nil, false, sourceNode, sourceRect) ?? false
|
||||
} : nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in
|
||||
if let strongSelf = self {
|
||||
strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { entry in
|
||||
@ -598,7 +653,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}, actionInteraction: GalleryControllerActionInteraction(openUrl: { [weak self] url, concealed in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerInteraction?.openUrl(url, concealed, nil, nil)
|
||||
strongSelf.openUrl(url, concealed: concealed, message: nil)
|
||||
}
|
||||
}, openUrlIn: { [weak self] url in
|
||||
if let strongSelf = self {
|
||||
@ -633,7 +688,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
storedState = MediaPlaybackStoredState(timestamp: timestamp, playbackRate: .x1)
|
||||
}
|
||||
let _ = updateMediaPlaybackStoredStateInteractively(postbox: strongSelf.context.account.postbox, messageId: messageId, state: storedState).start()
|
||||
}, editMedia: { [weak self] messageId in
|
||||
}, editMedia: { [weak self] messageId, snapshots, transitionCompletion in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -653,7 +708,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] {
|
||||
legacyMediaEditor(context: strongSelf.context, peer: peer, media: mediaReference, initialCaption: message.text, presentStickers: { [weak self] completion in
|
||||
legacyMediaEditor(context: strongSelf.context, peer: peer, media: mediaReference, initialCaption: message.text, snapshots: snapshots, transitionCompletion: {
|
||||
transitionCompletion()
|
||||
}, presentStickers: { [weak self] completion in
|
||||
if let strongSelf = self {
|
||||
let controller = DrawingStickersScreen(context: strongSelf.context, selectSticker: { fileReference, node, rect in
|
||||
completion(fileReference.media, fileReference.media.isAnimatedSticker, node.view, rect)
|
||||
@ -855,7 +912,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
attributes.append(TextEntitiesMessageAttribute(entities: entities))
|
||||
}
|
||||
strongSelf.sendMessages([.message(text: text, attributes: attributes, mediaReference: nil, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)])
|
||||
}, sendSticker: { [weak self] fileReference, clearInput, sourceNode, sourceRect in
|
||||
}, sendSticker: { [weak self] fileReference, query, clearInput, sourceNode, sourceRect in
|
||||
guard let strongSelf = self else {
|
||||
return false
|
||||
}
|
||||
@ -887,7 +944,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
}
|
||||
})
|
||||
strongSelf.sendMessages([.message(text: "", attributes: [], mediaReference: fileReference.abstract, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)])
|
||||
|
||||
var attributes: [MessageAttribute] = []
|
||||
if let query = query {
|
||||
attributes.append(EmojiSearchQueryMessageAttribute(query: query))
|
||||
}
|
||||
|
||||
strongSelf.sendMessages([.message(text: "", attributes: attributes, mediaReference: fileReference.abstract, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)])
|
||||
return true
|
||||
}, sendGif: { [weak self] fileReference, sourceNode, sourceRect in
|
||||
if let strongSelf = self {
|
||||
@ -2322,7 +2385,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] {
|
||||
legacyMediaEditor(context: strongSelf.context, peer: peer, media: mediaReference, initialCaption: message.text, presentStickers: { [weak self] completion in
|
||||
legacyMediaEditor(context: strongSelf.context, peer: peer, media: mediaReference, initialCaption: message.text, snapshots: [], transitionCompletion: nil, presentStickers: { [weak self] completion in
|
||||
if let strongSelf = self {
|
||||
let controller = DrawingStickersScreen(context: strongSelf.context, selectSticker: { fileReference, node, rect in
|
||||
completion(fileReference.media, fileReference.media.isAnimatedSticker, node.view, rect)
|
||||
@ -3338,6 +3401,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.keepPeerInfoScreenDataHotDisposable.dispose()
|
||||
self.preloadAvatarDisposable.dispose()
|
||||
self.peekTimerDisposable.dispose()
|
||||
self.hasActiveGroupCallDisposable?.dispose()
|
||||
self.createVoiceChatDisposable.dispose()
|
||||
}
|
||||
|
||||
public func updatePresentationMode(_ mode: ChatControllerPresentationMode) {
|
||||
@ -3753,6 +3818,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
pinnedMessageId = cachedData.pinnedMessageId
|
||||
} else if let cachedData = combinedInitialData.cachedData as? CachedGroupData {
|
||||
pinnedMessageId = cachedData.pinnedMessageId
|
||||
if let activeCall = cachedData.activeCall {
|
||||
activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall)
|
||||
}
|
||||
} else if let _ = combinedInitialData.cachedData as? CachedSecretChatData {
|
||||
}
|
||||
|
||||
@ -3908,6 +3976,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
pinnedMessageId = cachedData.pinnedMessageId
|
||||
} else if let cachedData = cachedData as? CachedGroupData {
|
||||
pinnedMessageId = cachedData.pinnedMessageId
|
||||
if let activeCall = cachedData.activeCall {
|
||||
activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall)
|
||||
}
|
||||
} else if let _ = cachedData as? CachedSecretChatData {
|
||||
}
|
||||
|
||||
@ -5036,6 +5107,29 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
subjectFlags = .banSendMedia
|
||||
}
|
||||
|
||||
if case .mediaRecording = subject, let _ = strongSelf.presentationInterfaceState.activeGroupCallInfo {
|
||||
let rect = strongSelf.chatDisplayNode.frameForInputActionButton()
|
||||
if let rect = rect {
|
||||
strongSelf.mediaRestrictedTooltipController?.dismiss()
|
||||
let tooltipController = TooltipController(content: .text(strongSelf.presentationInterfaceState.strings.Conversation_VoiceChatMediaRecordingRestricted), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize)
|
||||
strongSelf.mediaRestrictedTooltipController = tooltipController
|
||||
strongSelf.mediaRestrictedTooltipControllerMode = false
|
||||
tooltipController.dismissed = { [weak tooltipController] _ in
|
||||
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRestrictedTooltipController === tooltipController {
|
||||
strongSelf.mediaRestrictedTooltipController = nil
|
||||
}
|
||||
}
|
||||
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
|
||||
if let strongSelf = self {
|
||||
return (strongSelf.chatDisplayNode, rect)
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let bannedPermission: (Int32, Bool)?
|
||||
if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
|
||||
bannedPermission = channel.hasBannedPermission(subjectFlags)
|
||||
@ -5193,7 +5287,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}, sendSticker: { [weak self] file, sourceNode, sourceRect in
|
||||
if let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) {
|
||||
return strongSelf.controllerInteraction?.sendSticker(file, true, sourceNode, sourceRect) ?? false
|
||||
return strongSelf.controllerInteraction?.sendSticker(file, nil, true, sourceNode, sourceRect) ?? false
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@ -6221,9 +6315,25 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
let shouldBeActive = combineLatest(self.context.sharedContext.mediaManager.audioSession.isPlaybackActive() |> deliverOnMainQueue, self.chatDisplayNode.historyNode.hasVisiblePlayableItemNodes)
|
||||
|> mapToSignal { [weak self] isPlaybackActive, hasVisiblePlayableItemNodes -> Signal<Bool, NoError> in
|
||||
if hasVisiblePlayableItemNodes && !isPlaybackActive {
|
||||
let hasActiveCalls: Signal<Bool, NoError>
|
||||
if let callManager = self.context.sharedContext.callManager as? PresentationCallManagerImpl {
|
||||
hasActiveCalls = callManager.hasActiveCalls
|
||||
|
||||
self.hasActiveGroupCallDisposable = ((callManager.currentGroupCallSignal
|
||||
|> map { call -> Bool in
|
||||
return call != nil
|
||||
}) |> deliverOnMainQueue).start(next: { [weak self] hasActiveGroupCall in
|
||||
self?.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in
|
||||
return state.updatedHasActiveGroupCall(hasActiveGroupCall)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
hasActiveCalls = .single(false)
|
||||
}
|
||||
|
||||
let shouldBeActive = combineLatest(self.context.sharedContext.mediaManager.audioSession.isPlaybackActive() |> deliverOnMainQueue, self.chatDisplayNode.historyNode.hasVisiblePlayableItemNodes, hasActiveCalls)
|
||||
|> mapToSignal { [weak self] isPlaybackActive, hasVisiblePlayableItemNodes, hasActiveCalls -> Signal<Bool, NoError> in
|
||||
if hasVisiblePlayableItemNodes && !isPlaybackActive && !hasActiveCalls {
|
||||
return Signal<Bool, NoError> { [weak self] subscriber in
|
||||
guard let strongSelf = self else {
|
||||
subscriber.putCompletion()
|
||||
@ -7060,7 +7170,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.saveInterfaceState(includeScrollState: false)
|
||||
}
|
||||
|
||||
if let navigationController = self.navigationController as? NavigationController {
|
||||
if let navigationController = self.navigationController as? NavigationController, isTopmostChatController(self) {
|
||||
var voiceChatOverlayController: VoiceChatOverlayController?
|
||||
for controller in navigationController.globalOverlayControllers {
|
||||
if let controller = controller as? VoiceChatOverlayController {
|
||||
@ -7070,11 +7180,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
if let controller = voiceChatOverlayController {
|
||||
var hidden = false
|
||||
if self.presentationInterfaceState.interfaceState.editMessage != nil || self.presentationInterfaceState.interfaceState.forwardMessageIds != nil || self.presentationInterfaceState.interfaceState.composeInputState.inputText.string.count > 0 {
|
||||
hidden = true
|
||||
}
|
||||
controller.update(hidden: hidden, slide: false, animated: true)
|
||||
controller.updateVisibility()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7669,11 +7775,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
}, openCamera: { [weak self] cameraView, menuController in
|
||||
if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
|
||||
if let callManager = strongSelf.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveGroupCall {
|
||||
return
|
||||
var photoOnly = false
|
||||
if let callManager = strongSelf.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveCall {
|
||||
photoOnly = true
|
||||
}
|
||||
|
||||
presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
|
||||
presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, photoOnly: photoOnly, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
|
||||
if let strongSelf = self {
|
||||
if editMediaOptions != nil {
|
||||
strongSelf.editMessageMediaWithLegacySignals(signals!)
|
||||
@ -11307,6 +11414,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
//return self.chatDisplayNode.acceptEmbeddedTitlePeekContent(content: content)
|
||||
return false
|
||||
}
|
||||
|
||||
public var isSendButtonVisible: Bool {
|
||||
if self.presentationInterfaceState.interfaceState.editMessage != nil || self.presentationInterfaceState.interfaceState.forwardMessageIds != nil || self.presentationInterfaceState.interfaceState.composeInputState.inputText.string.count > 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||
|
@ -60,7 +60,7 @@ public final class ChatControllerInteraction {
|
||||
let toggleMessagesSelection: ([MessageId], Bool) -> Void
|
||||
let sendCurrentMessage: (Bool) -> Void
|
||||
let sendMessage: (String) -> Void
|
||||
let sendSticker: (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool
|
||||
let sendSticker: (FileMediaReference, String?, Bool, ASDisplayNode, CGRect) -> Bool
|
||||
let sendGif: (FileMediaReference, ASDisplayNode, CGRect) -> Bool
|
||||
let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool
|
||||
let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void
|
||||
@ -149,7 +149,7 @@ public final class ChatControllerInteraction {
|
||||
toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void,
|
||||
sendCurrentMessage: @escaping (Bool) -> Void,
|
||||
sendMessage: @escaping (String) -> Void,
|
||||
sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool,
|
||||
sendSticker: @escaping (FileMediaReference, String?, Bool, ASDisplayNode, CGRect) -> Bool,
|
||||
sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool,
|
||||
sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool,
|
||||
requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void,
|
||||
@ -295,7 +295,7 @@ public final class ChatControllerInteraction {
|
||||
|
||||
static var `default`: ChatControllerInteraction {
|
||||
return ChatControllerInteraction(openMessage: { _, _ in
|
||||
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
}, presentController: { _, _ in }, navigationController: {
|
||||
return nil
|
||||
}, chatControllerNode: {
|
||||
|
@ -71,14 +71,16 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
|
||||
switch inputQueryResult {
|
||||
case let .stickers(results):
|
||||
if !results.isEmpty {
|
||||
let query = chatPresentationInterfaceState.interfaceState.composeInputState.inputText.string
|
||||
|
||||
if let currentPanel = currentPanel as? InlineReactionSearchPanel {
|
||||
currentPanel.updateResults(results: results.map({ $0.file }))
|
||||
currentPanel.updateResults(results: results.map({ $0.file }), query: query)
|
||||
return currentPanel
|
||||
} else {
|
||||
let panel = InlineReactionSearchPanel(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize)
|
||||
panel.controllerInteraction = controllerInteraction
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
panel.updateResults(results: results.map({ $0.file }))
|
||||
panel.updateResults(results: results.map({ $0.file }), query: query)
|
||||
return panel
|
||||
}
|
||||
}
|
||||
|
@ -238,7 +238,7 @@ func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: Ordered
|
||||
if view.lower == nil {
|
||||
var savedStickerIds = Set<Int64>()
|
||||
if let savedStickers = savedStickers, !savedStickers.items.isEmpty {
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_FavoriteStickers.uppercased(), shortName: "", thumbnail: nil, hash: 0, count: 0)
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_FavoriteStickers.uppercased(), shortName: "", thumbnail: nil, immediateThumbnailData: nil, hash: 0, count: 0)
|
||||
for i in 0 ..< savedStickers.items.count {
|
||||
if let item = savedStickers.items[i].contents as? SavedStickerItem {
|
||||
savedStickerIds.insert(item.file.fileId.id)
|
||||
@ -250,7 +250,7 @@ func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: Ordered
|
||||
}
|
||||
|
||||
if let recentStickers = recentStickers, !recentStickers.items.isEmpty {
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_FrequentlyUsed.uppercased(), shortName: "", thumbnail: nil, hash: 0, count: 0)
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_FrequentlyUsed.uppercased(), shortName: "", thumbnail: nil, immediateThumbnailData: nil, hash: 0, count: 0)
|
||||
var addedCount = 0
|
||||
for i in 0 ..< recentStickers.items.count {
|
||||
if addedCount >= 20 {
|
||||
@ -278,7 +278,7 @@ func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: Ordered
|
||||
|
||||
if let peerSpecificPack = peerSpecificPack {
|
||||
for i in 0 ..< peerSpecificPack.items.count {
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_GroupStickers, shortName: "", thumbnail: nil, hash: 0, count: 0)
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_GroupStickers, shortName: "", thumbnail: nil, immediateThumbnailData: nil, hash: 0, count: 0)
|
||||
|
||||
if let item = peerSpecificPack.items[i] as? StickerPackItem {
|
||||
let index = ItemCollectionItemIndex(index: Int32(i), id: item.file.fileId.id)
|
||||
@ -535,7 +535,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
sendSticker: {
|
||||
fileReference, sourceNode, sourceRect in
|
||||
if let strongSelf = self {
|
||||
return strongSelf.controllerInteraction.sendSticker(fileReference, false, sourceNode, sourceRect)
|
||||
return strongSelf.controllerInteraction.sendSticker(fileReference, nil, false, sourceNode, sourceRect)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@ -806,7 +806,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
|
||||
let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.controllerInteraction.navigationController(), sendSticker: { fileReference, sourceNode, sourceRect in
|
||||
if let strongSelf = self {
|
||||
return strongSelf.controllerInteraction.sendSticker(fileReference, false, sourceNode, sourceRect)
|
||||
return strongSelf.controllerInteraction.sendSticker(fileReference, nil, false, sourceNode, sourceRect)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@ -1088,7 +1088,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
menuItems = [
|
||||
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { node, rect in
|
||||
if let strongSelf = self {
|
||||
return strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), false, node, rect)
|
||||
return strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), nil, false, node, rect)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@ -1111,7 +1111,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
if let packReference = packReference {
|
||||
let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.controllerInteraction.navigationController(), sendSticker: { file, sourceNode, sourceRect in
|
||||
if let strongSelf = self {
|
||||
return strongSelf.controllerInteraction.sendSticker(file, false, sourceNode, sourceRect)
|
||||
return strongSelf.controllerInteraction.sendSticker(file, nil, false, sourceNode, sourceRect)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@ -1172,7 +1172,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
menuItems = [
|
||||
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { node, rect in
|
||||
if let strongSelf = self {
|
||||
return strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), false, node, rect)
|
||||
return strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), nil, false, node, rect)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@ -1195,7 +1195,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
if let packReference = packReference {
|
||||
let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.controllerInteraction.navigationController(), sendSticker: { file, sourceNode, sourceRect in
|
||||
if let strongSelf = self {
|
||||
return strongSelf.controllerInteraction.sendSticker(file, false, sourceNode, sourceRect)
|
||||
return strongSelf.controllerInteraction.sendSticker(file, nil, false, sourceNode, sourceRect)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
@ -336,7 +336,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
||||
return
|
||||
}
|
||||
if let interfaceInteraction = self.interfaceInteraction, let (_, item, _) = self.currentState, case .ended = recognizer.state {
|
||||
let _ = interfaceInteraction.sendSticker(.standalone(media: item.file), false, self, self.bounds)
|
||||
let _ = interfaceInteraction.sendSticker(.standalone(media: item.file), nil, false, self, self.bounds)
|
||||
self.imageNode.layer.animateAlpha(from: 0.5, to: 1.0, duration: 1.0)
|
||||
}
|
||||
}
|
||||
|
@ -76,8 +76,7 @@ private let verticalOffset: CGFloat = 3.0
|
||||
final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||
private let imageNode: TransformImageNode
|
||||
private var animatedStickerNode: AnimatedStickerNode?
|
||||
private var placeholderNode: ShimmerEffectNode?
|
||||
private var placeholderImageNode: ASImageNode?
|
||||
private var placeholderNode: StickerShimmerEffectNode?
|
||||
private let highlightNode: ASImageNode
|
||||
|
||||
var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
||||
@ -109,11 +108,9 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
|
||||
self.placeholderImageNode = ASImageNode()
|
||||
self.placeholderImageNode?.isUserInteractionEnabled = false
|
||||
|
||||
//self.placeholderNode = ShimmerEffectNode()
|
||||
|
||||
self.placeholderNode = StickerShimmerEffectNode()
|
||||
self.placeholderNode?.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
|
||||
self.highlightNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - highlightSize.width) / 2.0) + verticalOffset - UIScreenPixel, y: floor((boundingSize.height - highlightSize.height) / 2.0) - UIScreenPixel), size: highlightSize)
|
||||
|
||||
@ -124,9 +121,6 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||
|
||||
self.addSubnode(self.highlightNode)
|
||||
self.addSubnode(self.imageNode)
|
||||
if let placeholderImageNode = self.placeholderImageNode {
|
||||
self.addSubnode(placeholderImageNode)
|
||||
}
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.addSubnode(placeholderNode)
|
||||
}
|
||||
@ -138,6 +132,9 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||
}
|
||||
if image != nil {
|
||||
strongSelf.removePlaceholder(animated: !firstTime)
|
||||
if firstTime {
|
||||
strongSelf.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
firstTime = false
|
||||
}
|
||||
@ -159,17 +156,6 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||
})
|
||||
}
|
||||
}
|
||||
if let placeholderImageNode = self.placeholderImageNode {
|
||||
self.placeholderImageNode = nil
|
||||
if !animated {
|
||||
placeholderImageNode.removeFromSupernode()
|
||||
} else {
|
||||
placeholderImageNode.alpha = 0.0
|
||||
placeholderImageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderImageNode] _ in
|
||||
placeholderImageNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateStickerPackItem(account: Account, info: StickerPackCollectionInfo, item: StickerPackItem?, collectionId: ItemCollectionId, theme: PresentationTheme) {
|
||||
@ -244,24 +230,13 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||
self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: resourceReference).start())
|
||||
}
|
||||
}
|
||||
|
||||
if let placeholderImageNode = self.placeholderImageNode {
|
||||
if placeholderImageNode.image == nil {
|
||||
placeholderImageNode.image = generateStretchableFilledCircleImage(diameter: 10.0, color: theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor.withMultipliedAlpha(0.3))
|
||||
}
|
||||
let size = boundingSize
|
||||
let imageSize = boundingImageSize
|
||||
let placeholderFrame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
|
||||
placeholderImageNode.frame = placeholderFrame
|
||||
}
|
||||
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
let size = boundingSize
|
||||
let imageSize = boundingImageSize
|
||||
let placeholderFrame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
|
||||
placeholderNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
placeholderNode.frame = placeholderFrame
|
||||
|
||||
placeholderNode.update(backgroundColor: theme.chat.inputPanel.panelBackgroundColor, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputPanel.panelBackgroundColor, alpha: 0.4), shimmeringColor: theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor.withMultipliedAlpha(0.2), shapes: [.roundedRect(rect: placeholderFrame, cornerRadius: 5.0)], size: bounds.size)
|
||||
placeholderNode.update(backgroundColor: nil, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputPanel.panelBackgroundColor, alpha: 0.4), shimmeringColor: theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor.withMultipliedAlpha(0.2), data: info.immediateThumbnailData, size: imageSize, small: true)
|
||||
}
|
||||
|
||||
self.updateIsHighlighted()
|
||||
@ -270,7 +245,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||
|
||||
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
//placeholderNode.updateAbsoluteRect(rect, within: containerSize)
|
||||
placeholderNode.updateAbsoluteRect(rect, within: containerSize)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,7 +329,7 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane {
|
||||
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
|
||||
let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.controllerInteraction.navigationController(), sendSticker: { fileReference, sourceNode, sourceRect in
|
||||
if let strongSelf = self {
|
||||
return strongSelf.controllerInteraction.sendSticker(fileReference, false, sourceNode, sourceRect)
|
||||
return strongSelf.controllerInteraction.sendSticker(fileReference, nil, false, sourceNode, sourceRect)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
@ -964,7 +964,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
|
||||
if let immediateThumbnailData = file?.immediateThumbnailData, let placeholderNode = strongSelf.placeholderNode {
|
||||
placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0x748391, alpha: 0.2), shimmeringColor: UIColor(rgb: 0x748391, alpha: 0.35), data: immediateThumbnailData, size: animationNodeFrame.size)
|
||||
let foregroundColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper)
|
||||
let shimmeringColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderShimmerColor, wallpaper: item.presentationData.theme.wallpaper)
|
||||
placeholderNode.update(backgroundColor: nil, foregroundColor: foregroundColor, shimmeringColor: shimmeringColor, data: immediateThumbnailData, size: animationNodeFrame.size)
|
||||
placeholderNode.frame = animationNodeFrame
|
||||
}
|
||||
|
||||
|
@ -58,6 +58,17 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
|
||||
super.init(layerBacked: false)
|
||||
|
||||
var firstTime = true
|
||||
self.imageNode.imageUpdated = { [weak self] image in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if image != nil {
|
||||
strongSelf.removePlaceholder(animated: !firstTime)
|
||||
}
|
||||
firstTime = false
|
||||
}
|
||||
|
||||
self.containerNode.shouldBegin = { [weak self] location in
|
||||
guard let strongSelf = self else {
|
||||
return false
|
||||
@ -215,6 +226,19 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
private var absoluteRect: (CGRect, CGSize)?
|
||||
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
self.absoluteRect = (rect, containerSize)
|
||||
if !self.contextSourceNode.isExtractedToContextPreview {
|
||||
var rect = rect
|
||||
rect.origin.y = containerSize.height - rect.maxY + self.insets.top
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + placeholderNode.frame.minX, y: rect.minY + placeholderNode.frame.minY), size: placeholderNode.frame.size), within: containerSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, Bool) -> Void) {
|
||||
let displaySize = CGSize(width: 184.0, height: 184.0)
|
||||
let telegramFile = self.telegramFile
|
||||
@ -578,6 +602,15 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
transition.updateFrame(node: strongSelf.imageNode, frame: updatedImageFrame)
|
||||
imageApply()
|
||||
|
||||
if let immediateThumbnailData = telegramFile?.immediateThumbnailData, let placeholderNode = strongSelf.placeholderNode {
|
||||
let foregroundColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper)
|
||||
let shimmeringColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderShimmerColor, wallpaper: item.presentationData.theme.wallpaper)
|
||||
|
||||
let placeholderFrame = updatedImageFrame.insetBy(dx: innerImageInset, dy: innerImageInset)
|
||||
placeholderNode.update(backgroundColor: nil, foregroundColor: foregroundColor, shimmeringColor: shimmeringColor, data: immediateThumbnailData, size: placeholderFrame.size)
|
||||
placeholderNode.frame = placeholderFrame
|
||||
}
|
||||
|
||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||
|