Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ali 2020-12-17 16:27:33 +04:00
commit b1850a2bd0
114 changed files with 5077 additions and 3990 deletions

View File

@ -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",

Binary file not shown.

Binary file not shown.

View 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.";

View File

@ -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)"

View File

@ -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 {

View File

@ -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()

View File

@ -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

View File

@ -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) {

View File

@ -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)
}
}
}
}

View File

@ -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 {

View File

@ -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)

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -27,6 +27,7 @@ swift_library(
"//submodules/StickerPackPreviewUI:StickerPackPreviewUI",
"//submodules/OverlayStatusController:OverlayStatusController",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/UrlEscaping:UrlEscaping",
],
visibility = [
"//visibility:public",

View File

@ -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 {

View File

@ -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)

View File

@ -19,6 +19,7 @@ swift_library(
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/ShimmerEffect:ShimmerEffect",
],
visibility = [
"//visibility:public",

View File

@ -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 {

View File

@ -19,7 +19,8 @@ typedef enum {
TGCameraControllerPassportIdIntent,
TGCameraControllerPassportMultipleIntent,
TGCameraControllerAvatarIntent,
TGCameraControllerSignupAvatarIntent
TGCameraControllerSignupAvatarIntent,
TGCameraControllerGenericPhotoOnlyIntent
} TGCameraControllerIntent;
@interface TGCameraControllerWindow : TGOverlayControllerWindow

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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];

View File

@ -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

View File

@ -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];
});
}
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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

View File

@ -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())
}

View File

@ -24,6 +24,8 @@ objc_library(
"MobileCoreServices",
"AddressBook",
"AVFoundation",
],
weak_sdk_frameworks = [
"PassKit",
],
visibility = [

View File

@ -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 {

View File

@ -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

View File

@ -19,6 +19,9 @@ objc_library(
sdk_frameworks = [
"Foundation",
"UIKit",
"AddressBook",
],
weak_sdk_frameworks = [
"PassKit",
],
visibility = [

View File

@ -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)
}
}

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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) }

View File

@ -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 {

View File

@ -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() {

View File

@ -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

View File

@ -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)
}
}

View File

@ -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),

View File

@ -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)

View File

@ -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
}

View File

@ -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 {

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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> {

View File

@ -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()
}

View File

@ -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 {

File diff suppressed because it is too large Load Diff

View File

@ -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)
}

View File

@ -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

View File

@ -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
}()

View File

@ -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
}

View File

@ -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)
}

View File

@ -93,6 +93,8 @@ private func filterMessageAttributesForOutgoingMessage(_ attributes: [MessageAtt
return true
case _ as EmbeddedMediaStickersMessageAttribute:
return true
case _ as EmojiSearchQueryMessageAttribute:
return true
default:
return false
}

View File

@ -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)

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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 }

View File

@ -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

View File

@ -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, _, _):

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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):

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_call_headphones.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -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" : {

View File

@ -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" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -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" : {

View File

@ -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" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -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

View File

@ -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 {

View File

@ -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: {

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

Some files were not shown because too many files have changed in this diff Show More