From cb523192d5659cae6551e58e23a4ef726b4d9326 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 17 Sep 2025 20:58:42 +0400 Subject: [PATCH] Various improvements --- Telegram/Telegram-iOS/Resources/Cake.tgs | Bin 0 -> 19679 bytes .../Telegram-iOS/en.lproj/Localizable.strings | 19 + .../Sources/AccountContext.swift | 4 + .../Sources/ChatListController.swift | 42 +- submodules/ReactionSelectionNode/BUILD | 1 + .../ReactionContextBackgroundNode.swift | 36 +- .../Sources/ReactionContextNode.swift | 10 +- submodules/TelegramApi/Sources/Api0.swift | 7 +- submodules/TelegramApi/Sources/Api15.swift | 22 + submodules/TelegramApi/Sources/Api19.swift | 80 ++ submodules/TelegramApi/Sources/Api25.swift | 20 +- submodules/TelegramApi/Sources/Api28.swift | 20 +- submodules/TelegramApi/Sources/Api39.swift | 48 +- submodules/TelegramCallsUI/BUILD | 6 + .../Components/MessageItemComponent.swift | 290 +++++ .../Components/MessageListComponent.swift | 261 +++++ .../Sources/PresentationGroupCall.swift | 7 +- .../VideoChatActionButtonComponent.swift | 58 +- .../VideoChatParticipantsComponent.swift | 8 +- .../Sources/VideoChatScreen.swift | 1036 ++++++++++++++++- .../Sources/VideoChatTitleComponent.swift | 4 +- .../Sources/ApiUtils/ApiGroupOrChannel.swift | 24 + .../ApiUtils/StoreMessage_Telegram.swift | 2 +- .../ApiUtils/TelegramMediaAction.swift | 2 + .../Sources/ApiUtils/TelegramUser.swift | 24 + .../Sources/State/AccountViewTracker.swift | 2 +- .../SyncCore/SyncCore_CachedUserData.swift | 150 ++- .../SyncCore_TelegramMediaAction.swift | 6 + .../AccountData/UpdateAccountPeerName.swift | 20 +- .../TelegramEngine/Calls/GroupCalls.swift | 79 +- .../Contacts/ImportContact.swift | 2 +- .../Contacts/TelegramEngineContacts.swift | 4 + .../Contacts/UpdateContactName.swift | 31 +- .../TelegramEngine/Messages/AdMessages.swift | 4 +- .../TelegramEngine/Payments/StarGifts.swift | 4 +- .../Peers/ChannelAdminEventLogs.swift | 36 +- .../Peers/SuggestBirthday.swift | 23 + .../Peers/TelegramEnginePeers.swift | 4 + .../Peers/UpdateCachedPeerData.swift | 13 +- .../Sources/ServiceMessageStrings.swift | 3 + .../BUILD | 34 + ...essageBirthdateSuggestionContentNode.swift | 390 +++++++ .../Chat/ChatMessageBubbleItemNode/BUILD | 1 + .../Sources/ChatMessageBubbleItemNode.swift | 5 +- .../Sources/GlassBackgroundComponent.swift | 41 +- .../Sources/LegacyMessageInputPanel.swift | 2 +- .../Sources/MediaEditorScreen.swift | 2 +- .../Sources/StoryPreviewComponent.swift | 2 +- .../MessageInputActionButtonComponent/BUILD | 1 + .../MessageInputActionButtonComponent.swift | 66 +- .../MessageInputPanelComponent/BUILD | 1 + .../Sources/MessageInputPanelComponent.swift | 96 +- .../Components/PeerInfo/PeerInfoScreen/BUILD | 1 + .../PeerInfoScreenLabeledValueItem.swift | 47 +- .../PeerInfoScreenNoteListItem.swift | 121 ++ .../PeerInfoScreen/Sources/PeerInfoData.swift | 31 + .../Sources/PeerInfoScreen.swift | 203 +++- .../Sources/PeerInfoGiftsPaneNode.swift | 2 +- .../Settings/BirthdayPickerScreen/BUILD | 2 +- .../Sources/BirthdayPickerComponent.swift | 14 +- .../BirthdayPickerContentComponent.swift | 247 ++-- .../Sources/BirthdayPickerScreen.swift | 80 +- .../StoryItemSetContainerComponent.swift | 2 +- .../CallCameraButton.imageset/Contents.json | 2 +- .../ic_call_camera.pdf | Bin 4032 -> 0 bytes .../CallCameraButton.imageset/videocall.pdf | Bin 0 -> 4383 bytes .../CallQuickMessageIcon@2x.png | Bin 535 -> 0 bytes .../CallQuickMessageIcon@3x.png | Bin 805 -> 0 bytes .../CallMessageButton.imageset/Contents.json | 20 +- .../CallMessageButton.imageset/comment.pdf | Bin 0 -> 4370 bytes .../TelegramUI/Sources/ChatController.swift | 32 +- .../ChatInterfaceStateInputPanels.swift | 1 + .../Sources/SharedAccountContext.swift | 30 + 73 files changed, 3460 insertions(+), 428 deletions(-) create mode 100644 Telegram/Telegram-iOS/Resources/Cake.tgs create mode 100644 submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift create mode 100644 submodules/TelegramCallsUI/Sources/Components/MessageListComponent.swift create mode 100644 submodules/TelegramCore/Sources/TelegramEngine/Peers/SuggestBirthday.swift create mode 100644 submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/BUILD create mode 100644 submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/Sources/ChatMessageBirthdateSuggestionContentNode.swift create mode 100644 submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenNoteListItem.swift delete mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallCameraButton.imageset/ic_call_camera.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallCameraButton.imageset/videocall.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallMessageButton.imageset/CallQuickMessageIcon@2x.png delete mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallMessageButton.imageset/CallQuickMessageIcon@3x.png create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallMessageButton.imageset/comment.pdf diff --git a/Telegram/Telegram-iOS/Resources/Cake.tgs b/Telegram/Telegram-iOS/Resources/Cake.tgs new file mode 100644 index 0000000000000000000000000000000000000000..2d49c2066dcb624fc0f8f785594cf59cfcd3b2c1 GIT binary patch literal 19679 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 Xoabu{j^}7^|Nj30o ViewController + func makeBirthdayPickerScreen(context: AccountContext, settings: Promise, openSettings: @escaping () -> Void, completion: @escaping (TelegramBirthday) -> Void) -> ViewController + func makeBirthdaySuggestionScreen(context: AccountContext, peerId: EnginePeer.Id, completion: @escaping (TelegramBirthday) -> Void) -> ViewController + func makeBirthdayAcceptSuggestionScreen(context: AccountContext, birthday: TelegramBirthday, settings: Promise, openSettings: @escaping () -> Void, completion: @escaping (TelegramBirthday) -> Void) -> ViewController + func makeDebugSettingsController(context: AccountContext?) -> ViewController? func openCreateGroupCallUI(context: AccountContext, peerIds: [EnginePeer.Id], parentController: ViewController) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 36b63d9971..b8770eb3e3 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -6205,25 +6205,31 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController settingsPromise = Promise() settingsPromise.set(.single(nil) |> then(context.engine.privacy.requestAccountPrivacySettings() |> map(Optional.init))) } - let controller = BirthdayPickerScreen(context: context, settings: settingsPromise.get(), openSettings: { [weak self] in - guard let self else { - return + + let controller = context.sharedContext.makeBirthdayPickerScreen( + context: context, + settings: settingsPromise, + openSettings: { [weak self] in + guard let self else { + return + } + self.context.sharedContext.makeBirthdayPrivacyController(context: self.context, settings: settingsPromise, openedFromBirthdayScreen: true, present: { [weak self] c in + self?.push(c) + }) + }, + completion: { [weak self] value in + guard let self else { + return + } + + let _ = context.engine.accountData.updateBirthday(birthday: value).startStandalone() + + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + self.present(UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: self.presentationData.strings.Birthday_Added, cancel: nil, destructive: false), elevatedLayout: false, action: { _ in + return true + }), in: .current) } - self.context.sharedContext.makeBirthdayPrivacyController(context: self.context, settings: settingsPromise, openedFromBirthdayScreen: true, present: { [weak self] c in - self?.push(c) - }) - }, completion: { [weak self] value in - guard let self else { - return - } - - let _ = context.engine.accountData.updateBirthday(birthday: value).startStandalone() - - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - self.present(UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: self.presentationData.strings.Birthday_Added, cancel: nil, destructive: false), elevatedLayout: false, action: { _ in - return true - }), in: .current) - }) + ) self.push(controller) } diff --git a/submodules/ReactionSelectionNode/BUILD b/submodules/ReactionSelectionNode/BUILD index fd7a395c21..cb3ce76c7c 100644 --- a/submodules/ReactionSelectionNode/BUILD +++ b/submodules/ReactionSelectionNode/BUILD @@ -39,6 +39,7 @@ swift_library( "//submodules/Components/BalancedTextComponent", "//submodules/Markdown", "//submodules/TelegramUI/Components/Premium/PremiumStarComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift index d37d4d1829..8e88b058c8 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift @@ -4,6 +4,8 @@ import AsyncDisplayKit import Display import TelegramPresentationData import AccountContext +import ComponentFlow +import GlassBackgroundComponent private func generateBackgroundImage(foreground: UIColor, diameter: CGFloat, sideInset: CGFloat) -> UIImage? { return generateImage(CGSize(width: diameter + sideInset * 2.0, height: diameter + sideInset * 2.0), rotatedContext: { size, context in @@ -41,6 +43,8 @@ final class ReactionContextBackgroundNode: ASDisplayNode { private let smallCircleSize: CGFloat private let backgroundView: BlurredBackgroundView + private let glassBackgroundView: GlassBackgroundView? + private let backgroundTintView: UIView private let backgroundTintMaskOuterContainer: UIView let backgroundTintMaskContainer: UIView @@ -57,11 +61,16 @@ final class ReactionContextBackgroundNode: ASDisplayNode { private var theme: PresentationTheme? - init(largeCircleSize: CGFloat, smallCircleSize: CGFloat, maskNode: ASDisplayNode) { + init(glass: Bool, largeCircleSize: CGFloat, smallCircleSize: CGFloat, maskNode: ASDisplayNode) { self.largeCircleSize = largeCircleSize self.smallCircleSize = smallCircleSize self.backgroundView = BlurredBackgroundView(color: nil, enableBlur: true) + if glass { + self.glassBackgroundView = GlassBackgroundView() + } else { + self.glassBackgroundView = nil + } self.backgroundTintView = UIView() self.backgroundTintMaskContainer = UIView() @@ -108,16 +117,22 @@ final class ReactionContextBackgroundNode: ASDisplayNode { self.backgroundTintMaskOuterContainer.backgroundColor = .white - self.view.addSubview(self.backgroundView) - self.backgroundView.addSubview(self.backgroundTintView) + + if let glassBackgroundView = self.glassBackgroundView { + self.view.addSubview(glassBackgroundView) + glassBackgroundView.addSubview(self.backgroundTintView) + //glassBackgroundView.maskContentView.layer.addSublayer(self.maskLayer) + } else { + self.backgroundView.layer.mask = self.maskLayer + self.view.addSubview(self.backgroundView) + self.backgroundView.addSubview(self.backgroundTintView) + } self.maskLayer.addSublayer(self.smallCircleLayer) self.maskLayer.addSublayer(self.largeCircleLayer) self.maskLayer.addSublayer(self.backgroundClippingLayer) self.backgroundClippingLayer.addSublayer(self.backgroundMaskNode.layer) - - self.backgroundView.layer.mask = self.maskLayer } func updateIsIntersectingContent(isIntersectingContent: Bool, transition: ContainedViewLayoutTransition) { @@ -218,7 +233,16 @@ final class ReactionContextBackgroundNode: ASDisplayNode { transition.updateFrame(view: self.backgroundView, frame: contentBounds, beginWithCurrentState: true) self.backgroundView.update(size: contentBounds.size, transition: transition) - transition.updateFrame(view: self.backgroundTintView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentBounds.width, height: contentBounds.height))) + if let glassBackgroundView = self.glassBackgroundView { + var glassBackgroundFrame = contentBounds.insetBy(dx: 10.0, dy: 10.0) + glassBackgroundFrame.size.height -= 8.0 + transition.updateFrame(view: glassBackgroundView, frame: glassBackgroundFrame, beginWithCurrentState: true) + glassBackgroundView.update(size: glassBackgroundFrame.size, cornerRadius: 23.0, isDark: true, tintColor: UIColor(rgb: 0x1b1d22), transition: ComponentTransition(transition)) + + transition.updateFrame(view: self.backgroundTintView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentBounds.width, height: contentBounds.height)).insetBy(dx: -10.0, dy: -10.0)) + } else { + transition.updateFrame(view: self.backgroundTintView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentBounds.width, height: contentBounds.height))) + } transition.updateFrame(view: self.backgroundTintMaskOuterContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: contentBounds.size)) transition.updateFrame(view: self.backgroundTintMaskContainer, frame: CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: contentBounds.size)) transition.updateFrame(view: self.vibrantExpandedContentContainer, frame: CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: contentBounds.size)) diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index 3c3b94018e..fc44b58dfe 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -26,6 +26,7 @@ import GZip import BalancedTextComponent import Markdown import PremiumStarComponent +import GlassBackgroundComponent public final class ReactionItem { public struct Reaction: Equatable { @@ -487,7 +488,12 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { } } - public init(context: AccountContext, animationCache: AnimationCache, presentationData: PresentationData, items: [ReactionContextItem], selectedItems: Set, title: String? = nil, reactionsLocked: Bool, alwaysAllowPremiumReactions: Bool, allPresetReactionsAreAvailable: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, requestUpdateOverlayWantsToBeBelowKeyboard: @escaping (ContainedViewLayoutTransition) -> Void) { + public enum Style { + case legacy + case glass + } + + public init(context: AccountContext, animationCache: AnimationCache, presentationData: PresentationData, style: Style = .legacy, items: [ReactionContextItem], selectedItems: Set, title: String? = nil, reactionsLocked: Bool, alwaysAllowPremiumReactions: Bool, allPresetReactionsAreAvailable: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, requestUpdateOverlayWantsToBeBelowKeyboard: @escaping (ContainedViewLayoutTransition) -> Void) { self.context = context self.presentationData = presentationData self.items = items @@ -503,7 +509,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { (self.animationRenderer as? MultiAnimationRendererImpl)?.useYuvA = context.sharedContext.immediateExperimentalUISettings.compressedEmojiCache self.backgroundMaskNode = ASDisplayNode() - self.backgroundNode = ReactionContextBackgroundNode(largeCircleSize: largeCircleSize, smallCircleSize: smallCircleSize, maskNode: self.backgroundMaskNode) + self.backgroundNode = ReactionContextBackgroundNode(glass: style == .glass, largeCircleSize: largeCircleSize, smallCircleSize: smallCircleSize, maskNode: self.backgroundMaskNode) self.leftBackgroundMaskNode = ASDisplayNode() self.leftBackgroundMaskNode.backgroundColor = .black self.rightBackgroundMaskNode = ASDisplayNode() diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 3a94b801ff..9439b1262b 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -612,6 +612,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } dict[-229775366] = { return Api.MessageAction.parse_messageActionStarGift($0) } dict[-1787656893] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) } + dict[747579941] = { return Api.MessageAction.parse_messageActionSuggestBirthday($0) } dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) } dict[-293988970] = { return Api.MessageAction.parse_messageActionSuggestedPostApproval($0) } dict[1777932024] = { return Api.MessageAction.parse_messageActionSuggestedPostRefund($0) } @@ -757,7 +758,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[918946202] = { return Api.Peer.parse_peerChat($0) } dict[1498486562] = { return Api.Peer.parse_peerUser($0) } dict[-386039788] = { return Api.PeerBlocked.parse_peerBlocked($0) } + dict[-1192589655] = { return Api.PeerColor.parse_inputPeerColorCollectible($0) } dict[-1253352753] = { return Api.PeerColor.parse_peerColor($0) } + dict[-1178573926] = { return Api.PeerColor.parse_peerColorCollectible($0) } dict[-901375139] = { return Api.PeerLocated.parse_peerLocated($0) } dict[-118740917] = { return Api.PeerLocated.parse_peerSelfLocated($0) } dict[-1721619444] = { return Api.PeerNotifySettings.parse_peerNotifySettings($0) } @@ -962,7 +965,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) } dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($0) } dict[-2136190013] = { return Api.StarGift.parse_starGift($0) } - dict[468707429] = { return Api.StarGift.parse_starGiftUnique($0) } + dict[973640632] = { return Api.StarGift.parse_starGiftUnique($0) } dict[-650279524] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) } dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) } dict[-524291476] = { return Api.StarGiftAttribute.parse_starGiftAttributeOriginalDetails($0) } @@ -1201,7 +1204,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1831650802] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) } dict[34280482] = { return Api.User.parse_user($0) } dict[-742634630] = { return Api.User.parse_userEmpty($0) } - dict[-982010451] = { return Api.UserFull.parse_userFull($0) } + dict[-1607745218] = { return Api.UserFull.parse_userFull($0) } dict[-2100168954] = { return Api.UserProfilePhoto.parse_userProfilePhoto($0) } dict[1326562017] = { return Api.UserProfilePhoto.parse_userProfilePhotoEmpty($0) } dict[164646985] = { return Api.UserStatus.parse_userStatusEmpty($0) } diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index 28616ad79f..07bdc0774d 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -395,6 +395,7 @@ public extension Api { case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?) case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, upgradeMsgId: Int32?, upgradeStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, prepaidUpgradeHash: String?, giftMsgId: Int32?) case messageActionStarGiftUnique(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, resaleAmount: Api.StarsAmount?, canTransferAt: Int32?, canResellAt: Int32?, dropOriginalDetailsStars: Int64?) + case messageActionSuggestBirthday(birthday: Api.Birthday) case messageActionSuggestProfilePhoto(photo: Api.Photo) case messageActionSuggestedPostApproval(flags: Int32, rejectComment: String?, scheduleDate: Int32?, price: Api.StarsAmount?) case messageActionSuggestedPostRefund(flags: Int32) @@ -815,6 +816,12 @@ public extension Api { if Int(flags) & Int(1 << 10) != 0 {serializeInt32(canResellAt!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 12) != 0 {serializeInt64(dropOriginalDetailsStars!, buffer: buffer, boxed: false)} break + case .messageActionSuggestBirthday(let birthday): + if boxed { + buffer.appendInt32(747579941) + } + birthday.serialize(buffer, true) + break case .messageActionSuggestProfilePhoto(let photo): if boxed { buffer.appendInt32(1474192222) @@ -998,6 +1005,8 @@ public extension Api { return ("messageActionStarGift", [("flags", flags as Any), ("gift", gift as Any), ("message", message as Any), ("convertStars", convertStars as Any), ("upgradeMsgId", upgradeMsgId as Any), ("upgradeStars", upgradeStars as Any), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any), ("prepaidUpgradeHash", prepaidUpgradeHash as Any), ("giftMsgId", giftMsgId as Any)]) case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars, let fromId, let peer, let savedId, let resaleAmount, let canTransferAt, let canResellAt, let dropOriginalDetailsStars): return ("messageActionStarGiftUnique", [("flags", flags as Any), ("gift", gift as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any), ("resaleAmount", resaleAmount as Any), ("canTransferAt", canTransferAt as Any), ("canResellAt", canResellAt as Any), ("dropOriginalDetailsStars", dropOriginalDetailsStars as Any)]) + case .messageActionSuggestBirthday(let birthday): + return ("messageActionSuggestBirthday", [("birthday", birthday as Any)]) case .messageActionSuggestProfilePhoto(let photo): return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)]) case .messageActionSuggestedPostApproval(let flags, let rejectComment, let scheduleDate, let price): @@ -1831,6 +1840,19 @@ public extension Api { return nil } } + public static func parse_messageActionSuggestBirthday(_ reader: BufferReader) -> MessageAction? { + var _1: Api.Birthday? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Birthday + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSuggestBirthday(birthday: _1!) + } + else { + return nil + } + } public static func parse_messageActionSuggestProfilePhoto(_ reader: BufferReader) -> MessageAction? { var _1: Api.Photo? if let signature = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api19.swift b/submodules/TelegramApi/Sources/Api19.swift index e49484163a..1512252096 100644 --- a/submodules/TelegramApi/Sources/Api19.swift +++ b/submodules/TelegramApi/Sources/Api19.swift @@ -750,10 +750,18 @@ public extension Api { } public extension Api { enum PeerColor: TypeConstructorDescription { + case inputPeerColorCollectible(collectibleId: Int64) case peerColor(flags: Int32, color: Int32?, backgroundEmojiId: Int64?) + case peerColorCollectible(flags: Int32, collectibleId: Int64, giftEmojiId: Int64, backgroundEmojiId: Int64, accentColor: Int32, colors: [Int32], darkAccentColor: Int32?, darkColors: [Int32]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { + case .inputPeerColorCollectible(let collectibleId): + if boxed { + buffer.appendInt32(-1192589655) + } + serializeInt64(collectibleId, buffer: buffer, boxed: false) + break case .peerColor(let flags, let color, let backgroundEmojiId): if boxed { buffer.appendInt32(-1253352753) @@ -762,16 +770,52 @@ public extension Api { if Int(flags) & Int(1 << 0) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt64(backgroundEmojiId!, buffer: buffer, boxed: false)} break + case .peerColorCollectible(let flags, let collectibleId, let giftEmojiId, let backgroundEmojiId, let accentColor, let colors, let darkAccentColor, let darkColors): + if boxed { + buffer.appendInt32(-1178573926) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(collectibleId, buffer: buffer, boxed: false) + serializeInt64(giftEmojiId, buffer: buffer, boxed: false) + serializeInt64(backgroundEmojiId, buffer: buffer, boxed: false) + serializeInt32(accentColor, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(colors.count)) + for item in colors { + serializeInt32(item, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(darkAccentColor!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(darkColors!.count)) + for item in darkColors! { + serializeInt32(item, buffer: buffer, boxed: false) + }} + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { + case .inputPeerColorCollectible(let collectibleId): + return ("inputPeerColorCollectible", [("collectibleId", collectibleId as Any)]) case .peerColor(let flags, let color, let backgroundEmojiId): return ("peerColor", [("flags", flags as Any), ("color", color as Any), ("backgroundEmojiId", backgroundEmojiId as Any)]) + case .peerColorCollectible(let flags, let collectibleId, let giftEmojiId, let backgroundEmojiId, let accentColor, let colors, let darkAccentColor, let darkColors): + return ("peerColorCollectible", [("flags", flags as Any), ("collectibleId", collectibleId as Any), ("giftEmojiId", giftEmojiId as Any), ("backgroundEmojiId", backgroundEmojiId as Any), ("accentColor", accentColor as Any), ("colors", colors as Any), ("darkAccentColor", darkAccentColor as Any), ("darkColors", darkColors as Any)]) } } + public static func parse_inputPeerColorCollectible(_ reader: BufferReader) -> PeerColor? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.PeerColor.inputPeerColorCollectible(collectibleId: _1!) + } + else { + return nil + } + } public static func parse_peerColor(_ reader: BufferReader) -> PeerColor? { var _1: Int32? _1 = reader.readInt32() @@ -789,6 +833,42 @@ public extension Api { return nil } } + public static func parse_peerColorCollectible(_ reader: BufferReader) -> PeerColor? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int64? + _4 = reader.readInt64() + var _5: Int32? + _5 = reader.readInt32() + var _6: [Int32]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _7: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_7 = reader.readInt32() } + var _8: [Int32]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.PeerColor.peerColorCollectible(flags: _1!, collectibleId: _2!, giftEmojiId: _3!, backgroundEmojiId: _4!, accentColor: _5!, colors: _6!, darkAccentColor: _7, darkColors: _8) + } + else { + return nil + } + } } } diff --git a/submodules/TelegramApi/Sources/Api25.swift b/submodules/TelegramApi/Sources/Api25.swift index 71a606a363..dc3eca3c04 100644 --- a/submodules/TelegramApi/Sources/Api25.swift +++ b/submodules/TelegramApi/Sources/Api25.swift @@ -289,7 +289,7 @@ public extension Api { public extension Api { enum StarGift: TypeConstructorDescription { case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?, releasedBy: Api.Peer?, perUserTotal: Int32?, perUserRemains: Int32?, lockedUntilDate: Int32?) - case starGiftUnique(flags: Int32, id: Int64, giftId: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellAmount: [Api.StarsAmount]?, releasedBy: Api.Peer?, valueAmount: Int64?, valueCurrency: String?, themePeer: Api.Peer?) + case starGiftUnique(flags: Int32, id: Int64, giftId: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellAmount: [Api.StarsAmount]?, releasedBy: Api.Peer?, valueAmount: Int64?, valueCurrency: String?, themePeer: Api.Peer?, peerColor: Api.PeerColor?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -315,9 +315,9 @@ public extension Api { if Int(flags) & Int(1 << 8) != 0 {serializeInt32(perUserRemains!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 9) != 0 {serializeInt32(lockedUntilDate!, buffer: buffer, boxed: false)} break - case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer): + case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer, let peerColor): if boxed { - buffer.appendInt32(468707429) + buffer.appendInt32(973640632) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -345,6 +345,7 @@ public extension Api { if Int(flags) & Int(1 << 8) != 0 {serializeInt64(valueAmount!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 8) != 0 {serializeString(valueCurrency!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 10) != 0 {themePeer!.serialize(buffer, true)} + if Int(flags) & Int(1 << 11) != 0 {peerColor!.serialize(buffer, true)} break } } @@ -353,8 +354,8 @@ public extension Api { switch self { case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy, let perUserTotal, let perUserRemains, let lockedUntilDate): return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("availabilityResale", availabilityResale as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any), ("resellMinStars", resellMinStars as Any), ("title", title as Any), ("releasedBy", releasedBy as Any), ("perUserTotal", perUserTotal as Any), ("perUserRemains", perUserRemains as Any), ("lockedUntilDate", lockedUntilDate as Any)]) - case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer): - return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("giftId", giftId as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellAmount", resellAmount as Any), ("releasedBy", releasedBy as Any), ("valueAmount", valueAmount as Any), ("valueCurrency", valueCurrency as Any), ("themePeer", themePeer as Any)]) + case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer, let peerColor): + return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("giftId", giftId as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellAmount", resellAmount as Any), ("releasedBy", releasedBy as Any), ("valueAmount", valueAmount as Any), ("valueCurrency", valueCurrency as Any), ("themePeer", themePeer as Any), ("peerColor", peerColor as Any)]) } } @@ -468,6 +469,10 @@ public extension Api { if Int(_1!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() { _18 = Api.parse(reader, signature: signature) as? Api.Peer } } + var _19: Api.PeerColor? + if Int(_1!) & Int(1 << 11) != 0 {if let signature = reader.readInt32() { + _19 = Api.parse(reader, signature: signature) as? Api.PeerColor + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -486,8 +491,9 @@ public extension Api { let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil let _c17 = (Int(_1!) & Int(1 << 8) == 0) || _17 != nil let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 { - return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, giftId: _3!, title: _4!, slug: _5!, num: _6!, ownerId: _7, ownerName: _8, ownerAddress: _9, attributes: _10!, availabilityIssued: _11!, availabilityTotal: _12!, giftAddress: _13, resellAmount: _14, releasedBy: _15, valueAmount: _16, valueCurrency: _17, themePeer: _18) + let _c19 = (Int(_1!) & Int(1 << 11) == 0) || _19 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 { + return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, giftId: _3!, title: _4!, slug: _5!, num: _6!, ownerId: _7, ownerName: _8, ownerAddress: _9, attributes: _10!, availabilityIssued: _11!, availabilityTotal: _12!, giftAddress: _13, resellAmount: _14, releasedBy: _15, valueAmount: _16, valueCurrency: _17, themePeer: _18, peerColor: _19) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api28.swift b/submodules/TelegramApi/Sources/Api28.swift index 49b9046b8c..db0684250b 100644 --- a/submodules/TelegramApi/Sources/Api28.swift +++ b/submodules/TelegramApi/Sources/Api28.swift @@ -614,13 +614,13 @@ public extension Api { } public extension Api { enum UserFull: TypeConstructorDescription { - case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, theme: Api.ChatTheme?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?, stargiftsCount: Int32?, starrefProgram: Api.StarRefProgram?, botVerification: Api.BotVerification?, sendPaidMessagesStars: Int64?, disallowedGifts: Api.DisallowedGiftsSettings?, starsRating: Api.StarsRating?, starsMyPendingRating: Api.StarsRating?, starsMyPendingRatingDate: Int32?, mainTab: Api.ProfileTab?, savedMusic: Api.Document?) + case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, theme: Api.ChatTheme?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?, stargiftsCount: Int32?, starrefProgram: Api.StarRefProgram?, botVerification: Api.BotVerification?, sendPaidMessagesStars: Int64?, disallowedGifts: Api.DisallowedGiftsSettings?, starsRating: Api.StarsRating?, starsMyPendingRating: Api.StarsRating?, starsMyPendingRatingDate: Int32?, mainTab: Api.ProfileTab?, savedMusic: Api.Document?, note: Api.TextWithEntities?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let theme, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedGifts, let starsRating, let starsMyPendingRating, let starsMyPendingRatingDate, let mainTab, let savedMusic): + case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let theme, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedGifts, let starsRating, let starsMyPendingRating, let starsMyPendingRatingDate, let mainTab, let savedMusic, let note): if boxed { - buffer.appendInt32(-982010451) + buffer.appendInt32(-1607745218) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags2, buffer: buffer, boxed: false) @@ -660,14 +660,15 @@ public extension Api { if Int(flags2) & Int(1 << 18) != 0 {serializeInt32(starsMyPendingRatingDate!, buffer: buffer, boxed: false)} if Int(flags2) & Int(1 << 20) != 0 {mainTab!.serialize(buffer, true)} if Int(flags2) & Int(1 << 21) != 0 {savedMusic!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 22) != 0 {note!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let theme, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedGifts, let starsRating, let starsMyPendingRating, let starsMyPendingRatingDate, let mainTab, let savedMusic): - return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("theme", theme as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any), ("businessGreetingMessage", businessGreetingMessage as Any), ("businessAwayMessage", businessAwayMessage as Any), ("businessIntro", businessIntro as Any), ("birthday", birthday as Any), ("personalChannelId", personalChannelId as Any), ("personalChannelMessage", personalChannelMessage as Any), ("stargiftsCount", stargiftsCount as Any), ("starrefProgram", starrefProgram as Any), ("botVerification", botVerification as Any), ("sendPaidMessagesStars", sendPaidMessagesStars as Any), ("disallowedGifts", disallowedGifts as Any), ("starsRating", starsRating as Any), ("starsMyPendingRating", starsMyPendingRating as Any), ("starsMyPendingRatingDate", starsMyPendingRatingDate as Any), ("mainTab", mainTab as Any), ("savedMusic", savedMusic as Any)]) + case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let theme, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedGifts, let starsRating, let starsMyPendingRating, let starsMyPendingRatingDate, let mainTab, let savedMusic, let note): + return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("theme", theme as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any), ("businessGreetingMessage", businessGreetingMessage as Any), ("businessAwayMessage", businessAwayMessage as Any), ("businessIntro", businessIntro as Any), ("birthday", birthday as Any), ("personalChannelId", personalChannelId as Any), ("personalChannelMessage", personalChannelMessage as Any), ("stargiftsCount", stargiftsCount as Any), ("starrefProgram", starrefProgram as Any), ("botVerification", botVerification as Any), ("sendPaidMessagesStars", sendPaidMessagesStars as Any), ("disallowedGifts", disallowedGifts as Any), ("starsRating", starsRating as Any), ("starsMyPendingRating", starsMyPendingRating as Any), ("starsMyPendingRatingDate", starsMyPendingRatingDate as Any), ("mainTab", mainTab as Any), ("savedMusic", savedMusic as Any), ("note", note as Any)]) } } @@ -796,6 +797,10 @@ public extension Api { if Int(_2!) & Int(1 << 21) != 0 {if let signature = reader.readInt32() { _38 = Api.parse(reader, signature: signature) as? Api.Document } } + var _39: Api.TextWithEntities? + if Int(_2!) & Int(1 << 22) != 0 {if let signature = reader.readInt32() { + _39 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -834,8 +839,9 @@ public extension Api { let _c36 = (Int(_2!) & Int(1 << 18) == 0) || _36 != nil let _c37 = (Int(_2!) & Int(1 << 20) == 0) || _37 != nil let _c38 = (Int(_2!) & Int(1 << 21) == 0) || _38 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 { - return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, theme: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, wallpaper: _19, stories: _20, businessWorkHours: _21, businessLocation: _22, businessGreetingMessage: _23, businessAwayMessage: _24, businessIntro: _25, birthday: _26, personalChannelId: _27, personalChannelMessage: _28, stargiftsCount: _29, starrefProgram: _30, botVerification: _31, sendPaidMessagesStars: _32, disallowedGifts: _33, starsRating: _34, starsMyPendingRating: _35, starsMyPendingRatingDate: _36, mainTab: _37, savedMusic: _38) + let _c39 = (Int(_2!) & Int(1 << 22) == 0) || _39 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 { + return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, theme: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, wallpaper: _19, stories: _20, businessWorkHours: _21, businessLocation: _22, businessGreetingMessage: _23, businessAwayMessage: _24, businessIntro: _25, birthday: _26, personalChannelId: _27, personalChannelMessage: _28, stargiftsCount: _29, starrefProgram: _30, botVerification: _31, sendPaidMessagesStars: _32, disallowedGifts: _33, starsRating: _34, starsMyPendingRating: _35, starsMyPendingRatingDate: _36, mainTab: _37, savedMusic: _38, note: _39) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api39.swift b/submodules/TelegramApi/Sources/Api39.swift index baca4c5d6f..f11e53fd5d 100644 --- a/submodules/TelegramApi/Sources/Api39.swift +++ b/submodules/TelegramApi/Sources/Api39.swift @@ -1654,13 +1654,12 @@ public extension Api.functions.account { } } public extension Api.functions.account { - static func updateColor(flags: Int32, color: Int32?, backgroundEmojiId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func updateColor(flags: Int32, color: Api.PeerColor?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(2096079197) + buffer.appendInt32(1749885262) serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(backgroundEmojiId!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "account.updateColor", parameters: [("flags", String(describing: flags)), ("color", String(describing: color)), ("backgroundEmojiId", String(describing: backgroundEmojiId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + if Int(flags) & Int(1 << 2) != 0 {color!.serialize(buffer, true)} + return (FunctionDescription(name: "account.updateColor", parameters: [("flags", String(describing: flags)), ("color", String(describing: color))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in let reader = BufferReader(buffer) var result: Api.Bool? if let signature = reader.readInt32() { @@ -4045,15 +4044,16 @@ public extension Api.functions.contacts { } } public extension Api.functions.contacts { - static func addContact(flags: Int32, id: Api.InputUser, firstName: String, lastName: String, phone: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func addContact(flags: Int32, id: Api.InputUser, firstName: String, lastName: String, phone: String, note: Api.TextWithEntities?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-386636848) + buffer.appendInt32(-642109868) serializeInt32(flags, buffer: buffer, boxed: false) id.serialize(buffer, true) serializeString(firstName, buffer: buffer, boxed: false) serializeString(lastName, buffer: buffer, boxed: false) serializeString(phone, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.addContact", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName)), ("phone", String(describing: phone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + if Int(flags) & Int(1 << 1) != 0 {note!.serialize(buffer, true)} + return (FunctionDescription(name: "contacts.addContact", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName)), ("phone", String(describing: phone)), ("note", String(describing: note))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { @@ -4474,6 +4474,22 @@ public extension Api.functions.contacts { }) } } +public extension Api.functions.contacts { + static func updateContactNote(id: Api.InputUser, note: Api.TextWithEntities) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(329212923) + id.serialize(buffer, true) + note.serialize(buffer, true) + return (FunctionDescription(name: "contacts.updateContactNote", parameters: [("id", String(describing: id)), ("note", String(describing: note))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.folders { static func editPeerFolders(folderPeers: [Api.InputFolderPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -12448,3 +12464,19 @@ public extension Api.functions.users { }) } } +public extension Api.functions.users { + static func suggestBirthday(id: Api.InputUser, birthday: Api.Birthday) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-61656206) + id.serialize(buffer, true) + birthday.serialize(buffer, true) + return (FunctionDescription(name: "users.suggestBirthday", parameters: [("id", String(describing: id)), ("birthday", String(describing: birthday))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} diff --git a/submodules/TelegramCallsUI/BUILD b/submodules/TelegramCallsUI/BUILD index a6ec198e91..68eeb68510 100644 --- a/submodules/TelegramCallsUI/BUILD +++ b/submodules/TelegramCallsUI/BUILD @@ -123,6 +123,12 @@ swift_library( "//submodules/InviteLinksUI", "//third-party/td:TdBinding", "//submodules/TelegramUI/Components/AnimatedTextComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/ChatPresentationInterfaceState", + "//submodules/TelegramUI/Components/MessageInputPanelComponent", + "//submodules/TelegramUI/Components/TextFieldComponent", + "//submodules/ReactionSelectionNode", + "//submodules/TelegramUI/Components/EntityKeyboard", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift new file mode 100644 index 0000000000..6b125f6018 --- /dev/null +++ b/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift @@ -0,0 +1,290 @@ +import Foundation +import Display +import UIKit +import ComponentFlow +import SwiftSignalKit +import TelegramCore +import AvatarNode +import GlassBackgroundComponent +import MultilineTextComponent +import MultilineTextWithEntitiesComponent +import AccountContext +import TextFormat +import TelegramPresentationData +import ReactionSelectionNode + +final class MessageItemComponent: Component { + private let context: AccountContext + private let peer: EnginePeer + private let text: String + private let entities: [MessageTextEntity] + private let availableReactions: [ReactionItem]? + private let avatarTapped: () -> Void + + init( + context: AccountContext, + peer: EnginePeer, + text: String, + entities: [MessageTextEntity], + availableReactions: [ReactionItem]?, + avatarTapped: @escaping () -> Void = {} + ) { + self.context = context + self.peer = peer + self.text = text + self.entities = entities + self.availableReactions = availableReactions + self.avatarTapped = avatarTapped + } + + static func == (lhs: MessageItemComponent, rhs: MessageItemComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.peer != rhs.peer { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.entities != rhs.entities { + return false + } + if (lhs.availableReactions ?? []).isEmpty != (rhs.availableReactions ?? []).isEmpty { + return false + } + return true + } + + final class View: UIView { + private let container: UIView + private let background: GlassBackgroundView + private let avatarNode: AvatarNode + private let text: ComponentView + weak var standaloneReactionAnimation: StandaloneReactionAnimation? + + private var component: MessageItemComponent? + + override init(frame: CGRect) { + self.container = UIView() + self.container.transform = CGAffineTransform(scaleX: 1.0, y: -1.0) + + self.background = GlassBackgroundView() + + self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 10.0)) + + self.text = ComponentView() + + super.init(frame: frame) + + self.addSubview(self.container) + self.container.addSubview(self.background) + self.container.addSubview(self.avatarNode.view) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func animateFrom(globalFrame: CGRect, cornerRadius: CGFloat, textSnapshotView: UIView, transition: ComponentTransition) { + guard let superview = self.superview?.superview?.superview else { + return + } + + let originalCenter = self.container.center + let originalTransform = self.container.transform + + let superviewCenter = self.convert(self.container.center, to: superview) + self.container.center = superviewCenter + self.container.transform = .identity + superview.addSubview(self.container) + + self.container.addSubview(textSnapshotView) + transition.setAlpha(view: textSnapshotView, alpha: 0.0, completion: { _ in + textSnapshotView.removeFromSuperview() + }) + transition.setPosition(view: textSnapshotView, position: CGPoint(x: textSnapshotView.center.x + 71.0, y: textSnapshotView.center.y)) + + let initialSize = self.background.frame.size + self.background.update(size: globalFrame.size, cornerRadius: cornerRadius, isDark: true, tintColor: UIColor(rgb: 0x1b1d22), transition: .immediate) + self.background.update(size: initialSize, cornerRadius: 18.0, isDark: true, tintColor: UIColor(rgb: 0x1b1d22), transition: transition) + + let deltaX = (globalFrame.width - self.container.frame.width) / 2.0 + let deltaY = (globalFrame.height - self.container.frame.height) / 2.0 + let fromFrame = superview.convert(globalFrame, from: nil).offsetBy(dx: -deltaX, dy: -deltaY) + + self.container.center = fromFrame.center + transition.setPosition(view: self.container, position: superviewCenter, completion: { _ in + self.container.center = originalCenter + self.container.transform = originalTransform + self.insertSubview(self.container, at: 0) + }) + + if let textView = self.text.view { + transition.animatePosition(view: textView, from: CGPoint(x: -71.0, y: 0.0), to: .zero, additive: true) + transition.animateAlpha(view: textView, from: 0.0, to: 1.0) + } + transition.animateAlpha(view: self.avatarNode.view, from: 0.0, to: 1.0) + transition.animateScale(view: self.avatarNode.view, from: 0.01, to: 1.0) + } + + func update(component: MessageItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let isFirstTime = self.component == nil + var transition = transition + if isFirstTime { + transition = .immediate + } + self.component = component + + let theme = defaultDarkPresentationTheme + + let backgroundColor = UIColor(rgb: 0x1b1d22) + + let textFont = Font.regular(14.0) + let boldTextFont = Font.semibold(14.0) + let textColor: UIColor = .white + let linkColor: UIColor = UIColor(rgb: 0x59b6fa) + + let minimalHeight: CGFloat = 36.0 + let cornerRadius = minimalHeight * 0.5 + let avatarInset: CGFloat = 4.0 + let avatarSize = CGSize(width: minimalHeight - avatarInset * 2.0, height: minimalHeight - avatarInset * 2.0) + let avatarSpacing: CGFloat = 10.0 + let rightInset: CGFloat = 13.0 + + let avatarFrame = CGRect(origin: CGPoint(x: avatarInset, y: avatarInset), size: avatarSize) + if component.peer.smallProfileImage != nil { + self.avatarNode.setPeerV2( + context: component.context, + theme: theme, + peer: component.peer, + authorOfMessage: nil, + overrideImage: nil, + emptyColor: nil, + clipStyle: .round, + synchronousLoad: true, + displayDimensions: avatarSize + ) + } else { + self.avatarNode.setPeer( + context: component.context, + theme: theme, + peer: component.peer, + clipStyle: .round, + synchronousLoad: true, + displayDimensions: avatarSize + ) + } + if self.avatarNode.bounds.isEmpty { + self.avatarNode.frame = avatarFrame + } else { + transition.setFrame(view: self.avatarNode.view, frame: avatarFrame) + } + + let attributedText = stringWithAppliedEntities(component.text, entities: component.entities, baseColor: textColor, linkColor: linkColor, baseFont: textFont, linkFont: textFont, boldFont: boldTextFont, italicFont: textFont, boldItalicFont: boldTextFont, fixedFont: textFont, blockQuoteFont: textFont, message: nil).mutableCopy() as! NSMutableAttributedString + attributedText.insert(NSAttributedString(string: component.peer.compactDisplayTitle + " ", font: boldTextFont, textColor: textColor), at: 0) + + let textSize = self.text.update( + transition: transition, + component: AnyComponent(MultilineTextWithEntitiesComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + placeholderColor: .white, + text: .plain(attributedText), + maximumNumberOfLines: 0, + lineSpacing: 0.0 + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - avatarInset - avatarSize.width - avatarSpacing - rightInset, height: .greatestFiniteMagnitude) + ) + + let size = CGSize(width: avatarInset + avatarSize.width + avatarSpacing + textSize.width + rightInset, height: max(minimalHeight, textSize.height + 15.0)) + + let textFrame = CGRect(origin: CGPoint(x: avatarInset + avatarSize.width + avatarSpacing, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) + if let textView = self.text.view { + if textView.superview == nil { + self.container.addSubview(textView) + } + transition.setFrame(view: textView, frame: textFrame) + } + + transition.setFrame(view: self.container, frame: CGRect(origin: CGPoint(), size: size)) + + self.background.update(size: size, cornerRadius: cornerRadius, isDark: true, tintColor: backgroundColor, transition: transition) + transition.setFrame(view: self.background, frame: CGRect(origin: CGPoint(), size: size)) + + if isFirstTime, let availableReactions = component.availableReactions, let textView = self.text.view { + var reactionItem: ReactionItem? + for item in availableReactions { + if case .builtin(component.text.strippedEmoji) = item.reaction.rawValue { + reactionItem = item + break + } + } + + if let reactionItem { + Queue.mainQueue().justDispatch { + guard let listView = self.superview else { + return + } + + let emojiTargetView = UIView(frame: CGRect(origin: CGPoint(x: textView.frame.width - 44.0, y: 0.0), size: CGSize(width: 44.0, height: 44.0))) + emojiTargetView.isUserInteractionEnabled = false + textView.addSubview(emojiTargetView) + + let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: nil, useDirectRendering: false) + self.container.addSubview(standaloneReactionAnimation.view) + + if let standaloneReactionAnimation = self.standaloneReactionAnimation { + self.standaloneReactionAnimation = nil + standaloneReactionAnimation.view.removeFromSuperview() + } + self.standaloneReactionAnimation = standaloneReactionAnimation + + standaloneReactionAnimation.frame = listView.bounds + standaloneReactionAnimation.animateReactionSelection( + context: component.context, + theme: theme, + animationCache: component.context.animationCache, + reaction: reactionItem, + avatarPeers: [], + playHaptic: true, + isLarge: false, + hideCenterAnimation: true, + targetView: emojiTargetView, + addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in + guard let self else { + return + } + + if let standaloneReactionAnimation = self.standaloneReactionAnimation { + self.standaloneReactionAnimation = nil + standaloneReactionAnimation.view.removeFromSuperview() + } + self.standaloneReactionAnimation = standaloneReactionAnimation + + standaloneReactionAnimation.frame = self.bounds + listView.addSubview(standaloneReactionAnimation.view) + }, + completion: { [weak standaloneReactionAnimation] in + standaloneReactionAnimation?.view.removeFromSuperview() + } + ) + } + } + } + + + return size + } + } + + func makeView() -> View { + return View() + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramCallsUI/Sources/Components/MessageListComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MessageListComponent.swift new file mode 100644 index 0000000000..a45c6391b6 --- /dev/null +++ b/submodules/TelegramCallsUI/Sources/Components/MessageListComponent.swift @@ -0,0 +1,261 @@ +import Foundation +import Display +import UIKit +import ComponentFlow +import SwiftSignalKit +import TelegramCore +import AccountContext +import ReactionSelectionNode + +final class MessageListComponent: Component { + struct Item: Equatable { + let id: AnyHashable + let peer: EnginePeer + let text: String + let entities: [MessageTextEntity] + } + + class SendActionTransition { + public let textSnapshotView: UIView + public let globalFrame: CGRect + public let cornerRadius: CGFloat + + init(textSnapshotView: UIView, globalFrame: CGRect, cornerRadius: CGFloat) { + self.textSnapshotView = textSnapshotView + self.globalFrame = globalFrame + self.cornerRadius = cornerRadius + } + } + + private let context: AccountContext + private let items: [Item] + private let availableReactions: [ReactionItem]? + private let sendActionTransition: SendActionTransition? + + init( + context: AccountContext, + items: [Item], + availableReactions: [ReactionItem]?, + sendActionTransition: SendActionTransition? + ) { + self.context = context + self.items = items + self.availableReactions = availableReactions + self.sendActionTransition = sendActionTransition + } + + static func == (lhs: MessageListComponent, rhs: MessageListComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.items != rhs.items { + return false + } + if (lhs.availableReactions ?? []).isEmpty != (rhs.availableReactions ?? []).isEmpty { + return false + } + if lhs.sendActionTransition !== rhs.sendActionTransition { + return false + } + return true + } + + private final class ScrollView: UIScrollView { + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + } + + final class View: UIView, UIScrollViewDelegate { + private let scrollView: ScrollView + + private var component: MessageListComponent? + private weak var state: EmptyComponentState? + private var isUpdating: Bool = false + + private var nextSendActionTransition: MessageListComponent.SendActionTransition? + + private var itemViews: [AnyHashable: ComponentView] = [:] + + private let topInset: CGFloat = 8.0 + private let bottomInset: CGFloat = 8.0 + private let itemSpacing: CGFloat = 6.0 + + private var ignoreScrolling: Bool = false + + override init(frame: CGRect) { + self.scrollView = ScrollView() + + super.init(frame: frame) + + self.scrollView.delaysContentTouches = false + self.scrollView.canCancelContentTouches = true + self.scrollView.contentInsetAdjustmentBehavior = .never + if #available(iOS 13.0, *) { + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + } + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.alwaysBounceHorizontal = false + self.scrollView.alwaysBounceVertical = true + self.scrollView.scrollsToTop = false + self.scrollView.delegate = self + self.scrollView.clipsToBounds = false + self.scrollView.transform = CGAffineTransform(scaleX: 1.0, y: -1.0) + + self.addSubview(self.scrollView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + + } + + private func isAtBottom(tolerance: CGFloat = 1.0) -> Bool { + let bottomY = -self.scrollView.adjustedContentInset.top + return self.scrollView.contentOffset.y <= bottomY + tolerance + } + + private func scrollToBottom(animated: Bool) { + let targetY = -self.scrollView.adjustedContentInset.top + if animated { + self.scrollView.setContentOffset(CGPoint(x: 0, y: targetY), animated: true) + } else { + self.scrollView.contentOffset = CGPoint(x: 0, y: targetY) + } + } + + func update(component: MessageListComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + if let _ = component.sendActionTransition { + self.nextSendActionTransition = component.sendActionTransition + } + + let originalTransition = transition + transition.setFrame(view: self.scrollView, frame: CGRect(origin: .zero, size: availableSize)) + + let previousContentHeight = self.scrollView.contentSize.height + let wasAtBottom = self.isAtBottom(tolerance: 1.0) + + let maxWidth: CGFloat = 300.0 + + var measured: [(id: AnyHashable, size: CGSize, item: MessageListComponent.Item, itemTransition: ComponentTransition)] = [] + measured.reserveCapacity(component.items.count) + + for item in component.items { + var itemTransition = transition + let key = item.id + let container = self.itemViews[key] ?? { + itemTransition = .immediate + let v = ComponentView() + self.itemViews[key] = v + return v + }() + + let size = container.update( + transition: transition, + component: AnyComponent(MessageItemComponent( + context: component.context, + peer: item.peer, + text: item.text, + entities: item.entities, + availableReactions: component.availableReactions + )), + environment: {}, + containerSize: CGSize(width: maxWidth, height: .greatestFiniteMagnitude) + ) + measured.append((id: key, size: size, item: item, itemTransition: itemTransition)) + } + + let itemsHeight: CGFloat = measured.reduce(0) { $0 + $1.size.height } + + CGFloat(max(0, measured.count - 1)) * self.itemSpacing + let contentHeight = self.topInset + itemsHeight + self.bottomInset + + var y = self.bottomInset + + var validKeys = Set() + for (index, entry) in measured.enumerated() { + validKeys.insert(entry.id) + if let itemView = self.itemViews[entry.id]?.view { + var customAnimation = false + if entry.item.peer.id == component.context.account.peerId, let _ = self.nextSendActionTransition { + customAnimation = true + } + let itemFrame = CGRect( + origin: CGPoint(x: floor((availableSize.width - entry.size.width) / 2.0), y: y), + size: entry.size + ) + + if itemView.superview == nil { + if !originalTransition.animation.isImmediate && !customAnimation { + originalTransition.animateAlpha(view: itemView, from: 0.0, to: 1.0) + originalTransition.animateScale(view: itemView, from: 0.01, to: 1.0) + } + if customAnimation, let nextSendActionTransition = self.nextSendActionTransition { + self.nextSendActionTransition = nil + itemView.frame = itemFrame + if let itemView = itemView as? MessageItemComponent.View { + itemView.isHidden = true + Queue.mainQueue().justDispatch { + itemView.animateFrom(globalFrame: nextSendActionTransition.globalFrame, cornerRadius: nextSendActionTransition.cornerRadius, textSnapshotView: nextSendActionTransition.textSnapshotView, transition: originalTransition) + itemView.isHidden = false + } + } + } + self.scrollView.addSubview(itemView) + } + entry.itemTransition.setFrame(view: itemView, frame: itemFrame) + } + y += entry.size.height + if index != measured.count - 1 { y += self.itemSpacing } + } + + let finalContentHeight = max(availableSize.height, contentHeight) + self.scrollView.contentSize = CGSize(width: availableSize.width, height: finalContentHeight) + + let delta = self.scrollView.contentSize.height - previousContentHeight + if !wasAtBottom && abs(delta) > .ulpOfOne { + self.scrollView.contentOffset.y += delta + } else if wasAtBottom { + self.scrollToBottom(animated: false) + } + + if self.itemViews.count > validKeys.count { + let toRemove = self.itemViews.keys.filter { !validKeys.contains($0) } + for key in toRemove { + if let itemView = self.itemViews[key]?.view { + if transition.animation.isImmediate { + itemView.removeFromSuperview() + } else { + transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in + itemView.removeFromSuperview() + }) + transition.setScale(view: itemView, scale: 0.01) + } + } + self.itemViews.removeValue(forKey: key) + } + } + + if wasAtBottom { + self.scrollToBottom(animated: false) + } + + return availableSize + } + } + + + func makeView() -> View { + return View() + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index b3b34302d0..ba5f5cf411 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -929,6 +929,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { reference: .id(id: initialCall.description.id, accessHash: initialCall.description.accessHash), e2eContext: self.e2eContext ) + self.messagesStatePromise.set(self.messagesContext!.state) } var sharedAudioContext = sharedAudioContext @@ -2639,6 +2640,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } })) + self.isFailedEventDisposable?.dispose() self.isFailedEventDisposable = (participantsContext.isFailedEvent |> filter { $0 } |> take(1) @@ -2979,11 +2981,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } public func setIsMuted(action: PresentationGroupCallMuteAction) { - if "".isEmpty { - self.messagesContext?.send(text: "test\(UInt32.random(in: 0 ... UInt32.max))", entities: []) - return - } - if self.isMutedValue == action { return } diff --git a/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift index 5879d044a0..f7faf4c7d0 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift @@ -6,6 +6,7 @@ import MultilineTextComponent import TelegramPresentationData import AppBundle import TelegramAudio +import GlassBackgroundComponent final class VideoChatActionButtonComponent: Component { enum Content: Equatable { @@ -34,12 +35,14 @@ final class VideoChatActionButtonComponent: Component { case audio(audio: Audio) case video case rotateCamera + case message case leave } case audio(audio: Audio, isEnabled: Bool) case video(isActive: Bool) case rotateCamera + case message case leave fileprivate var iconType: IconType { @@ -59,6 +62,8 @@ final class VideoChatActionButtonComponent: Component { return .video case .rotateCamera: return .rotateCamera + case .message: + return .message case .leave: return .leave } @@ -108,7 +113,7 @@ final class VideoChatActionButtonComponent: Component { final class View: HighlightTrackingButton { private let icon = ComponentView() - private let background: UIImageView + private let background: GlassBackgroundView private let title = ComponentView() private var component: VideoChatActionButtonComponent? @@ -117,7 +122,7 @@ final class VideoChatActionButtonComponent: Component { private var contentImage: UIImage? override init(frame: CGRect) { - self.background = UIImageView() + self.background = GlassBackgroundView() super.init(frame: frame) } @@ -137,6 +142,8 @@ final class VideoChatActionButtonComponent: Component { let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2) + let genericBackgroundColor = UIColor(rgb: 0x1b1d22) + let titleText: String let backgroundColor: UIColor let iconDiameter: CGFloat @@ -158,11 +165,11 @@ final class VideoChatActionButtonComponent: Component { } switch component.microphoneState { case .connecting: - backgroundColor = UIColor(white: 0.1, alpha: 1.0) + backgroundColor = genericBackgroundColor case .muted: - backgroundColor = !isActive ? UIColor(rgb: 0x002E5D) : UIColor(rgb: 0x027FFF) + backgroundColor = !isActive ? genericBackgroundColor : UIColor(rgb: 0x027FFF) case .unmuted: - backgroundColor = !isActive ? UIColor(rgb: 0x124B21) : UIColor(rgb: 0x34C659) + backgroundColor = !isActive ? genericBackgroundColor : UIColor(rgb: 0x34C659) case .raiseHand, .scheduled: backgroundColor = !isActive ? UIColor(rgb: 0x23306B) : UIColor(rgb: 0x3252EF) } @@ -171,20 +178,20 @@ final class VideoChatActionButtonComponent: Component { titleText = component.strings.VoiceChat_Video switch component.microphoneState { case .connecting: - backgroundColor = UIColor(white: 0.1, alpha: 1.0) + backgroundColor = genericBackgroundColor case .muted: - backgroundColor = !isActive ? UIColor(rgb: 0x002E5D) : UIColor(rgb: 0x027FFF) + backgroundColor = !isActive ? genericBackgroundColor : UIColor(rgb: 0x027FFF) case .unmuted: - backgroundColor = !isActive ? UIColor(rgb: 0x124B21) : UIColor(rgb: 0x34C659) + backgroundColor = !isActive ? genericBackgroundColor : UIColor(rgb: 0x34C659) case .raiseHand, .scheduled: backgroundColor = UIColor(rgb: 0x3252EF) } - iconDiameter = 60.0 + iconDiameter = 58.0 case .rotateCamera: titleText = "" switch component.microphoneState { case .connecting: - backgroundColor = UIColor(white: 0.1, alpha: 1.0) + backgroundColor = genericBackgroundColor case .muted: backgroundColor = UIColor(rgb: 0x027FFF) case .unmuted: @@ -193,9 +200,14 @@ final class VideoChatActionButtonComponent: Component { backgroundColor = UIColor(rgb: 0x3252EF) } iconDiameter = 60.0 + case .message: + //TODO:localize + titleText = "message" + backgroundColor = genericBackgroundColor + iconDiameter = 56.0 case .leave: titleText = component.strings.VoiceChat_Leave - backgroundColor = UIColor(rgb: 0x47191E) + backgroundColor = UIColor(rgb: 0x330d0b) iconDiameter = 22.0 } @@ -225,6 +237,8 @@ final class VideoChatActionButtonComponent: Component { self.contentImage = UIImage(bundleImageName: "Call/CallCameraButton")?.precomposed().withRenderingMode(.alwaysTemplate) case .rotateCamera: self.contentImage = UIImage(bundleImageName: "Call/CallSwitchCameraButton")?.precomposed().withRenderingMode(.alwaysTemplate) + case .message: + self.contentImage = UIImage(bundleImageName: "Call/CallMessageButton")?.precomposed().withRenderingMode(.alwaysTemplate) case .leave: self.contentImage = generateImage(CGSize(width: 28.0, height: 28.0), opaque: false, rotatedContext: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) @@ -256,20 +270,20 @@ final class VideoChatActionButtonComponent: Component { let size = CGSize(width: availableSize.width, height: availableSize.height) - if self.background.superview == nil { - self.addSubview(self.background) - self.background.image = generateStretchableFilledCircleImage(diameter: 56.0, color: .white)?.withRenderingMode(.alwaysTemplate) - self.background.tintColor = backgroundColor - } - transition.setFrame(view: self.background, frame: CGRect(origin: CGPoint(), size: size)) - let tintTransition: ComponentTransition - if !transition.animation.isImmediate { - tintTransition = .easeInOut(duration: 0.2) - } else { + if self.background.superview == nil { tintTransition = .immediate + self.insertSubview(self.background, at: 0) + } else { + if !transition.animation.isImmediate { + tintTransition = .easeInOut(duration: 0.2) + } else { + tintTransition = .immediate + } } - tintTransition.setTintColor(layer: self.background.layer, color: backgroundColor) + + self.background.update(size: size, cornerRadius: size.width * 0.5, isDark: true, tintColor: backgroundColor, transition: tintTransition) + transition.setFrame(view: self.background, frame: CGRect(origin: CGPoint(), size: size)) let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) * 0.5), y: size.height + 8.0), size: titleSize) if let titleView = self.title.view { diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift index 7a3eb86cd7..564a15f75b 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift @@ -1463,7 +1463,7 @@ final class VideoChatParticipantsComponent: Component { transition.setFrame(layer: self.listItemViewSeparatorContainer, frame: CGRect(origin: CGPoint(), size: itemLayout.listItemContainerFrame().size)) if self.expandedGridItemContainer.frame != expandedGridItemContainerFrame { - self.expandedGridItemContainer.layer.cornerRadius = 10.0 + self.expandedGridItemContainer.layer.cornerRadius = 16.0 transition.setFrame(view: self.expandedGridItemContainer, frame: expandedGridItemContainerFrame, completion: { [weak self] completed in guard let self, completed else { @@ -1934,7 +1934,7 @@ final class VideoChatParticipantsComponent: Component { transition: transition, component: AnyComponent(RoundedRectangle( color: UIColor(white: 0.1, alpha: 1.0), - cornerRadius: 10.0 + cornerRadius: 16.0 )), environment: {}, containerSize: CGSize(width: itemLayout.listFrame.width - itemLayout.layout.mainColumn.insets.left - itemLayout.layout.mainColumn.insets.right, height: itemLayout.list.contentHeight()) @@ -2018,7 +2018,7 @@ final class VideoChatParticipantsComponent: Component { self.scrollViewClippingContainer.update(params: SolidRoundedCornersContainer.Params( size: itemLayout.scrollClippingFrame.size, color: .black, - cornerRadius: 10.0, + cornerRadius: 16.0, smoothCorners: false ), transition: transition) @@ -2059,7 +2059,7 @@ final class VideoChatParticipantsComponent: Component { self.separateVideoScrollViewClippingContainer.update(params: SolidRoundedCornersContainer.Params( size: itemLayout.separateVideoScrollClippingFrame.size, color: .black, - cornerRadius: 10.0, + cornerRadius: 16.0, smoothCorners: false ), transition: transition) diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index 2478b8dcdb..13bc645722 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -27,8 +27,16 @@ import BlurredBackgroundComponent import CallsEmoji import InviteLinksUI import AnimatedTextComponent +import TextFieldComponent +import MessageInputPanelComponent +import ChatEntityKeyboardInputNode +import ChatPresentationInterfaceState +import TextFormat +import ReactionSelectionNode +import EntityKeyboard +import GlassBackgroundComponent -extension VideoChatCall { +extension VideoChatCall { var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> { switch self { case let .group(group): @@ -231,10 +239,36 @@ final class VideoChatScreenComponent: Component { let videoControlButton = ComponentView() let leaveButton = ComponentView() let microphoneButton = ComponentView() + let messageButton = ComponentView() + + let speakerButton = ComponentView() let participants = ComponentView() var scheduleInfo: ComponentView? + + var inputPanelIsActive = false + let inputPanel = ComponentView() + let inputPanelExternalState = MessageInputPanelComponent.ExternalState() + var currentInputMode: MessageInputPanelComponent.InputMode = .text + var nextSendMessageTransition: MessageInputPanelComponent.SendActionTransition? + + var didInitializeInputMediaNodeDataPromise = false + var inputMediaNodeData: ChatEntityKeyboardInputNode.InputData? + var inputMediaNodeDataPromise = Promise() + var inputMediaNodeDataDisposable: Disposable? + var inputMediaNodeStateContext = ChatEntityKeyboardInputNode.StateContext() + var inputMediaInteraction: ChatEntityKeyboardInputNode.Interaction? + var inputMediaNode: ChatEntityKeyboardInputNode? + + var reactionItems: [ReactionItem]? + var reactionContextNode: ReactionContextNode? + weak var disappearingReactionContextNode: ReactionContextNode? + weak var willDismissReactionContextNode: ReactionContextNode? + let messagesList = ComponentView() + var messagesState: GroupCallMessagesContext.State? + var messagesStateDisposable: Disposable? + var enableVideoSharpening: Bool = false var reconnectedAsEventsDisposable: Disposable? @@ -974,6 +1008,12 @@ final class VideoChatScreenComponent: Component { } } + private func onMessagePressed() { + self.inputPanelIsActive = true + self.state?.updated() + self.activateInput() + } + private func onLeavePressed() { guard let environment = self.environment, let currentCall = self.currentCall else { return @@ -1222,12 +1262,174 @@ final class VideoChatScreenComponent: Component { } } + private func setupInputMediaNode(context: AccountContext) { + guard !self.didInitializeInputMediaNodeDataPromise else { + return + } + self.didInitializeInputMediaNodeDataPromise = true + + self.inputMediaNodeDataDisposable = (self.inputMediaNodeDataPromise.get() + |> deliverOnMainQueue).start(next: { [weak self] value in + guard let self else { + return + } + self.inputMediaNodeData = value + }) + + self.inputMediaNodeDataPromise.set( + ChatEntityKeyboardInputNode.inputData( + context: context, + chatPeerId: nil, + areCustomEmojiEnabled: true, + hasSearch: true, + hideBackground: true, + sendGif: nil + ) |> map { inputData -> ChatEntityKeyboardInputNode.InputData in + return ChatEntityKeyboardInputNode.InputData( + emoji: inputData.emoji, + stickers: nil, + gifs: nil, + availableGifSearchEmojies: [] + ) + } + ) + + self.inputMediaInteraction = ChatEntityKeyboardInputNode.Interaction( + sendSticker: { _, _, _, _, _, _, _, _, _ in + return false + }, + sendEmoji: { [weak self] text, attribute, bool1 in + if let self { + let _ = self + } + }, + sendGif: { _, _, _, _, _ in + return false + }, + sendBotContextResultAsGif: { _, _, _, _, _, _ in + return false + }, + updateChoosingSticker: { _ in }, + switchToTextInput: { [weak self] in + if let self { + self.activateInput() + } + }, + dismissTextInput: { + }, + insertText: { [weak self] text in + if let self { + self.inputPanelExternalState.insertText(text) + } + }, + backwardsDeleteText: { [weak self] in + if let self { + self.inputPanelExternalState.deleteBackward() + } + }, + openStickerEditor: {}, + presentController: { [weak self] c, a in + if let self { + self.environment?.controller()?.present(c, in: .window(.root), with: a) + } + }, + presentGlobalOverlayController: { [weak self] c, a in + if let self { + self.environment?.controller()?.presentInGlobalOverlay(c, with: a) + } + }, + getNavigationController: { [weak self] in + if let self { + return self.environment?.controller()?.navigationController as? NavigationController + } else { + return nil + } + }, + requestLayout: { [weak self] transition in + if let self { + (self.environment?.controller() as? VideoChatScreenV2Impl)?.requestLayout(forceUpdate: true, transition: ComponentTransition(transition)) + } + } + ) + self.inputMediaInteraction?.forceTheme = defaultDarkColorPresentationTheme + + let _ = (allowedStoryReactions(context: context) + |> deliverOnMainQueue).start(next: { [weak self] reactionItems in + self?.reactionItems = reactionItems + }) + } + + private func sendInput() { + guard let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View else { + return + } + guard case let .group(groupCall) = self.currentCall, let call = groupCall as? PresentationGroupCallImpl else { + return + } + + switch inputPanelView.getSendMessageInput() { + case let .text(text): + guard !text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { + return + } + let entities = generateChatInputTextEntities(text) + call.sendMessage(text: text.string, entities: entities) + } + + inputPanelView.clearSendMessageInput(updateState: true) + + self.currentInputMode = .text + if hasFirstResponder(self) { + self.endEditing(true) + } else { + self.state?.updated(transition: .spring(duration: 0.3)) + } + (self.environment?.controller() as? VideoChatScreenV2Impl)?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) + } + + private func activateInput() { + self.currentInputMode = .text + if !hasFirstResponder(self) { + if let view = self.inputPanel.view as? MessageInputPanelComponent.View { + view.activateInput() + } + } else { + self.state?.updated(transition: .immediate) + } + } + + private var nextTransitionUserData: Any? + @objc private func deactivateInput() { + guard let _ = self.inputPanel.view as? MessageInputPanelComponent.View else { + return + } + self.currentInputMode = .text + if hasFirstResponder(self) { + if let view = self.inputPanel.view as? MessageInputPanelComponent.View { + self.nextTransitionUserData = TextFieldComponent.AnimationHint(view: nil, kind: .textFocusChanged(isFocused: false)) + if view.isActive { + view.deactivateInput(force: true) + } else { + self.endEditing(true) + } + } + } else { + self.state?.updated(transition: .spring(duration: 0.4).withUserData(TextFieldComponent.AnimationHint(view: nil, kind: .textFocusChanged(isFocused: false)))) + } + } + func update(component: VideoChatScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true defer { self.isUpdating = false } + var transition = transition + if let nextTransitionUserData = self.nextTransitionUserData { + self.nextTransitionUserData = nil + transition = transition.withUserData(nextTransitionUserData) + } + let alphaTransition: ComponentTransition if transition.animation.isImmediate { alphaTransition = .immediate @@ -1251,6 +1453,8 @@ final class VideoChatScreenComponent: Component { if let data = component.initialCall.accountContext.currentAppConfiguration.with({ $0 }).data, let value = data["ios_call_video_sharpening"] as? Double { self.enableVideoSharpening = value != 0.0 } + + self.setupInputMediaNode(context: component.initialCall.accountContext) } var call: VideoChatCall @@ -1262,7 +1466,7 @@ final class VideoChatScreenComponent: Component { if case let .conferenceSource(conferenceSource) = call, let conferenceCall = conferenceSource.conferenceCall, conferenceSource.conferenceStateValue == .ready { call = .group(conferenceCall) } - + self.currentCall = call if self.appliedCurrentCall != call { self.appliedCurrentCall = call @@ -1661,6 +1865,22 @@ final class VideoChatScreenComponent: Component { }) } }) + + self.messagesStateDisposable?.dispose() + if let groupCall = groupCall as? PresentationGroupCallImpl { + self.messagesStateDisposable = (groupCall.messagesState + |> deliverOnMainQueue).start(next: { [weak self] messagesState in + guard let self else { + return + } + if self.messagesState != messagesState { + self.messagesState = messagesState + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + } + }) + } case let .conferenceSource(conferenceSource): self.membersDisposable?.dispose() self.membersDisposable = (View.groupCallMembersForConferenceSource(conferenceSource: conferenceSource) @@ -1975,11 +2195,11 @@ final class VideoChatScreenComponent: Component { let landscapeControlsWidth: CGFloat = 104.0 var landscapeControlsOffsetX: CGFloat = 0.0 - let landscapeControlsSpacing: CGFloat = 30.0 + //let landscapeControlsSpacing: CGFloat = 30.0 - var leftInset: CGFloat = max(environment.safeInsets.left, 14.0) + var leftInset: CGFloat = max(environment.safeInsets.left, 16.0) - var rightInset: CGFloat = max(environment.safeInsets.right, 14.0) + var rightInset: CGFloat = max(environment.safeInsets.right, 16.0) var buttonsOnTheSide = false if availableSize.width > maxSingleColumnWidth && !environment.metrics.isTablet { leftInset += 2.0 @@ -1995,11 +2215,12 @@ final class VideoChatScreenComponent: Component { } let topInset: CGFloat = environment.statusBarHeight + 2.0 - let navigationBarHeight: CGFloat = 61.0 + let navigationBarHeight: CGFloat = 56.0 var navigationHeight = topInset + navigationBarHeight - let navigationButtonAreaWidth: CGFloat = 34.0 - let navigationButtonDiameter: CGFloat = 28.0 + let navigationButtonAreaWidth: CGFloat = 44.0 + let navigationButtonDiameter: CGFloat = 40.0 + let navigationButtonInset: CGFloat = 4.0 let navigationLeftButtonSize = self.navigationLeftButton.update( transition: .immediate, @@ -2010,10 +2231,9 @@ final class VideoChatScreenComponent: Component { ), color: .white )), - background: AnyComponent(Circle( - fillColor: UIColor(white: 1.0, alpha: 0.1), - size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter) - )), + background: AnyComponent( + GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), tintColor: UIColor(rgb: 0x101014)) + ), effectAlignment: .center, minSize: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), action: { [weak self] in @@ -2033,10 +2253,9 @@ final class VideoChatScreenComponent: Component { content: AnyComponent(Image( image: closeButtonImage(dark: false) )), - background: AnyComponent(Circle( - fillColor: UIColor(white: 1.0, alpha: 0.1), - size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter) - )), + background: AnyComponent( + GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), tintColor: UIColor(rgb: 0x101014)) + ), effectAlignment: .center, minSize: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), action: { [weak self] in @@ -2050,7 +2269,7 @@ final class VideoChatScreenComponent: Component { containerSize: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter) ) - let navigationLeftButtonFrame = CGRect(origin: CGPoint(x: leftInset + floor((navigationButtonAreaWidth - navigationLeftButtonSize.width) * 0.5), y: topInset + floor((navigationBarHeight - navigationLeftButtonSize.height) * 0.5)), size: navigationLeftButtonSize) + let navigationLeftButtonFrame = CGRect(origin: CGPoint(x: leftInset + floor((navigationButtonAreaWidth - navigationLeftButtonSize.width) * 0.5) - navigationButtonInset, y: topInset + floor((navigationBarHeight - navigationLeftButtonSize.height) * 0.5)), size: navigationLeftButtonSize) if let navigationLeftButtonView = self.navigationLeftButton.view { if navigationLeftButtonView.superview == nil { self.containerView.addSubview(navigationLeftButtonView) @@ -2059,7 +2278,7 @@ final class VideoChatScreenComponent: Component { alphaTransition.setAlpha(view: navigationLeftButtonView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0) } - var navigationRightButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - navigationButtonAreaWidth + floor((navigationButtonAreaWidth - navigationRightButtonSize.width) * 0.5), y: topInset + floor((navigationBarHeight - navigationRightButtonSize.height) * 0.5)), size: navigationRightButtonSize) + var navigationRightButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - navigationButtonAreaWidth + floor((navigationButtonAreaWidth - navigationRightButtonSize.width) * 0.5) + navigationButtonInset, y: topInset + floor((navigationBarHeight - navigationRightButtonSize.height) * 0.5)), size: navigationRightButtonSize) if buttonsOnTheSide { navigationRightButtonFrame.origin.x += 42.0 } @@ -2232,7 +2451,7 @@ final class VideoChatScreenComponent: Component { mainColumnWidth = min(isLandscape ? 340.0 : 320.0, availableSize.width - leftInset - rightInset - 340.0) mainColumnSideInset = 0.0 } else { - areButtonsCollapsed = self.expandedParticipantsVideoState != nil + areButtonsCollapsed = true //self.expandedParticipantsVideoState != nil if availableSize.width > maxSingleColumnWidth { mainColumnWidth = 420.0 @@ -2344,21 +2563,23 @@ final class VideoChatScreenComponent: Component { microphoneButtonDiameter = self.expandedParticipantsVideoState == nil ? collapsedMicrophoneButtonDiameter : expandedMicrophoneButtonDiameter } } + let _ = microphoneButtonDiameter + //let buttonsSideInset: CGFloat = 26.0 - let buttonsSideInset: CGFloat = 26.0 - - let buttonsWidth: CGFloat = actionButtonDiameter * 2.0 + microphoneButtonDiameter - let remainingButtonsSpace: CGFloat = availableSize.width - buttonsSideInset * 2.0 - buttonsWidth + //let buttonsWidth: CGFloat = actionButtonDiameter * 2.0 + microphoneButtonDiameter + //let remainingButtonsSpace: CGFloat = availableSize.width - buttonsSideInset * 2.0 - buttonsWidth let effectiveMaxActionMicrophoneButtonSpacing: CGFloat if areButtonsCollapsed { - effectiveMaxActionMicrophoneButtonSpacing = 80.0 + effectiveMaxActionMicrophoneButtonSpacing = 101.0 } else { effectiveMaxActionMicrophoneButtonSpacing = maxActionMicrophoneButtonSpacing } - let actionMicrophoneButtonSpacing = min(effectiveMaxActionMicrophoneButtonSpacing, floor(remainingButtonsSpace * 0.5)) - + //TODO:release + //let actionMicrophoneButtonSpacing = min(effectiveMaxActionMicrophoneButtonSpacing, floor(remainingButtonsSpace * 0.5)) + let actionMicrophoneButtonSpacing = effectiveMaxActionMicrophoneButtonSpacing + var collapsedMicrophoneButtonFrame: CGRect = CGRect(origin: CGPoint(x: floor((availableSize.width - collapsedMicrophoneButtonDiameter) * 0.5), y: availableSize.height - 48.0 - environment.safeInsets.bottom - collapsedMicrophoneButtonDiameter), size: CGSize(width: collapsedMicrophoneButtonDiameter, height: collapsedMicrophoneButtonDiameter)) if self.isAnimatedOutFromPrivateCall { collapsedMicrophoneButtonFrame.origin.y = availableSize.height + 48.0 @@ -2423,16 +2644,26 @@ final class VideoChatScreenComponent: Component { expandedParticipantsClippingY = expandedMicrophoneButtonFrame.minY - 24.0 } - var leftActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.minX - actionMicrophoneButtonSpacing - actionButtonDiameter, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter)) - var rightActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.maxX + actionMicrophoneButtonSpacing, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter)) + //TODO:release + let actionButtonSize = CGSize(width: actionButtonDiameter, height: actionButtonDiameter) + let firstActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.minX - actionMicrophoneButtonSpacing - actionButtonDiameter, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: actionButtonSize) + let secondActionButtonFrame = CGRect(origin: CGPoint(x: firstActionButtonFrame.minX + 80.0, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: actionButtonSize) + + let fourthActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.maxX + actionMicrophoneButtonSpacing, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: actionButtonSize) + let thirdActionButtonFrame = CGRect(origin: CGPoint(x: fourthActionButtonFrame.minX - 80.0, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: actionButtonSize) - if buttonsOnTheSide { - leftActionButtonFrame.origin.x = microphoneButtonFrame.minX - leftActionButtonFrame.origin.y = microphoneButtonFrame.minY - landscapeControlsSpacing - actionButtonDiameter - - rightActionButtonFrame.origin.x = microphoneButtonFrame.minX - rightActionButtonFrame.origin.y = microphoneButtonFrame.maxY + landscapeControlsSpacing - } + let _ = firstActionButtonFrame + +// var leftActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.minX - actionMicrophoneButtonSpacing - actionButtonDiameter, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter)) +// var rightActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.maxX + actionMicrophoneButtonSpacing, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter)) + +// if buttonsOnTheSide { +// leftActionButtonFrame.origin.x = microphoneButtonFrame.minX +// leftActionButtonFrame.origin.y = microphoneButtonFrame.minY - landscapeControlsSpacing - actionButtonDiameter +// +// rightActionButtonFrame.origin.x = microphoneButtonFrame.minX +// rightActionButtonFrame.origin.y = microphoneButtonFrame.maxY + landscapeControlsSpacing +// } let participantsSize = availableSize @@ -2446,7 +2677,7 @@ final class VideoChatScreenComponent: Component { rightInset: rightInset, videoColumn: VideoChatParticipantsComponent.Layout.Column( width: videoColumnWidth, - insets: UIEdgeInsets(top: navigationHeight, left: 0.0, bottom: max(14.0, environment.safeInsets.bottom), right: 0.0) + insets: UIEdgeInsets(top: navigationHeight + 10.0, left: 0.0, bottom: max(14.0, environment.safeInsets.bottom), right: 0.0) ), mainColumn: VideoChatParticipantsComponent.Layout.Column( width: mainColumnWidth, @@ -2456,7 +2687,7 @@ final class VideoChatScreenComponent: Component { isMainColumnHidden: self.isTwoColumnSidebarHidden ) } else { - let mainColumnInsets: UIEdgeInsets = UIEdgeInsets(top: navigationHeight, left: mainColumnSideInset, bottom: availableSize.height - collapsedParticipantsClippingY, right: mainColumnSideInset) + let mainColumnInsets: UIEdgeInsets = UIEdgeInsets(top: navigationHeight + 10.0, left: mainColumnSideInset, bottom: availableSize.height - collapsedParticipantsClippingY, right: mainColumnSideInset) participantsLayout = VideoChatParticipantsComponent.Layout( leftInset: leftInset, rightInset: rightInset, @@ -2964,11 +3195,11 @@ final class VideoChatScreenComponent: Component { let videoControlButtonSpacing: CGFloat = 8.0 - var videoButtonFrame = leftActionButtonFrame - if displayVideoControlButton { - let totalVideoButtonsHeight = actionButtonDiameter + videoControlButtonSpacing + videoControlButtonSize.height - videoButtonFrame.origin.y = videoButtonFrame.minY + floor((videoButtonFrame.height - totalVideoButtonsHeight) / 2.0) + videoControlButtonSpacing + videoControlButtonSize.height - } + let videoButtonFrame = secondActionButtonFrame +// if displayVideoControlButton { +// let totalVideoButtonsHeight = actionButtonDiameter + videoControlButtonSpacing + videoControlButtonSize.height +// videoButtonFrame.origin.y = videoButtonFrame.minY + floor((videoButtonFrame.height - totalVideoButtonsHeight) / 2.0) + videoControlButtonSpacing + videoControlButtonSize.height +// } let videoControlButtonFrame = CGRect(origin: CGPoint(x: videoButtonFrame.minX + floor((videoButtonFrame.width - videoControlButtonSize.width) / 2.0), y: videoButtonFrame.minY - videoControlButtonSpacing - videoControlButtonSize.height), size: videoControlButtonSize) @@ -2990,6 +3221,61 @@ final class VideoChatScreenComponent: Component { transition.setBounds(view: videoButtonView, bounds: CGRect(origin: CGPoint(), size: videoButtonFrame.size)) } + let _ = self.speakerButton.update( + transition: transition, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(VideoChatActionButtonComponent( + strings: environment.strings, + content: .audio(audio: .speaker, isEnabled: false), + microphoneState: actionButtonMicrophoneState, + isCollapsed: areButtonsCollapsed || buttonsOnTheSide + )), + effectAlignment: .center, + action: { [weak self] in + let _ = self + }, + animateAlpha: false + )), + environment: {}, + containerSize: CGSize(width: actionButtonDiameter, height: actionButtonDiameter) + ) + if let speakerButtonView = self.speakerButton.view { + if speakerButtonView.superview == nil { + self.containerView.addSubview(speakerButtonView) + } + transition.setPosition(view: speakerButtonView, position: firstActionButtonFrame.center) + transition.setBounds(view: speakerButtonView, bounds: CGRect(origin: CGPoint(), size: firstActionButtonFrame.size)) + } + + let _ = self.messageButton.update( + transition: transition, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(VideoChatActionButtonComponent( + strings: environment.strings, + content: .message, + microphoneState: actionButtonMicrophoneState, + isCollapsed: areButtonsCollapsed || buttonsOnTheSide + )), + effectAlignment: .center, + action: { [weak self] in + guard let self else { + return + } + self.onMessagePressed() + }, + animateAlpha: false + )), + environment: {}, + containerSize: CGSize(width: actionButtonDiameter, height: actionButtonDiameter) + ) + if let messageButtonView = self.messageButton.view { + if messageButtonView.superview == nil { + self.containerView.addSubview(messageButtonView) + } + transition.setPosition(view: messageButtonView, position: thirdActionButtonFrame.center) + transition.setBounds(view: messageButtonView, bounds: CGRect(origin: CGPoint(), size: thirdActionButtonFrame.size)) + } + let _ = self.leaveButton.update( transition: transition, component: AnyComponent(PlainButtonComponent( @@ -3015,8 +3301,548 @@ final class VideoChatScreenComponent: Component { if leaveButtonView.superview == nil { self.containerView.addSubview(leaveButtonView) } - transition.setPosition(view: leaveButtonView, position: rightActionButtonFrame.center) - transition.setBounds(view: leaveButtonView, bounds: CGRect(origin: CGPoint(), size: rightActionButtonFrame.size)) + transition.setPosition(view: leaveButtonView, position: fourthActionButtonFrame.center) + transition.setBounds(view: leaveButtonView, bounds: CGRect(origin: CGPoint(), size: fourthActionButtonFrame.size)) + } + + var inputPanelBottomInset: CGFloat = 0.0 + var inputPanelSize: CGSize = .zero + if self.inputPanelIsActive { + var inputPanelAvailableWidth = availableSize.width + var inputPanelAvailableHeight = 103.0 + if case .regular = environment.metrics.widthClass { + if (self.inputPanelExternalState.isEditing || self.inputPanelExternalState.hasText) { + inputPanelAvailableWidth += 200.0 + } + } + + let keyboardWasHidden = self.inputPanelExternalState.isKeyboardHidden + if environment.inputHeight > 0.0 || self.currentInputMode == .emoji || keyboardWasHidden { + inputPanelAvailableHeight = 200.0 + } + + var inputHeight = environment.inputHeight + if case .emoji = self.currentInputMode, let inputData = self.inputMediaNodeData { + let inputMediaNode: ChatEntityKeyboardInputNode + if let current = self.inputMediaNode { + inputMediaNode = current + } else { + inputMediaNode = ChatEntityKeyboardInputNode( + context: call.accountContext, + currentInputData: inputData, + updatedInputData: self.inputMediaNodeDataPromise.get(), + defaultToEmojiTab: true, + opaqueTopPanelBackground: false, + interaction: self.inputMediaInteraction, + chatPeerId: nil, + stateContext: self.inputMediaNodeStateContext + ) + inputMediaNode.externalTopPanelContainerImpl = nil + inputMediaNode.useExternalSearchContainer = true + if let inputPanelView = self.inputPanel.view, inputMediaNode.view.superview == nil { + self.containerView.insertSubview(inputMediaNode.view, belowSubview: inputPanelView) + } + self.inputMediaNode = inputMediaNode + } + + let presentationData = call.accountContext.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme) + let presentationInterfaceState = ChatPresentationInterfaceState( + chatWallpaper: .builtin(WallpaperSettings()), + theme: presentationData.theme, + strings: presentationData.strings, + dateTimeFormat: presentationData.dateTimeFormat, + nameDisplayOrder: presentationData.nameDisplayOrder, + limitsConfiguration: call.accountContext.currentLimitsConfiguration.with { $0 }, + fontSize: presentationData.chatFontSize, + bubbleCorners: presentationData.chatBubbleCorners, + accountPeerId: call.accountContext.account.peerId, + mode: .standard(.default), + chatLocation: .peer(id: call.accountContext.account.peerId), + subject: nil, + peerNearbyData: nil, + greetingData: nil, + pendingUnpinnedAllMessages: false, + activeGroupCallInfo: nil, + hasActiveGroupCall: false, + importState: nil, + threadData: nil, + isGeneralThreadClosed: nil, + replyMessage: nil, + accountPeerColor: nil, + businessIntro: nil + ) + + let availableInputMediaWidth = availableSize.width + let heightAndOverflow = inputMediaNode.updateLayout(width: availableInputMediaWidth, leftInset: 0.0, rightInset: 0.0, bottomInset: environment.safeInsets.bottom, standardInputHeight: environment.deviceMetrics.standardInputHeight(inLandscape: false), inputHeight: environment.inputHeight, maximumHeight: availableSize.height, inputPanelHeight: 0.0, transition: .immediate, interfaceState: presentationInterfaceState, layoutMetrics: environment.metrics, deviceMetrics: environment.deviceMetrics, isVisible: true, isExpanded: false) + let inputNodeHeight = heightAndOverflow.0 + let inputNodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - availableInputMediaWidth) / 2.0), y: availableSize.height - inputNodeHeight), size: CGSize(width: availableInputMediaWidth, height: inputNodeHeight)) + transition.setFrame(layer: inputMediaNode.layer, frame: inputNodeFrame) + + if inputNodeHeight > 0.0 { + inputHeight = inputNodeHeight + } + } else if let inputMediaNode = self.inputMediaNode { + self.inputMediaNode = nil + + var dismissingInputHeight = environment.inputHeight + if self.currentInputMode == .emoji || (dismissingInputHeight.isZero && keyboardWasHidden) { + dismissingInputHeight = max(inputHeight, environment.deviceMetrics.standardInputHeight(inLandscape: false)) + } + + if let animationHint = transition.userData(TextFieldComponent.AnimationHint.self), case .textFocusChanged = animationHint.kind { + dismissingInputHeight = 0.0 + } + + var targetFrame = inputMediaNode.frame + if dismissingInputHeight > 0.0 { + targetFrame.origin.y = availableSize.height - dismissingInputHeight + } else { + targetFrame.origin.y = availableSize.height + } + transition.setFrame(view: inputMediaNode.view, frame: targetFrame, completion: { [weak inputMediaNode] _ in + if let inputMediaNode { + Queue.mainQueue().after(0.2) { + inputMediaNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak inputMediaNode] _ in + inputMediaNode?.view.removeFromSuperview() + }) + } + } + }) + } + + if self.inputPanelExternalState.isEditing { + if self.currentInputMode == .emoji || (inputHeight.isZero && keyboardWasHidden) { + inputHeight = max(inputHeight, environment.deviceMetrics.standardInputHeight(inLandscape: false)) + } + } + + let nextInputMode: MessageInputPanelComponent.InputMode + switch self.currentInputMode { + case .text: + nextInputMode = .emoji + case .emoji: + nextInputMode = .text + default: + nextInputMode = .emoji + } + + self.inputPanel.parentState = state + inputPanelSize = self.inputPanel.update( + transition: transition, + component: AnyComponent(MessageInputPanelComponent( + externalState: self.inputPanelExternalState, + context: call.accountContext, + theme: environment.theme, + strings: environment.strings, + style: .glass, + placeholder: .plain("Message"), + sendPaidMessageStars: nil, + maxLength: 128, + queryTypes: [.mention, .hashtag], + alwaysDarkWhenHasText: false, + useGrayBackground: false, + resetInputContents: nil, + nextInputMode: { _ in return nextInputMode }, + areVoiceMessagesAvailable: false, + presentController: { c in + //controller.present(c, in: .window(.root)) + }, + presentInGlobalOverlay: { c in + //controller.presentInGlobalOverlay(c) + }, + sendMessageAction: { [weak self] transition in + guard let self else { + return + } + self.inputPanelIsActive = false + if self.inputPanelExternalState.hasText { + self.nextSendMessageTransition = transition + + self.sendInput() + } else { + self.deactivateInput() + } + }, + sendMessageOptionsAction: nil, + sendStickerAction: { _ in }, + setMediaRecordingActive: nil, + lockMediaRecording: nil, + stopAndPreviewMediaRecording: nil, + discardMediaRecordingPreview: nil, + attachmentAction: nil, + myReaction: nil, + likeAction: nil, + likeOptionsAction: nil, + inputModeAction: { [weak self] in + if let self { + switch self.currentInputMode { + case .text: + self.currentInputMode = .emoji + case .emoji: + self.currentInputMode = .text + default: + self.currentInputMode = .emoji + } + if self.currentInputMode == .text { + self.activateInput() + } else { + self.state?.updated(transition: .immediate) + } + } + }, + timeoutAction: nil, + forwardAction: nil, + moreAction: nil, + presentCaptionPositionTooltip: nil, + presentVoiceMessagesUnavailableTooltip: nil, + presentTextLengthLimitTooltip: { + }, + presentTextFormattingTooltip: { + }, + paste: { _ in + }, + audioRecorder: nil, + videoRecordingStatus: nil, + isRecordingLocked: false, + hasRecordedVideo: false, + recordedAudioPreview: nil, + hasRecordedVideoPreview: false, + wasRecordingDismissed: false, + timeoutValue: nil, + timeoutSelected: false, + displayGradient: false, + bottomInset: 0.0, + isFormattingLocked: false, + hideKeyboard: self.currentInputMode == .emoji, + customInputView: nil, + forceIsEditing: self.currentInputMode == .emoji, + disabledPlaceholder: nil, + header: nil, + isChannel: false, + storyItem: nil, + chatLocation: nil + )), + environment: {}, + containerSize: CGSize(width: inputPanelAvailableWidth, height: inputPanelAvailableHeight) + ) + + if inputHeight > 0.0 { + inputPanelBottomInset = inputHeight - environment.safeInsets.bottom + } else { + if self.inputPanelExternalState.isEditing { + inputPanelBottomInset = 70.0 + } else { + inputPanelBottomInset = -inputPanelSize.height - environment.safeInsets.bottom + } + } + let inputPanelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - inputPanelSize.width) / 2.0), y: availableSize.height - environment.safeInsets.bottom - inputPanelBottomInset - inputPanelSize.height - 3.0), size: inputPanelSize) + if let inputPanelView = self.inputPanel.view { + if inputPanelView.superview == nil { + self.containerView.addSubview(inputPanelView) + } + transition.setFrame(view: inputPanelView, frame: inputPanelFrame) + } + } else if let inputPanelView = self.inputPanel.view { + self.inputPanelExternalState.isEditing = false + + var inputPanelFrame = inputPanelView.frame + inputPanelFrame.origin.y = availableSize.height + transition.setFrame(view: inputPanelView, frame: inputPanelFrame) + } + + + + + + var reactionsInset = 0.0 + var effectiveDisplayReactions = false + if self.inputPanelExternalState.isEditing && !self.inputPanelExternalState.hasText { + effectiveDisplayReactions = true + } + + if let reactionContextNode = self.reactionContextNode, self.willDismissReactionContextNode !== reactionContextNode, (reactionContextNode.isReactionSearchActive && !reactionContextNode.isAnimatingOutToReaction && !reactionContextNode.isAnimatingOut) { + effectiveDisplayReactions = true + } + + let reactionsAnchorRect: CGRect = CGRect(origin: CGPoint(x: availableSize.width - 44.0, y: availableSize.height - inputPanelBottomInset - inputPanelSize.height - 33.0), size: CGSize(width: 44.0, height: 44.0)) + if let reactionItems = self.reactionItems, effectiveDisplayReactions { + reactionsInset += 63.0 + + let reactionContextNode: ReactionContextNode + var reactionContextNodeTransition = transition + if let current = self.reactionContextNode { + reactionContextNode = current + } else { + let context = component.initialCall.accountContext + reactionContextNodeTransition = .immediate + reactionContextNode = ReactionContextNode( + context: call.accountContext, + animationCache: call.accountContext.animationCache, + presentationData: call.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme), + style: .glass, + items: reactionItems.map { ReactionContextItem.reaction(item: $0, icon: .none) }, + selectedItems: Set(), + title: nil, + reactionsLocked: false, + alwaysAllowPremiumReactions: false, + allPresetReactionsAreAvailable: false, + getEmojiContent: { animationCache, animationRenderer in + let mappedReactionItems: [EmojiComponentReactionItem] = reactionItems.map { reaction -> EmojiComponentReactionItem in + return EmojiComponentReactionItem(reaction: reaction.reaction.rawValue, file: reaction.stillAnimation) + } + + return EmojiPagerContentComponent.emojiInputData( + context: context, + animationCache: animationCache, + animationRenderer: animationRenderer, + isStandalone: false, + subject: .reaction(onlyTop: false), + hasTrending: false, + topReactionItems: mappedReactionItems, + areUnicodeEmojiEnabled: false, + areCustomEmojiEnabled: true, + chatPeerId: context.account.peerId, + selectedItems: Set(), + premiumIfSavedMessages: false + ) + }, + isExpandedUpdated: { [weak self] transition in + guard let self else { + return + } + self.state?.updated(transition: ComponentTransition(transition)) + }, + requestLayout: { [weak self] transition in + guard let self else { + return + } + self.state?.updated(transition: ComponentTransition(transition)) + }, + requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in + guard let self else { + return + } + self.state?.updated(transition: ComponentTransition(transition)) + } + ) + reactionContextNode.displayTail = false //self.displayLikeReactions + reactionContextNode.forceTailToRight = true //self.displayLikeReactions + reactionContextNode.forceDark = true + self.reactionContextNode = reactionContextNode + +// if let tempReactionsGesture = self.tempReactionsGesture { +// tempReactionsGesture.externalUpdated = { [weak self] view, point in +// guard let self, let view, let reactionContextNode = self.reactionContextNode else { +// return +// } +// let presentationPoint = view.convert(point, to: reactionContextNode.view) +// reactionContextNode.highlightGestureMoved(location: presentationPoint, hover: false) +// } +// tempReactionsGesture.externalEnded = { [weak self] viewAndPoint in +// guard let self, let viewAndPoint, let reactionContextNode = self.reactionContextNode else { +// return +// } +// let _ = viewAndPoint +// /*let (view, point) = viewAndPoint +// let presentationPoint = view.convert(point, to: reactionContextNode.view) +// let _ = presentationPoint*/ +// reactionContextNode.highlightGestureFinished(performAction: true) +// } +// } + + reactionContextNode.reactionSelected = { [weak self, weak reactionContextNode] updateReaction, _ in + guard let self, let reactionContextNode else { + return + } + + let _ = (context.engine.stickers.availableReactions() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self, weak reactionContextNode] availableReactions in + guard let self, let _ = availableReactions else { + return + } + + let targetSize = CGSize(width: 24.0, height: 24.0) + let targetView = UIView(frame: CGRect(origin: CGPoint(x: 230.0, y: self.bounds.height - targetSize.height - 133.0), size: targetSize)) //UIView(frame: CGRect(origin: CGPoint(x: floor((self.bounds.width - targetSize.width) * 0.5), y: floor((self.bounds.height - targetSize.height) * 0.5)), size: targetSize)) + targetView.isUserInteractionEnabled = false + self.addSubview(targetView) + + if let reactionContextNode { + reactionContextNode.willAnimateOutToReaction(value: updateReaction.reaction) + reactionContextNode.animateOutToReaction(value: updateReaction.reaction, targetView: targetView, hideNode: false, animateTargetContainer: nil, addStandaloneReactionAnimation: nil, onHit: nil, completion: { [weak targetView, weak reactionContextNode] in + targetView?.removeFromSuperview() + if let reactionContextNode { + reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak reactionContextNode] _ in + reactionContextNode?.view.removeFromSuperview() + }) + } + }) + } + + if hasFirstResponder(self) { + self.currentInputMode = .text + self.endEditing(true) + } + self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.25, curve: .easeInOut))) + + var text = "" + var entities: [MessageTextEntity] = [] + switch updateReaction { + case let .builtin(textValue): + text = textValue + case let .custom(fileId, file): + if let file { + loop: for attribute in file.attributes { + if case let .CustomEmoji(_, _, alt, _) = attribute { + text = alt + let length = (alt as NSString).length + entities = [MessageTextEntity(range: 0 ..< length, type: .CustomEmoji(stickerPack: nil, fileId: fileId))] + } + } + } + case .stars: + break + } + + guard case let .group(groupCall) = self.currentCall, let call = groupCall as? PresentationGroupCallImpl else { + return + } + call.sendMessage(text: text, entities: entities) + }) + } + + reactionContextNode.premiumReactionsSelected = { [weak self] file in + guard let self, let component = self.component else { + return + } + + let _ = component + +// guard let file else { +// let context = component.context +// var replaceImpl: ((ViewController) -> Void)? +// let controller = PremiumDemoScreen(context: context, subject: .uniqueReactions, forceDark: true, action: { +// let controller = PremiumIntroScreen(context: context, source: .reactions) +// replaceImpl?(controller) +// }) +// controller.disposed = { [weak self] in +// self?.updateIsProgressPaused() +// } +// replaceImpl = { [weak controller] c in +// controller?.replace(with: c) +// } +// component.controller()?.push(controller) +// return +// } +// +// let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } +// let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: component.context, file: file, loop: true, title: nil, text: presentationData.strings.Chat_PremiumReactionToastTitle, undoText: presentationData.strings.Chat_PremiumReactionToastAction, customAction: { [weak self] in +// guard let self, let component = self.component else { +// return +// } +// +// let context = component.context +// var replaceImpl: ((ViewController) -> Void)? +// let controller = PremiumDemoScreen(context: context, subject: .uniqueReactions, forceDark: true, action: { +// let controller = PremiumIntroScreen(context: context, source: .reactions) +// replaceImpl?(controller) +// }) +// controller.disposed = { [weak self] in +// self?.updateIsProgressPaused() +// } +// replaceImpl = { [weak controller] c in +// controller?.replace(with: c) +// } +// component.controller()?.push(controller) +// }), elevatedLayout: false, animateInAsReplacement: false, appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in true }) +// component.controller()?.present(undoController, in: .current) + } + } + + var animateReactionsIn = false + if reactionContextNode.view.superview == nil { + animateReactionsIn = true + self.containerView.addSubview(reactionContextNode.view) + } + +// if reactionContextNode.isAnimatingOutToReaction { +// if !reactionContextNode.isAnimatingOut { +// reactionContextNode.animateOut(to: reactionsAnchorRect, animatingOutToReaction: true) +// } +// } else { + reactionContextNodeTransition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(), size: availableSize)) + reactionContextNode.updateLayout(size: availableSize, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: true, isCoveredByInput: false, isAnimatingOut: false, transition: reactionContextNodeTransition.containedViewLayoutTransition) + + if animateReactionsIn { + reactionContextNode.animateIn(from: reactionsAnchorRect) + } +// } + } else { + if let reactionContextNode = self.reactionContextNode { + if let disappearingReactionContextNode = self.disappearingReactionContextNode { + disappearingReactionContextNode.view.removeFromSuperview() + } + self.disappearingReactionContextNode = reactionContextNode + + self.reactionContextNode = nil + let reactionTransition = ComponentTransition.easeInOut(duration: 0.25) + reactionTransition.setAlpha(view: reactionContextNode.view, alpha: 0.0, completion: { [weak reactionContextNode] _ in + reactionContextNode?.view.removeFromSuperview() + }) + } + } + if let reactionContextNode = self.disappearingReactionContextNode { + if !reactionContextNode.isAnimatingOutToReaction { + transition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(), size: availableSize)) + reactionContextNode.updateLayout(size: availableSize, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: reactionContextNode.centerAligned, isCoveredByInput: false, isAnimatingOut: false, transition: transition.containedViewLayoutTransition) + } + } + + + var sendActionTransition: MessageListComponent.SendActionTransition? + if let next = self.nextSendMessageTransition { + self.nextSendMessageTransition = nil + sendActionTransition = MessageListComponent.SendActionTransition( + textSnapshotView: next.textSnapshotView, globalFrame: next.globalFrame, cornerRadius: next.cornerRadius + ) + } + + var messageItems: [MessageListComponent.Item] = [] + if let messagesState = self.messagesState { + for message in messagesState.messages.reversed() { + guard let author = message.author else { + continue + } + messageItems.append( + MessageListComponent.Item( + id: message.id, + peer: author, + text: message.text, + entities: message.entities + ) + ) + } + } + let messagesListSize = self.messagesList.update( + transition: transition, + component: AnyComponent(MessageListComponent( + context: call.accountContext, + items: messageItems, + availableReactions: self.reactionItems, + sendActionTransition: sendActionTransition + )), + environment: {}, + containerSize: availableSize + ) + let messagesBottomInset: CGFloat = max(inputPanelBottomInset + inputPanelSize.height + 31.0, 118.0) + reactionsInset + + let messagesListFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - messagesListSize.height - messagesBottomInset), size: messagesListSize) + if let messagesListView = self.messagesList.view { + if messagesListView.superview == nil { + messagesListView.isUserInteractionEnabled = false + self.containerView.addSubview(messagesListView) + } + transition.setFrame(view: messagesListView, frame: messagesListFrame) } return availableSize @@ -3079,7 +3905,7 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo theme: defaultDarkPresentationTheme, editing: false, title: nil, - accentColor: UIColor(rgb: 0x3E88F7), + accentColor: UIColor(rgb: 0x42a2ff), backgroundColors: [], bubbleColors: [], animateBubbleColors: false @@ -3248,3 +4074,123 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo } } } + +private func hasFirstResponder(_ view: UIView) -> Bool { + if view.isFirstResponder { + return true + } + for subview in view.subviews { + if hasFirstResponder(subview) { + return true + } + } + return false +} + +private func allowedStoryReactions(context: AccountContext) -> Signal<[ReactionItem], NoError> { + let viewKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudTopReactions) + let topReactions = context.account.postbox.combinedView(keys: [viewKey]) + |> map { views -> [RecentReactionItem] in + guard let view = views.views[viewKey] as? OrderedItemListView else { + return [] + } + return view.items.compactMap { item -> RecentReactionItem? in + return item.contents.get(RecentReactionItem.self) + } + } + + return combineLatest( + context.engine.stickers.availableReactions(), + topReactions + ) + |> take(1) + |> map { availableReactions, topReactions -> [ReactionItem] in + guard let availableReactions = availableReactions else { + return [] + } + + var result: [ReactionItem] = [] + + var existingIds = Set() + + for topReaction in topReactions { + switch topReaction.content { + case let .builtin(value): + if let reaction = availableReactions.reactions.first(where: { $0.value == .builtin(value) }) { + guard let centerAnimation = reaction.centerAnimation else { + continue + } + guard let aroundAnimation = reaction.aroundAnimation else { + continue + } + + if existingIds.contains(reaction.value) { + continue + } + existingIds.insert(reaction.value) + + result.append(ReactionItem( + reaction: ReactionItem.Reaction(rawValue: reaction.value), + appearAnimation: reaction.appearAnimation, + stillAnimation: reaction.selectAnimation, + listAnimation: centerAnimation, + largeListAnimation: reaction.activateAnimation, + applicationAnimation: aroundAnimation, + largeApplicationAnimation: reaction.effectAnimation, + isCustom: false + )) + } else { + continue + } + case let .custom(file): + if existingIds.contains(.custom(file.fileId.id)) { + continue + } + existingIds.insert(.custom(file.fileId.id)) + + result.append(ReactionItem( + reaction: ReactionItem.Reaction(rawValue: .custom(file.fileId.id)), + appearAnimation: file, + stillAnimation: file, + listAnimation: file, + largeListAnimation: file, + applicationAnimation: nil, + largeApplicationAnimation: nil, + isCustom: true + )) + case .stars: + break + } + } + + for reaction in availableReactions.reactions { + guard let centerAnimation = reaction.centerAnimation else { + continue + } + guard let aroundAnimation = reaction.aroundAnimation else { + continue + } + if !reaction.isEnabled { + continue + } + + if existingIds.contains(reaction.value) { + continue + } + existingIds.insert(reaction.value) + + result.append(ReactionItem( + reaction: ReactionItem.Reaction(rawValue: reaction.value), + appearAnimation: reaction.appearAnimation, + stillAnimation: reaction.selectAnimation, + listAnimation: centerAnimation, + largeListAnimation: reaction.activateAnimation, + applicationAnimation: aroundAnimation, + largeApplicationAnimation: reaction.effectAnimation, + isCustom: false + )) + } + + return result + } +} diff --git a/submodules/TelegramCallsUI/Sources/VideoChatTitleComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatTitleComponent.swift index 9736688ee3..a758001b86 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatTitleComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatTitleComponent.swift @@ -201,7 +201,7 @@ final class VideoChatTitleComponent: Component { self.tapRecognizer?.isEnabled = component.longTapAction != nil || component.tapAction != nil - let spacing: CGFloat = 1.0 + let spacing: CGFloat = 0.0 var maxTitleWidth = availableSize.width if component.isRecording { @@ -219,7 +219,7 @@ final class VideoChatTitleComponent: Component { let statusComponent: AnyComponent statusComponent = AnyComponent(AnimatedTextComponent( - font: Font.regular(13.0), + font: Font.regular(12.0), color: UIColor(white: 1.0, alpha: 0.5), items: component.status )) diff --git a/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift b/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift index 87373e26a7..5f3c71b847 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift @@ -175,6 +175,16 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? { case let .peerColor(_, color, backgroundEmojiIdValue): nameColorIndex = color backgroundEmojiId = backgroundEmojiIdValue + case let .peerColorCollectible(_, collectibleId, giftEmojiId, backgroundEmojiId, accentColor, colors, darkAccentColor, darkColors): + let _ = collectibleId + let _ = giftEmojiId + let _ = backgroundEmojiId + let _ = accentColor + let _ = colors + let _ = darkAccentColor + let _ = darkColors + case .inputPeerColorCollectible: + break } } @@ -185,6 +195,8 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? { case let .peerColor(_, color, backgroundEmojiIdValue): profileColorIndex = color profileBackgroundEmojiId = backgroundEmojiIdValue + default: + break } } @@ -254,6 +266,16 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? { case let .peerColor(_, color, backgroundEmojiIdValue): nameColorIndex = color backgroundEmojiId = backgroundEmojiIdValue + case let .peerColorCollectible(_, collectibleId, giftEmojiId, backgroundEmojiId, accentColor, colors, darkAccentColor, darkColors): + let _ = collectibleId + let _ = giftEmojiId + let _ = backgroundEmojiId + let _ = accentColor + let _ = colors + let _ = darkAccentColor + let _ = darkColors + case .inputPeerColorCollectible: + break } } @@ -264,6 +286,8 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? { case let .peerColor(_, color, backgroundEmojiIdValue): profileColorIndex = color profileBackgroundEmojiId = backgroundEmojiIdValue + default: + break } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index a364e4da81..0d6385478f 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -232,7 +232,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { } switch action { - case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique, .messageActionPaidMessagesRefunded, .messageActionPaidMessagesPrice, .messageActionTodoCompletions, .messageActionTodoAppendTasks, .messageActionSuggestedPostApproval, .messageActionGiftTon, .messageActionSuggestedPostSuccess, .messageActionSuggestedPostRefund: + case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique, .messageActionPaidMessagesRefunded, .messageActionPaidMessagesPrice, .messageActionTodoCompletions, .messageActionTodoAppendTasks, .messageActionSuggestedPostApproval, .messageActionGiftTon, .messageActionSuggestedPostSuccess, .messageActionSuggestedPostRefund, .messageActionSuggestBirthday: break case let .messageActionChannelMigrateFrom(_, chatId): result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 37e7e5e2b8..0bc723812b 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -272,6 +272,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .suggestedPostSuccess(amount: CurrencyAmount(apiAmount: price))) case let .messageActionSuggestedPostRefund(flags): return TelegramMediaAction(action: .suggestedPostRefund(TelegramMediaActionType.SuggestedPostRefund(isUserInitiated: (flags & (1 << 0)) != 0))) + case let .messageActionSuggestBirthday(birthday): + return TelegramMediaAction(action: .suggestedBirthday(TelegramBirthday(apiBirthday: birthday))) } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift index e95fc8896f..6ce71f46c1 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift @@ -133,6 +133,16 @@ extension TelegramUser { case let .peerColor(_, color, backgroundEmojiIdValue): nameColorIndex = color backgroundEmojiId = backgroundEmojiIdValue + case let .peerColorCollectible(_, collectibleId, giftEmojiId, backgroundEmojiId, accentColor, colors, darkAccentColor, darkColors): + let _ = collectibleId + let _ = giftEmojiId + let _ = backgroundEmojiId + let _ = accentColor + let _ = colors + let _ = darkAccentColor + let _ = darkColors + case .inputPeerColorCollectible: + break } } @@ -143,6 +153,8 @@ extension TelegramUser { case let .peerColor(_, color, backgroundEmojiIdValue): profileColorIndex = color profileBackgroundEmojiId = backgroundEmojiIdValue + default: + break } } @@ -242,6 +254,16 @@ extension TelegramUser { case let .peerColor(_, color, backgroundEmojiIdValue): nameColorIndex = color backgroundEmojiId = backgroundEmojiIdValue + case let .peerColorCollectible(_, collectibleId, giftEmojiId, backgroundEmojiId, accentColor, colors, darkAccentColor, darkColors): + let _ = collectibleId + let _ = giftEmojiId + let _ = backgroundEmojiId + let _ = accentColor + let _ = colors + let _ = darkAccentColor + let _ = darkColors + case .inputPeerColorCollectible: + break } } @@ -252,6 +274,8 @@ extension TelegramUser { case let .peerColor(_, color, backgroundEmojiIdValue): profileColorIndex = color profileBackgroundEmojiId = backgroundEmojiIdValue + default: + break } } diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index cc583a8fd0..c04a4858ee 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -1539,7 +1539,7 @@ public final class AccountViewTracker { let peerId = slice[i].0 let value = result[i] transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData in - var cachedData = cachedData as? CachedUserData ?? CachedUserData(about: nil, botInfo: nil, editableBotInfo: nil, peerStatusSettings: nil, pinnedMessageId: nil, isBlocked: false, commonGroupCount: 0, voiceCallsAvailable: true, videoCallsAvailable: true, callsPrivate: true, canPinMessages: true, hasScheduledMessages: true, autoremoveTimeout: .unknown, chatTheme: nil, photo: .unknown, personalPhoto: .unknown, fallbackPhoto: .unknown, voiceMessagesAvailable: true, wallpaper: nil, flags: [], businessHours: nil, businessLocation: nil, greetingMessage: nil, awayMessage: nil, connectedBot: nil, businessIntro: .unknown, birthday: nil, personalChannel: .unknown, botPreview: nil, starGiftsCount: nil, starRefProgram: nil, verification: nil, sendPaidMessageStars: nil, disallowedGifts: [], botGroupAdminRights: nil, botChannelAdminRights: nil, starRating: nil, pendingStarRating: nil, mainProfileTab: nil, savedMusic: nil) + var cachedData = cachedData as? CachedUserData ?? CachedUserData(about: nil, botInfo: nil, editableBotInfo: nil, peerStatusSettings: nil, pinnedMessageId: nil, isBlocked: false, commonGroupCount: 0, voiceCallsAvailable: true, videoCallsAvailable: true, callsPrivate: true, canPinMessages: true, hasScheduledMessages: true, autoremoveTimeout: .unknown, chatTheme: nil, photo: .unknown, personalPhoto: .unknown, fallbackPhoto: .unknown, voiceMessagesAvailable: true, wallpaper: nil, flags: [], businessHours: nil, businessLocation: nil, greetingMessage: nil, awayMessage: nil, connectedBot: nil, businessIntro: .unknown, birthday: nil, personalChannel: .unknown, botPreview: nil, starGiftsCount: nil, starRefProgram: nil, verification: nil, sendPaidMessageStars: nil, disallowedGifts: [], botGroupAdminRights: nil, botChannelAdminRights: nil, starRating: nil, pendingStarRating: nil, mainProfileTab: nil, savedMusic: nil, note: nil) var flags = cachedData.flags var sendPaidMessageStars = cachedData.sendPaidMessageStars switch value { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift index 14a240f775..71a525f59b 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift @@ -1086,6 +1086,48 @@ public final class CachedUserData: CachedPeerData { } } + public final class Note: Codable, Equatable { + private enum CodingKeys: String, CodingKey { + case text + case entities + } + + public let text: String + public let entities: [MessageTextEntity] + + public init(text: String, entities: [MessageTextEntity]) { + self.text = text + self.entities = entities + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.text = try container.decode(String.self, forKey: .text) + self.entities = try container.decode([MessageTextEntity].self, forKey: .entities) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.text, forKey: .text) + try container.encode(self.entities, forKey: .entities) + } + + public static func ==(lhs: Note, rhs: Note) -> Bool { + if lhs === rhs { + return true + } + if lhs.text != rhs.text { + return false + } + if lhs.entities != rhs.entities { + return false + } + return true + } + } + public let about: String? public let botInfo: BotInfo? public let editableBotInfo: EditableBotInfo? @@ -1126,6 +1168,7 @@ public final class CachedUserData: CachedPeerData { public let pendingStarRating: TelegramStarPendingRating? public let mainProfileTab: TelegramProfileTab? public let savedMusic: TelegramMediaFile? + public let note: Note? public let peerIds: Set public let messageIds: Set @@ -1174,9 +1217,10 @@ public final class CachedUserData: CachedPeerData { self.pendingStarRating = nil self.mainProfileTab = nil self.savedMusic = nil + self.note = nil } - public init(about: String?, botInfo: BotInfo?, editableBotInfo: EditableBotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, chatTheme: ChatTheme?, photo: CachedPeerProfilePhoto, personalPhoto: CachedPeerProfilePhoto, fallbackPhoto: CachedPeerProfilePhoto, voiceMessagesAvailable: Bool, wallpaper: TelegramWallpaper?, flags: CachedUserFlags, businessHours: TelegramBusinessHours?, businessLocation: TelegramBusinessLocation?, greetingMessage: TelegramBusinessGreetingMessage?, awayMessage: TelegramBusinessAwayMessage?, connectedBot: TelegramAccountConnectedBot?, businessIntro: CachedTelegramBusinessIntro, birthday: TelegramBirthday?, personalChannel: CachedTelegramPersonalChannel, botPreview: BotPreview?, starGiftsCount: Int32?, starRefProgram: TelegramStarRefProgram?, verification: PeerVerification?, sendPaidMessageStars: StarsAmount?, disallowedGifts: TelegramDisallowedGifts?, botGroupAdminRights: TelegramChatAdminRights?, botChannelAdminRights: TelegramChatAdminRights?, starRating: TelegramStarRating?, pendingStarRating: TelegramStarPendingRating?, mainProfileTab: TelegramProfileTab?, savedMusic: TelegramMediaFile?) { + public init(about: String?, botInfo: BotInfo?, editableBotInfo: EditableBotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, chatTheme: ChatTheme?, photo: CachedPeerProfilePhoto, personalPhoto: CachedPeerProfilePhoto, fallbackPhoto: CachedPeerProfilePhoto, voiceMessagesAvailable: Bool, wallpaper: TelegramWallpaper?, flags: CachedUserFlags, businessHours: TelegramBusinessHours?, businessLocation: TelegramBusinessLocation?, greetingMessage: TelegramBusinessGreetingMessage?, awayMessage: TelegramBusinessAwayMessage?, connectedBot: TelegramAccountConnectedBot?, businessIntro: CachedTelegramBusinessIntro, birthday: TelegramBirthday?, personalChannel: CachedTelegramPersonalChannel, botPreview: BotPreview?, starGiftsCount: Int32?, starRefProgram: TelegramStarRefProgram?, verification: PeerVerification?, sendPaidMessageStars: StarsAmount?, disallowedGifts: TelegramDisallowedGifts?, botGroupAdminRights: TelegramChatAdminRights?, botChannelAdminRights: TelegramChatAdminRights?, starRating: TelegramStarRating?, pendingStarRating: TelegramStarPendingRating?, mainProfileTab: TelegramProfileTab?, savedMusic: TelegramMediaFile?, note: Note?) { self.about = about self.botInfo = botInfo self.editableBotInfo = editableBotInfo @@ -1217,6 +1261,7 @@ public final class CachedUserData: CachedPeerData { self.pendingStarRating = pendingStarRating self.mainProfileTab = mainProfileTab self.savedMusic = savedMusic + self.note = note self.peerIds = Set() @@ -1306,12 +1351,14 @@ public final class CachedUserData: CachedPeerData { self.pendingStarRating = decoder.decodeCodable(TelegramStarPendingRating.self, forKey: "pendingStarRating") self.mainProfileTab = decoder.decodeCodable(TelegramProfileTab.self, forKey: "mainProfileTab") - + if let savedMusic = decoder.decodeObjectForKey("savedMusic", decoder: { TelegramMediaFile(decoder: $0) }) as? TelegramMediaFile { self.savedMusic = savedMusic } else { self.savedMusic = nil } + + self.note = decoder.decodeCodable(Note.self, forKey: "note") } public func encode(_ encoder: PostboxEncoder) { @@ -1477,6 +1524,18 @@ public final class CachedUserData: CachedPeerData { } else { encoder.encodeNil(forKey: "mainProfileTab") } + + if let savedMusic = self.savedMusic { + encoder.encodeObject(savedMusic, forKey: "savedMusic") + } else { + encoder.encodeNil(forKey: "savedMusic") + } + + if let note = self.note { + encoder.encodeCodable(note, forKey: "note") + } else { + encoder.encodeNil(forKey: "note") + } } public func isEqual(to: CachedPeerData) -> Bool { @@ -1550,168 +1609,175 @@ public final class CachedUserData: CachedPeerData { if other.savedMusic != self.savedMusic { return false } + if other.note != self.note { + return false + } return other.about == self.about && other.botInfo == self.botInfo && other.editableBotInfo == self.editableBotInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.voiceCallsAvailable == other.voiceCallsAvailable && self.videoCallsAvailable == other.videoCallsAvailable && self.callsPrivate == other.callsPrivate && self.hasScheduledMessages == other.hasScheduledMessages && self.autoremoveTimeout == other.autoremoveTimeout && self.chatTheme == other.chatTheme && self.photo == other.photo && self.personalPhoto == other.personalPhoto && self.fallbackPhoto == other.fallbackPhoto && self.voiceMessagesAvailable == other.voiceMessagesAvailable && self.flags == other.flags && self.wallpaper == other.wallpaper } public func withUpdatedAbout(_ about: String?) -> CachedUserData { - return CachedUserData(about: about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedBotInfo(_ botInfo: BotInfo?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedEditableBotInfo(_ editableBotInfo: EditableBotInfo?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedIsBlocked(_ isBlocked: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedCommonGroupCount(_ commonGroupCount: Int32) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedVoiceCallsAvailable(_ voiceCallsAvailable: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedVideoCallsAvailable(_ videoCallsAvailable: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedCallsPrivate(_ callsPrivate: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedCanPinMessages(_ canPinMessages: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedChatTheme(_ chatTheme: ChatTheme?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedPhoto(_ photo: CachedPeerProfilePhoto) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedPersonalPhoto(_ personalPhoto: CachedPeerProfilePhoto) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedFallbackPhoto(_ fallbackPhoto: CachedPeerProfilePhoto) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedVoiceMessagesAvailable(_ voiceMessagesAvailable: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedWallpaper(_ wallpaper: TelegramWallpaper?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedFlags(_ flags: CachedUserFlags) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedBusinessHours(_ businessHours: TelegramBusinessHours?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedBusinessLocation(_ businessLocation: TelegramBusinessLocation?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedGreetingMessage(_ greetingMessage: TelegramBusinessGreetingMessage?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedAwayMessage(_ awayMessage: TelegramBusinessAwayMessage?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedConnectedBot(_ connectedBot: TelegramAccountConnectedBot?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedBusinessIntro(_ businessIntro: TelegramBusinessIntro?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: .known(businessIntro), birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: .known(businessIntro), birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedBirthday(_ birthday: TelegramBirthday?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedPersonalChannel(_ personalChannel: TelegramPersonalChannel?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: .known(personalChannel), botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: .known(personalChannel), botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedBotPreview(_ botPreview: BotPreview?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedStarGiftsCount(_ starGiftsCount: Int32?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedStarRefProgram(_ starRefProgram: TelegramStarRefProgram?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedVerification(_ verification: PeerVerification?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedSendPaidMessageStars(_ sendPaidMessageStars: StarsAmount?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: sendPaidMessageStars, disallowedGifts: self.disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedDisallowedGifts(_ disallowedGifts: TelegramDisallowedGifts) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedBotGroupAdminRights(_ botGroupAdminRights: TelegramChatAdminRights?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: disallowedGifts, botGroupAdminRights: botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: disallowedGifts, botGroupAdminRights: botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedBotChannelAdminRights(_ botChannelAdminRights: TelegramChatAdminRights?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedStarRating(_ starRating: TelegramStarRating?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedPendingStarRating(_ pendingStarRating: TelegramStarPendingRating?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedMainProfileTab(_ mainProfileTab: TelegramProfileTab?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: mainProfileTab, savedMusic: self.savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: mainProfileTab, savedMusic: self.savedMusic, note: self.note) } public func withUpdatedSavedMusic(_ savedMusic: TelegramMediaFile?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: savedMusic) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: savedMusic, note: self.note) + } + + public func withUpdatedNote(_ note: Note?) -> CachedUserData { + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, chatTheme: self.chatTheme, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount, starRefProgram: self.starRefProgram, verification: self.verification, sendPaidMessageStars: self.sendPaidMessageStars, disallowedGifts: disallowedGifts, botGroupAdminRights: self.botGroupAdminRights, botChannelAdminRights: self.botChannelAdminRights, starRating: self.starRating, pendingStarRating: self.pendingStarRating, mainProfileTab: self.mainProfileTab, savedMusic: self.savedMusic, note: note) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index e7052f7854..faad9541f5 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -254,6 +254,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case giftTon(currency: String, amount: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?) case suggestedPostSuccess(amount: CurrencyAmount) case suggestedPostRefund(SuggestedPostRefund) + case suggestedBirthday(TelegramBirthday) public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) @@ -419,6 +420,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { self = .suggestedPostSuccess(amount: decoder.decodeCodable(CurrencyAmount.self, forKey: "amt") ?? CurrencyAmount(amount: .zero, currency: .stars)) case 54: self = .suggestedPostRefund(decoder.decodeCodable(SuggestedPostRefund.self, forKey: "s") ?? SuggestedPostRefund(isUserInitiated: true)) + case 55: + self = .suggestedBirthday(decoder.decodeCodable(TelegramBirthday.self, forKey: "birthday") ?? TelegramBirthday(day: 1, month: 1, year: nil)) default: self = .unknown } @@ -874,6 +877,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case let .suggestedPostRefund(status): encoder.encodeInt32(54, forKey: "_rawValue") encoder.encodeCodable(status, forKey: "s") + case let .suggestedBirthday(birthday): + encoder.encodeInt32(55, forKey: "_rawValue") + encoder.encodeCodable(birthday, forKey: "birthday") } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/UpdateAccountPeerName.swift b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/UpdateAccountPeerName.swift index 48216dd0d1..4aad4b509f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/UpdateAccountPeerName.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/UpdateAccountPeerName.swift @@ -64,16 +64,22 @@ func _internal_updateNameColorAndEmoji(account: Account, nameColor: PeerNameColo |> switchToLatest |> castError(UpdateNameColorAndEmojiError.self) |> mapToSignal { _ -> Signal in - let flagsReplies: Int32 = (1 << 0) | (1 << 2) - - var flagsProfile: Int32 = (1 << 0) | (1 << 1) - if profileColor != nil { - flagsProfile |= (1 << 2) + var flagsReplies: Int32 = (1 << 0) + if let _ = backgroundEmojiId { + flagsReplies |= (1 << 1) } + var flagsProfile: Int32 = 0 + if let _ = profileColor { + flagsProfile |= (1 << 0) + } + if let _ = profileBackgroundEmojiId { + flagsProfile |= (1 << 1) + } + return combineLatest( - account.network.request(Api.functions.account.updateColor(flags: flagsReplies, color: nameColor.rawValue, backgroundEmojiId: backgroundEmojiId ?? 0)), - account.network.request(Api.functions.account.updateColor(flags: flagsProfile, color: profileColor?.rawValue, backgroundEmojiId: profileBackgroundEmojiId ?? 0)) + account.network.request(Api.functions.account.updateColor(flags: (1 << 2), color: .peerColor(flags: flagsReplies, color: nameColor.rawValue, backgroundEmojiId: backgroundEmojiId))), + account.network.request(Api.functions.account.updateColor(flags: (1 << 1) | (1 << 2), color: .peerColor(flags: flagsProfile, color: profileColor?.rawValue ?? 0, backgroundEmojiId: profileBackgroundEmojiId))) ) |> mapError { _ -> UpdateNameColorAndEmojiError in return .generic diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index b6d444c2d9..6b20b98b6c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -3395,12 +3395,16 @@ public final class GroupCallMessagesContext { public let author: EnginePeer? public let text: String public let entities: [MessageTextEntity] + public let date: Int32 + public let lifetime: Int32 - public init(id: Int64, author: EnginePeer?, text: String, entities: [MessageTextEntity]) { + public init(id: Int64, author: EnginePeer?, text: String, entities: [MessageTextEntity], date: Int32, lifetime: Int32) { self.id = id self.author = author self.text = text self.entities = entities + self.date = date + self.lifetime = lifetime } public static func ==(lhs: Message, rhs: Message) -> Bool { @@ -3419,6 +3423,12 @@ public final class GroupCallMessagesContext { if lhs.entities != rhs.entities { return false } + if lhs.date != rhs.date { + return false + } + if lhs.lifetime != rhs.lifetime { + return false + } return true } } @@ -3432,6 +3442,8 @@ public final class GroupCallMessagesContext { } private final class Impl { + private let defaultMessageLifetime: Int32 = 10 + let queue: Queue let account: Account let callId: Int64 @@ -3450,6 +3462,8 @@ public final class GroupCallMessagesContext { var updatesDisposable: Disposable? let sendMessageDisposables = DisposableSet() + private var messageLifeTimer: SwiftSignalKit.Timer? + init(queue: Queue, account: Account, callId: Int64, reference: InternalGroupCallReference, e2eContext: ConferenceCallE2EContext?) { self.queue = queue self.account = account @@ -3465,6 +3479,7 @@ public final class GroupCallMessagesContext { guard let self else { return } + let currentTime = Int32(CFAbsoluteTimeGetCurrent()) var addedMessages: [(authorId: PeerId, text: String, entities: [MessageTextEntity])] = [] var addedOpaqueMessages: [(authorId: PeerId, data: Data)] = [] for update in updates { @@ -3502,28 +3517,40 @@ public final class GroupCallMessagesContext { guard let decryptedMessage else { continue } - guard let text = String(data: decryptedMessage, encoding: .utf8) else { - continue + let buffer = Buffer(data: decryptedMessage) + let bufferReader = BufferReader(buffer) + if bufferReader.readInt32() == 1964978502 { + if let textWithEntities = Api.TextWithEntities.parse_textWithEntities(bufferReader) { + switch textWithEntities { + case let .textWithEntities(text, entities): + let messageId = self.nextId + self.nextId += 1 + messages.append(Message( + id: messageId, + author: transaction.getPeer(addedOpaqueMessage.authorId).flatMap(EnginePeer.init), + text: text, + entities: messageTextEntitiesFromApiEntities(entities), + date: currentTime, + lifetime: self.defaultMessageLifetime + )) + } + } } - - let messageId = self.nextId - self.nextId += 1 - messages.append(Message( - id: messageId, - author: transaction.getPeer(addedOpaqueMessage.authorId).flatMap(EnginePeer.init), - text: text, - entities: [] - )) } } else { for addedMessage in addedMessages { + if addedMessage.authorId == account.peerId { + continue + } let messageId = self.nextId self.nextId += 1 messages.append(Message( id: messageId, author: transaction.getPeer(addedMessage.authorId).flatMap(EnginePeer.init), text: addedMessage.text, - entities: addedMessage.entities + entities: addedMessage.entities, + date: currentTime, + lifetime: self.defaultMessageLifetime )) } } @@ -3539,11 +3566,28 @@ public final class GroupCallMessagesContext { }) } }) + + let timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in + self?.messageLifetimeTick() + }, queue: self.queue) + self.messageLifeTimer = timer + timer.start() } deinit { self.updatesDisposable?.dispose() self.sendMessageDisposables.dispose() + self.messageLifeTimer?.invalidate() + } + + private func messageLifetimeTick() { + let now = Int32(CFAbsoluteTimeGetCurrent()) + let filtered = self.state.messages.filter { now - $0.date < $0.lifetime } + if filtered.count != self.state.messages.count { + var state = self.state + state.messages = filtered + self.state = state + } } func send(text: String, entities: [MessageTextEntity]) { @@ -3555,6 +3599,7 @@ public final class GroupCallMessagesContext { guard let self else { return } + let currentTime = Int32(CFAbsoluteTimeGetCurrent()) let messageId = self.nextId self.nextId += 1 @@ -3564,12 +3609,16 @@ public final class GroupCallMessagesContext { id: messageId, author: accountPeer.flatMap(EnginePeer.init), text: text, - entities: entities + entities: entities, + date: currentTime, + lifetime: self.defaultMessageLifetime )) self.state = state if let e2eContext = self.e2eContext { - let messageData = text.data(using: .utf8)! + let buffer = Buffer() + Api.TextWithEntities.textWithEntities(text: text, entities: apiEntitiesFromMessageTextEntities(entities, associatedPeers: SimpleDictionary())).serialize(buffer, true) + let messageData = buffer.makeData() let encryptedMessage = e2eContext.state.with({ state -> Data? in guard let state = state.state else { return nil diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift index ce9d96f5a7..1fe48b4db9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift @@ -60,7 +60,7 @@ func _internal_addContactInteractively(account: Account, peerId: PeerId, firstNa if addToPrivacyExceptions { flags |= (1 << 0) } - return account.network.request(Api.functions.contacts.addContact(flags: flags, id: inputUser, firstName: firstName, lastName: lastName, phone: phone)) + return account.network.request(Api.functions.contacts.addContact(flags: flags, id: inputUser, firstName: firstName, lastName: lastName, phone: phone, note: nil)) |> mapError { _ -> AddContactError in return .generic } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift index bc9b34b249..72f9156b76 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift @@ -34,6 +34,10 @@ public extension TelegramEngine { return _internal_updateContactPhoto(account: self.account, peerId: peerId, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: mode, mapResourceToAvatarSizes: mapResourceToAvatarSizes) } + public func updateContactNote(peerId: PeerId, text: String, entities: [MessageTextEntity]) -> Signal { + return _internal_updateContactNote(account: self.account, peerId: peerId, text: text, entities: entities) + } + public func deviceContactsImportedByCount(contacts: [(String, [DeviceContactNormalizedPhoneNumber])]) -> Signal<[String: Int32], NoError> { return _internal_deviceContactsImportedByCount(postbox: self.account.postbox, contacts: contacts) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/UpdateContactName.swift b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/UpdateContactName.swift index 6daaab4baa..55625b1c03 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/UpdateContactName.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/UpdateContactName.swift @@ -12,7 +12,7 @@ public enum UpdateContactNameError { func _internal_updateContactName(account: Account, peerId: PeerId, firstName: String, lastName: String) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId) as? TelegramUser, let inputUser = apiInputUser(peer) { - return account.network.request(Api.functions.contacts.addContact(flags: 0, id: inputUser, firstName: firstName, lastName: lastName, phone: "")) + return account.network.request(Api.functions.contacts.addContact(flags: 0, id: inputUser, firstName: firstName, lastName: lastName, phone: "", note: nil)) |> mapError { _ -> UpdateContactNameError in return .generic } @@ -27,3 +27,32 @@ func _internal_updateContactName(account: Account, peerId: PeerId, firstName: St |> mapError { _ -> UpdateContactNameError in } |> switchToLatest } + +public enum UpdateContactNoteError { + case generic +} + +func _internal_updateContactNote(account: Account, peerId: PeerId, text: String, entities: [MessageTextEntity]) -> Signal { + return account.postbox.transaction { transaction -> Signal in + if let peer = transaction.getPeer(peerId) as? TelegramUser, let inputUser = apiInputUser(peer) { + return account.network.request(Api.functions.contacts.updateContactNote(id: inputUser, note: .textWithEntities(text: text, entities: apiEntitiesFromMessageTextEntities(entities, associatedPeers: SimpleDictionary())))) + |> mapError { _ -> UpdateContactNoteError in + return .generic + } + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction in + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { peerId, cachedData in + let cachedData = cachedData as? CachedUserData ?? CachedUserData() + return cachedData.withUpdatedNote(!text.isEmpty ? CachedUserData.Note(text: text, entities: entities) : nil) + }) + } + |> castError(UpdateContactNoteError.self) + } + } else { + return .fail(.generic) + } + } + |> mapError { _ -> UpdateContactNoteError in } + |> switchToLatest + |> ignoreValues +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift index bdec7f6acc..a60886ac5f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift @@ -529,11 +529,13 @@ private class AdMessagesHistoryContextImpl { var nameColorIndex: Int32? var backgroundEmojiId: Int64? - if let color = color { + if let color { switch color { case let .peerColor(_, color, backgroundEmojiIdValue): nameColorIndex = color backgroundEmojiId = backgroundEmojiIdValue + default: + break } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index fdf02ea5f8..2b5dfadea2 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -320,6 +320,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { case valueCurrency case flags case themePeerId + case peerColor } public struct Flags: OptionSet { @@ -958,7 +959,8 @@ extension StarGift { return nil } self = .generic(StarGift.Gift(id: id, title: title, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars, releasedBy: releasedBy?.peerId, perUserLimit: perUserLimit, lockedUntilDate: lockedUntilDate)) - case let .starGiftUnique(apiFlags, id, giftId, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellAmounts, releasedBy, valueAmount, valueCurrency, themePeer): + case let .starGiftUnique(apiFlags, id, giftId, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellAmounts, releasedBy, valueAmount, valueCurrency, themePeer, peerColor): + let _ = peerColor let owner: StarGift.UniqueGift.Owner if let ownerAddress { owner = .address(ownerAddress) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift index 6937e7b94a..3f2d19367b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift @@ -384,40 +384,20 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net case let .channelAdminLogEventActionToggleAntiSpam(newValue): action = .toggleAntiSpam(isEnabled: newValue == .boolTrue) case let .channelAdminLogEventActionChangePeerColor(prevValue, newValue): - var prevColorIndex: Int32 - var prevEmojiId: Int64? - switch prevValue { - case let .peerColor(_, color, backgroundEmojiIdValue): - prevColorIndex = color ?? 0 - prevEmojiId = backgroundEmojiIdValue + guard case let .peerColor(_, prevColor, prevBackgroundEmojiIdValue) = prevValue, case let .peerColor(_, newColor, newBackgroundEmojiIdValue) = newValue else { + continue } + let prevColorIndex = prevColor ?? 0 + let prevEmojiId = prevBackgroundEmojiIdValue - var newColorIndex: Int32 - var newEmojiId: Int64? - switch newValue { - case let .peerColor(_, color, backgroundEmojiIdValue): - newColorIndex = color ?? 0 - newEmojiId = backgroundEmojiIdValue - } + let newColorIndex = newColor ?? 0 + let newEmojiId = newBackgroundEmojiIdValue action = .changeNameColor(prevColor: PeerNameColor(rawValue: prevColorIndex), prevIcon: prevEmojiId, newColor: PeerNameColor(rawValue: newColorIndex), newIcon: newEmojiId) case let .channelAdminLogEventActionChangeProfilePeerColor(prevValue, newValue): - var prevColorIndex: Int32? - var prevEmojiId: Int64? - switch prevValue { - case let .peerColor(_, color, backgroundEmojiIdValue): - prevColorIndex = color - prevEmojiId = backgroundEmojiIdValue + guard case let .peerColor(_, prevColorIndex, prevEmojiId) = prevValue, case let .peerColor(_, newColorIndex, newEmojiId) = newValue else { + continue } - - var newColorIndex: Int32? - var newEmojiId: Int64? - switch newValue { - case let .peerColor(_, color, backgroundEmojiIdValue): - newColorIndex = color - newEmojiId = backgroundEmojiIdValue - } - action = .changeProfileColor(prevColor: prevColorIndex.flatMap(PeerNameColor.init(rawValue:)), prevIcon: prevEmojiId, newColor: newColorIndex.flatMap(PeerNameColor.init(rawValue:)), newIcon: newEmojiId) case let .channelAdminLogEventActionChangeWallpaper(prevValue, newValue): let prev: TelegramWallpaper? diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/SuggestBirthday.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/SuggestBirthday.swift new file mode 100644 index 0000000000..f530bf8b5c --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/SuggestBirthday.swift @@ -0,0 +1,23 @@ +import Foundation +import Postbox +import SwiftSignalKit +import TelegramApi +import MtProtoKit + +public enum SuggestBirthdayError { + case generic +} + +func _internal_suggestBirthday(account: Account, peerId: EnginePeer.Id, birthday: TelegramBirthday) -> Signal { + return account.postbox.loadedPeerWithId(peerId) + |> castError(SuggestBirthdayError.self) + |> mapToSignal { peer in + guard let inputUser = apiInputUser(peer) else { + return .complete() + } + return account.network.request(Api.functions.users.suggestBirthday(id: inputUser, birthday: birthday.apiBirthday)) + |> mapError { _ in + return .generic + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 048f67206b..064db9b593 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -923,6 +923,10 @@ public extension TelegramEngine { public func removeSavedMusic(file: FileMediaReference) -> Signal { return _internal_removeSavedMusic(account: self.account, file: file) } + + public func suggestBirthday(peerId: EnginePeer.Id, birthday: TelegramBirthday) -> Signal { + return _internal_suggestBirthday(account: self.account, peerId: peerId, birthday: birthday) + } public func getNextUnreadChannel(peerId: PeerId, chatListFilterId: Int32?, getFilterPredicate: @escaping (ChatListFilterData) -> ChatListFilterPredicate) -> Signal<(peer: EnginePeer, unreadCount: Int, location: NextUnreadChannelLocation)?, NoError> { let startTime = CFAbsoluteTimeGetCurrent() diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 4ecca44385..1953276db4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -265,7 +265,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee } switch fullUser { - case let .userFull(_, _, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .userFull(_, _, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: userFullNotifySettings)]) } @@ -277,7 +277,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee previous = CachedUserData() } switch fullUser { - case let .userFull(userFullFlags, userFullFlags2, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullChatTheme, _, groupAdminRights, channelAdminRights, userWallpaper, _, businessWorkHours, businessLocation, greetingMessage, awayMessage, businessIntro, birthday, personalChannelId, personalChannelMessage, starGiftsCount, starRefProgram, verification, sendPaidMessageStars, disallowedStarGifts, starsRating, starsMyPendingRating, starsMyPendingRatingDate, mainTab, savedMusic): + case let .userFull(userFullFlags, userFullFlags2, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullChatTheme, _, groupAdminRights, channelAdminRights, userWallpaper, _, businessWorkHours, businessLocation, greetingMessage, awayMessage, businessIntro, birthday, personalChannelId, personalChannelMessage, starGiftsCount, starRefProgram, verification, sendPaidMessageStars, disallowedStarGifts, starsRating, starsMyPendingRating, starsMyPendingRatingDate, mainTab, savedMusic, note): let botInfo = userFullBotInfo.flatMap(BotInfo.init(apiBotInfo:)) let isBlocked = (userFullFlags & (1 << 0)) != 0 let voiceCallsAvailable = (userFullFlags & (1 << 4)) != 0 @@ -436,6 +436,14 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee let mappedChatTheme: ChatTheme? = userFullChatTheme.flatMap { ChatTheme(apiChatTheme: $0) } + var mappedNote: CachedUserData.Note? + if let note { + switch note { + case let .textWithEntities(text, entities): + mappedNote = CachedUserData.Note(text: text, entities: messageTextEntitiesFromApiEntities(entities)) + } + } + return previous.withUpdatedAbout(userFullAbout) .withUpdatedBotInfo(botInfo) .withUpdatedEditableBotInfo(editableBotInfo) @@ -476,6 +484,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee .withUpdatedPendingStarRating(pendingRating) .withUpdatedMainProfileTab(mappedMainProfileTab) .withUpdatedSavedMusic(mappedSavedMusic) + .withUpdatedNote(mappedNote) } }) } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index d44cba23de..f21f83d735 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1011,6 +1011,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } else { attributedString = NSAttributedString(string: strings.Notification_SuggestedProfileVideo, font: titleFont, textColor: primaryTextColor) } + case let .suggestedBirthday(birthday): + let _ = birthday + attributedString = NSAttributedString(string: strings.Notification_SuggestBirthdate, font: titleFont, textColor: primaryTextColor) case .attachMenuBotAllowed: attributedString = NSAttributedString(string: strings.Notification_BotWriteAllowed, font: titleFont, textColor: primaryTextColor) case let .requestedPeer(_, peerIds): diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/BUILD new file mode 100644 index 0000000000..ab37ef98ba --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/BUILD @@ -0,0 +1,34 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageBirthdateSuggestionContentNode", + module_name = "ChatMessageBirthdateSuggestionContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/AccountContext", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/TextFormat", + "//submodules/LocalizedPeerData", + "//submodules/TelegramStringFormatting", + "//submodules/WallpaperBackgroundNode", + "//submodules/ReactionSelectionNode", + "//submodules/Markdown", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/Sources/ChatMessageBirthdateSuggestionContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/Sources/ChatMessageBirthdateSuggestionContentNode.swift new file mode 100644 index 0000000000..f99ae32c5b --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/Sources/ChatMessageBirthdateSuggestionContentNode.swift @@ -0,0 +1,390 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import AccountContext +import TelegramPresentationData +import TelegramUIPreferences +import TextFormat +import TelegramStringFormatting +import WallpaperBackgroundNode +import Markdown +import ChatMessageBubbleContentNode +import ChatMessageItemCommon +import ChatControllerInteraction +import AnimatedStickerNode +import TelegramAnimatedStickerNode + +public class ChatMessageBirthdateSuggestionContentNode: ChatMessageBubbleContentNode { + private var mediaBackgroundContent: WallpaperBubbleBackgroundNode? + private let mediaBackgroundNode: NavigationBackgroundNode + + private let animationNode: AnimatedStickerNode + + private let subtitleNode: TextNode + + private let dayTitleNode: TextNode + private let dayValueNode: TextNode + + private let monthTitleNode: TextNode + private let monthValueNode: TextNode + + private let yearTitleNode: TextNode + private let yearValueNode: TextNode + + private let buttonNode: HighlightTrackingButtonNode + private let buttonTitleNode: TextNode + + private var absoluteRect: (CGRect, CGSize)? + + required public init() { + self.mediaBackgroundNode = NavigationBackgroundNode(color: .clear) + self.mediaBackgroundNode.clipsToBounds = true + self.mediaBackgroundNode.cornerRadius = 27.0 + + self.animationNode = DefaultAnimatedStickerNodeImpl() + + self.subtitleNode = TextNode() + self.subtitleNode.isUserInteractionEnabled = false + self.subtitleNode.displaysAsynchronously = false + + self.dayTitleNode = TextNode() + self.dayTitleNode.isUserInteractionEnabled = false + self.dayTitleNode.displaysAsynchronously = false + + self.dayValueNode = TextNode() + self.dayValueNode.isUserInteractionEnabled = false + self.dayValueNode.displaysAsynchronously = false + + self.monthTitleNode = TextNode() + self.monthTitleNode.isUserInteractionEnabled = false + self.monthTitleNode.displaysAsynchronously = false + + self.monthValueNode = TextNode() + self.monthValueNode.isUserInteractionEnabled = false + self.monthValueNode.displaysAsynchronously = false + + self.yearTitleNode = TextNode() + self.yearTitleNode.isUserInteractionEnabled = false + self.yearTitleNode.displaysAsynchronously = false + + self.yearValueNode = TextNode() + self.yearValueNode.isUserInteractionEnabled = false + self.yearValueNode.displaysAsynchronously = false + + self.buttonNode = HighlightTrackingButtonNode() + self.buttonNode.clipsToBounds = true + self.buttonNode.cornerRadius = 17.0 + + self.buttonTitleNode = TextNode() + self.buttonTitleNode.isUserInteractionEnabled = false + self.buttonTitleNode.displaysAsynchronously = false + + super.init() + + self.addSubnode(self.mediaBackgroundNode) + + self.addSubnode(self.animationNode) + + self.addSubnode(self.subtitleNode) + + self.addSubnode(self.dayTitleNode) + self.addSubnode(self.dayValueNode) + + self.addSubnode(self.monthTitleNode) + self.addSubnode(self.monthValueNode) + + self.addSubnode(self.yearTitleNode) + self.addSubnode(self.yearValueNode) + + self.addSubnode(self.buttonNode) + self.addSubnode(self.buttonTitleNode) + + self.buttonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.buttonNode.layer.removeAnimation(forKey: "opacity") + strongSelf.buttonNode.alpha = 0.4 + strongSelf.buttonTitleNode.layer.removeAnimation(forKey: "opacity") + strongSelf.buttonTitleNode.alpha = 0.4 + } else { + strongSelf.buttonNode.alpha = 1.0 + strongSelf.buttonNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.buttonTitleNode.alpha = 1.0 + strongSelf.buttonTitleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func buttonPressed() { + guard let item = self.item else { + return + } + let _ = item.controllerInteraction.openMessage(item.message, OpenMessageParams(mode: .default)) + } + + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode) + + let makeDayTitleLayout = TextNode.asyncLayout(self.dayTitleNode) + let makeDayValueLayout = TextNode.asyncLayout(self.dayValueNode) + let makeMonthTitleLayout = TextNode.asyncLayout(self.monthTitleNode) + let makeMonthValueLayout = TextNode.asyncLayout(self.monthValueNode) + let makeYearTitleLayout = TextNode.asyncLayout(self.yearTitleNode) + let makeYearValueLayout = TextNode.asyncLayout(self.yearValueNode) + + let makeButtonTitleLayout = TextNode.asyncLayout(self.buttonTitleNode) + + return { item, layoutConstants, _, _, _, _ in + let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center) + + return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in + let width: CGFloat = 186.0 + + var day: Int32 = 1 + var month: Int32 = 1 + var year: Int32? + + if let action = item.message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .suggestedBirthday(birthday) = action.action { + day = birthday.day + month = birthday.month + year = birthday.year + } + + let primaryTextColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper).primaryText + let subtitleColor = primaryTextColor.withAlphaComponent(item.presentationData.theme.theme.overallDarkAppearance ? 0.7 : 0.8) + + let peerName = item.message.peers[item.message.id.peerId].flatMap { EnginePeer($0).compactDisplayTitle } ?? "" + let text: String + + let fromYou = item.message.author?.id == item.context.account.peerId + if fromYou { + text = item.presentationData.strings.Conversation_SuggestedBirthdateTextYou(peerName).string + } else { + text = item.presentationData.strings.Conversation_SuggestedBirthdateText(peerName).string + } + + let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor) + let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: primaryTextColor) + let subtitle = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in + return nil + }), textAlignment: .center) + + let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitle, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let titleFont = Font.regular(13.0) + let valueFont = Font.semibold(13.0) + + let (dayTitleLayout, dayTitleApply) = makeDayTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Conversation_SuggestedBirthdate_Day, font: titleFont, textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let (dayValueLayout, dayValueApply) = makeDayValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "\(day)", font: valueFont, textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let (monthTitleLayout, monthTitleApply) = makeMonthTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Conversation_SuggestedBirthdate_Month, font: titleFont, textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let (monthValueLayout, monthValueApply) = makeMonthValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: stringForMonth(strings: item.presentationData.strings, month: month - 1), font: valueFont, textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let (yearTitleLayout, yearTitleApply) = makeYearTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Conversation_SuggestedBirthdate_Year, font: titleFont, textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let (yearValueLayout, yearValueApply) = makeYearValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: year.flatMap { "\($0)" } ?? "", font: valueFont, textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Conversation_SuggestedBirthdate_View, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + var backgroundSize = CGSize(width: width, height: subtitleLayout.size.height + 160.0) + if !fromYou { + backgroundSize.height += 44.0 + } + + return (backgroundSize.width, { boundingWidth in + return (backgroundSize, { [weak self] animation, synchronousLoads, _ in + if let strongSelf = self { + let isFirstTime = strongSelf.item == nil + strongSelf.item = item + + let mediaBackgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - width) / 2.0), y: 0.0), size: backgroundSize) + strongSelf.mediaBackgroundNode.frame = mediaBackgroundFrame + + strongSelf.mediaBackgroundNode.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate) + strongSelf.mediaBackgroundNode.update(size: mediaBackgroundFrame.size, transition: .immediate) + strongSelf.buttonNode.backgroundColor = item.presentationData.theme.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12) + + if item.presentationData.theme.theme.overallDarkAppearance { + strongSelf.dayTitleNode.layer.compositingFilter = nil + strongSelf.monthTitleNode.layer.compositingFilter = nil + strongSelf.yearTitleNode.layer.compositingFilter = nil + } else { + strongSelf.dayTitleNode.layer.compositingFilter = "overlayBlendMode" + strongSelf.monthTitleNode.layer.compositingFilter = "overlayBlendMode" + strongSelf.yearTitleNode.layer.compositingFilter = "overlayBlendMode" + } + + let _ = subtitleApply() + + let _ = dayTitleApply() + let _ = monthTitleApply() + let _ = yearTitleApply() + + let _ = dayValueApply() + let _ = monthValueApply() + let _ = yearValueApply() + + let _ = buttonTitleApply() + + let iconSize = CGSize(width: 80.0, height: 80.0) + + let animationFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY + 8.0), size: iconSize) + strongSelf.animationNode.frame = animationFrame + if isFirstTime { + strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "Cake"), width: 384, height: 384, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + strongSelf.animationNode.visibility = true + } + strongSelf.animationNode.updateLayout(size: iconSize) + + let subtitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 96.0), size: subtitleLayout.size) + strongSelf.subtitleNode.frame = subtitleFrame + + let titleOriginY = subtitleFrame.maxY + 11.0 + let valueOriginY = titleOriginY + 19.0 + + let minX = mediaBackgroundFrame.minX + let maxX = mediaBackgroundFrame.maxX + let width = mediaBackgroundFrame.width + + let dayColWidth = max(dayTitleLayout.size.width, dayValueLayout.size.width) + let monthColWidth = max(monthTitleLayout.size.width, monthValueLayout.size.width) + let yearColW = max(yearTitleLayout.size.width, yearValueLayout.size.width) + + func centerX(inLeft left: CGFloat, right: CGFloat, contentWidth: CGFloat) -> CGFloat { + return left + floorToScreenPixels((right - left - contentWidth) * 0.5) + } + + if yearValueLayout.size.width > 0.0 { + strongSelf.yearTitleNode.isHidden = false + strongSelf.yearValueNode.isHidden = false + + let monthLeft = centerX(inLeft: minX, right: maxX, contentWidth: monthColWidth) + let monthRight = monthLeft + monthColWidth + + let dayLeft = centerX(inLeft: minX, right: monthLeft, contentWidth: dayColWidth) + let yearLeft = centerX(inLeft: monthRight, right: maxX, contentWidth: yearColW) + + strongSelf.dayTitleNode.frame = CGRect( + origin: CGPoint(x: dayLeft + floorToScreenPixels((dayColWidth - dayTitleLayout.size.width) * 0.5), y: titleOriginY), + size: dayTitleLayout.size + ) + strongSelf.dayValueNode.frame = CGRect( + origin: CGPoint(x: dayLeft + floorToScreenPixels((dayColWidth - dayValueLayout.size.width) * 0.5), y: valueOriginY), + size: dayValueLayout.size + ) + + strongSelf.monthTitleNode.frame = CGRect( + origin: CGPoint(x: monthLeft + floorToScreenPixels((monthColWidth - monthTitleLayout.size.width) * 0.5), y: titleOriginY), + size: monthTitleLayout.size + ) + strongSelf.monthValueNode.frame = CGRect( + origin: CGPoint(x: monthLeft + floorToScreenPixels((monthColWidth - monthValueLayout.size.width) * 0.5), y: valueOriginY), + size: monthValueLayout.size + ) + + strongSelf.yearTitleNode.frame = CGRect( + origin: CGPoint(x: yearLeft + floorToScreenPixels((yearColW - yearTitleLayout.size.width) * 0.5), y: titleOriginY), + size: yearTitleLayout.size + ) + strongSelf.yearValueNode.frame = CGRect( + origin: CGPoint(x: yearLeft + floorToScreenPixels((yearColW - yearValueLayout.size.width) * 0.5), y: valueOriginY), + size: yearValueLayout.size + ) + } else { + strongSelf.yearTitleNode.isHidden = true + strongSelf.yearValueNode.isHidden = true + + let spacing: CGFloat = 16.0 + let totalWidth = dayColWidth + monthColWidth + spacing + let dayLeft = minX + floorToScreenPixels((width - totalWidth) / 2.0) + let monthLeft = dayLeft + dayColWidth + spacing + + strongSelf.dayTitleNode.frame = CGRect( + origin: CGPoint(x: dayLeft + floorToScreenPixels((dayColWidth - dayTitleLayout.size.width) * 0.5), y: titleOriginY), + size: dayTitleLayout.size + ) + strongSelf.dayValueNode.frame = CGRect( + origin: CGPoint(x: dayLeft + floorToScreenPixels((dayColWidth - dayValueLayout.size.width) * 0.5), y: valueOriginY), + size: dayValueLayout.size + ) + + strongSelf.monthTitleNode.frame = CGRect( + origin: CGPoint(x: monthLeft + floorToScreenPixels((monthColWidth - monthTitleLayout.size.width) * 0.5), y: titleOriginY), + size: monthTitleLayout.size + ) + strongSelf.monthValueNode.frame = CGRect( + origin: CGPoint(x: monthLeft + floorToScreenPixels((monthColWidth - monthValueLayout.size.width) * 0.5), y: valueOriginY), + size: monthValueLayout.size + ) + } + + strongSelf.buttonNode.isHidden = fromYou + strongSelf.buttonTitleNode.isHidden = fromYou + + let buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0) + let buttonFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonSize.width) / 2.0), y: mediaBackgroundFrame.maxY - buttonSize.height - 16.0), size: buttonSize) + strongSelf.buttonNode.frame = buttonFrame + + let buttonTitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonTitleLayout.size.width) / 2.0), y: floorToScreenPixels(buttonFrame.midY - buttonTitleLayout.size.height / 2.0)), size: buttonTitleLayout.size) + strongSelf.buttonTitleNode.frame = buttonTitleFrame + + if item.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true { + if strongSelf.mediaBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + strongSelf.mediaBackgroundNode.isHidden = true + backgroundContent.clipsToBounds = true + backgroundContent.allowsGroupOpacity = true + backgroundContent.cornerRadius = 27.0 + + strongSelf.mediaBackgroundContent = backgroundContent + strongSelf.insertSubnode(backgroundContent, at: 0) + } + + strongSelf.mediaBackgroundContent?.frame = mediaBackgroundFrame + } else { + strongSelf.mediaBackgroundNode.isHidden = false + strongSelf.mediaBackgroundContent?.removeFromSupernode() + strongSelf.mediaBackgroundContent = nil + } + + if let (rect, size) = strongSelf.absoluteRect { + strongSelf.updateAbsoluteRect(rect, within: size) + } + } + }) + }) + }) + } + } + + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + self.absoluteRect = (rect, containerSize) + + if let mediaBackgroundContent = self.mediaBackgroundContent { + var backgroundFrame = mediaBackgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + mediaBackgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } + + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + if self.mediaBackgroundNode.frame.contains(point) { + return ChatMessageBubbleContentTapAction(content: .openMessage) + } else { + return ChatMessageBubbleContentTapAction(content: .none) + } + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD index 8f1dd1764c..7e0d886f68 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD @@ -74,6 +74,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode", "//submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode", "//submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode", "//submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode", "//submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode", "//submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 79bc0e9406..fd9bbee9b0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -64,6 +64,7 @@ import ChatMessageInvoiceBubbleContentNode import ChatMessageMapBubbleContentNode import ChatMessageMediaBubbleContentNode import ChatMessageProfilePhotoSuggestionContentNode +import ChatMessageBirthdateSuggestionContentNode import ChatMessageRestrictedBubbleContentNode import ChatMessageStoryMentionContentNode import ChatMessageUnsupportedBubbleContentNode @@ -243,6 +244,8 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ needReactions = false } else if case let .setChatTheme(chatTheme) = action.action, case .gift = chatTheme { result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) + } else if case let .suggestedBirthday = action.action { + result.append((message, ChatMessageBirthdateSuggestionContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) } else { if !canAddMessageReactions(message: message) { needReactions = false @@ -295,7 +298,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ if let updatingMedia = itemAttributes.updatingMedia { messageText = updatingMedia.text } - + if !messageText.isEmpty || isUnsupportedMedia || isStoryWithText { if !skipText { if case .group = item.content, !isFile { diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift index e0437a48fe..3c7f5ec804 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift @@ -124,7 +124,7 @@ private final class ContentContainer: UIView { } } -public final class GlassBackgroundView: UIView { +public class GlassBackgroundView: UIView { public protocol ContentView: UIView { var tintMask: UIView { get } } @@ -414,7 +414,7 @@ public final class GlassBackgroundView: UIView { if let nativeView { if #available(iOS 26.0, *) { let glassEffect = UIGlassEffect(style: .regular) - //glassEffect.tintColor = tintColor.withMultipliedAlpha(0.1) + glassEffect.tintColor = tintColor//.withMultipliedAlpha(0.1) glassEffect.isInteractive = false nativeView.effect = glassEffect @@ -519,3 +519,40 @@ public final class VariableBlurView: UIVisualEffectView { backdropLayer?.filters = [variableBlur] } } + +public final class GlassBackgroundComponent: Component { + private let size: CGSize + private let tintColor: UIColor + + public init(size: CGSize, tintColor: UIColor) { + self.size = size + self.tintColor = tintColor + } + + public static func == (lhs: GlassBackgroundComponent, rhs: GlassBackgroundComponent) -> Bool { + if lhs.size != rhs.size { + return false + } + if lhs.tintColor != rhs.tintColor { + return false + } + return true + } + + public final class View: GlassBackgroundView { + func update(component: GlassBackgroundComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.update(size: component.size, cornerRadius: component.size.height / 2.0, isDark: true, tintColor: component.tintColor, transition: transition) + self.frame = CGRect(origin: .zero, size: component.size) + + return component.size + } + } + + public func makeView() -> View { + return View() + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift index a0a6034849..fa6a9230db 100644 --- a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift @@ -229,7 +229,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { areVoiceMessagesAvailable: false, presentController: self.present, presentInGlobalOverlay: self.presentInGlobalOverlay, - sendMessageAction: { [weak self] in + sendMessageAction: { [weak self] _ in if let self { self.sendPressed?(self.caption()) let _ = self.dismissInput() diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 49f2834ccf..5958ab79b4 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -1396,7 +1396,7 @@ final class MediaEditorScreenComponent: Component { } controller.presentInGlobalOverlay(c) }, - sendMessageAction: { [weak self] in + sendMessageAction: { [weak self] _ in guard let self else { return } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift index 900b4f8ba0..0e583c58f3 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift @@ -260,7 +260,7 @@ final class StoryPreviewComponent: Component { areVoiceMessagesAvailable: false, presentController: { _ in }, presentInGlobalOverlay: { _ in }, - sendMessageAction: { }, + sendMessageAction: { _ in }, sendMessageOptionsAction: nil, sendStickerAction: { _ in }, setMediaRecordingActive: { _, _, _, _ in }, diff --git a/submodules/TelegramUI/Components/MessageInputActionButtonComponent/BUILD b/submodules/TelegramUI/Components/MessageInputActionButtonComponent/BUILD index 2cacc7517f..c628d3dcd2 100644 --- a/submodules/TelegramUI/Components/MessageInputActionButtonComponent/BUILD +++ b/submodules/TelegramUI/Components/MessageInputActionButtonComponent/BUILD @@ -21,6 +21,7 @@ swift_library( "//submodules/ContextUI", "//submodules/Components/ReactionButtonListComponent", "//submodules/TelegramCore", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift b/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift index 1bb989cb1a..09e5c26f09 100644 --- a/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift @@ -12,6 +12,7 @@ import MoreHeaderButton import ContextUI import ReactionButtonListComponent import LottieComponent +import GlassBackgroundComponent private class ButtonIcon: Equatable { enum IconType: Equatable { @@ -24,6 +25,7 @@ private class ButtonIcon: Equatable { case forward case like case repost + case close } let icon: IconType @@ -32,7 +34,7 @@ private class ButtonIcon: Equatable { self.icon = icon } - var image: UIImage? { + func image(withBackground: Bool) -> UIImage? { switch icon { case .delete: return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: .white) @@ -46,6 +48,8 @@ private class ButtonIcon: Equatable { return generateTintedImage(image: UIImage(bundleImageName: "Media Editor/RemoveRecordedVideo"), color: .white) case .repost: return generateTintedImage(image: UIImage(bundleImageName: "Stories/InputRepost"), color: .white) + case .close: + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/DownButton"), color: .white) case .apply: return generateImage(CGSize(width: 33.0, height: 33.0), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) @@ -62,10 +66,19 @@ private class ButtonIcon: Equatable { case .send: return generateImage(CGSize(width: 33.0, height: 33.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(UIColor.white.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + + if withBackground { + context.setFillColor(UIColor.white.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + } + context.setBlendMode(.copy) - context.setStrokeColor(UIColor.clear.cgColor) + + if withBackground { + context.setStrokeColor(UIColor.clear.cgColor) + } else { + context.setStrokeColor(UIColor.white.cgColor) + } context.setLineWidth(2.0) context.setLineCap(.round) context.setLineJoin(.round) @@ -111,6 +124,8 @@ private extension MessageInputActionButtonComponent.Mode { return ButtonIcon(icon: .apply) case .send: return ButtonIcon(icon: .send) + case .close: + return ButtonIcon(icon: .close) case .stars: return nil default: @@ -137,14 +152,21 @@ public final class MessageInputActionButtonComponent: Component { case repost case captionUp case captionDown + case close } public enum Action { case down case up } + + public enum Style { + case legacy + case glass + } public let mode: Mode + public let style: Style public let storyId: Int32? public let action: (Mode, Action, Bool) -> Void public let longPressAction: ((UIView, ContextGesture?) -> Void)? @@ -163,6 +185,7 @@ public final class MessageInputActionButtonComponent: Component { public init( mode: Mode, + style: Style = .legacy, storyId: Int32?, action: @escaping (Mode, Action, Bool) -> Void, longPressAction: ((UIView, ContextGesture?) -> Void)?, @@ -180,6 +203,7 @@ public final class MessageInputActionButtonComponent: Component { hasShadow: Bool = false ) { self.mode = mode + self.style = style self.storyId = storyId self.action = action self.longPressAction = longPressAction @@ -201,6 +225,9 @@ public final class MessageInputActionButtonComponent: Component { if lhs.mode != rhs.mode { return false } + if lhs.style != rhs.style { + return false + } if lhs.storyId != rhs.storyId { return false } @@ -231,6 +258,7 @@ public final class MessageInputActionButtonComponent: Component { public let button: HighlightTrackingButtonNode public let referenceNode: ContextReferenceContentNode public let containerNode: ContextControllerSourceNode + private var backgroundView: GlassBackgroundView? private let sendIconView: UIImageView private var reactionHeartView: UIImageView? private var moreButton: MoreHeaderButton? @@ -475,7 +503,7 @@ public final class MessageInputActionButtonComponent: Component { break case .captionUp, .captionDown: sendAlpha = 0.0 - case .send, .apply, .attach, .delete, .forward, .removeVideoInput, .repost, .stars: + case .send, .apply, .attach, .delete, .forward, .removeVideoInput, .repost, .stars, .close: sendAlpha = 1.0 case let .like(reaction, _, _): if reaction != nil { @@ -491,9 +519,29 @@ public final class MessageInputActionButtonComponent: Component { microphoneAlpha = 0.4 } + if component.style == .glass, [.send, .close].contains(component.mode) { + let backgroundView: GlassBackgroundView + if let current = self.backgroundView { + backgroundView = current + } else { + backgroundView = GlassBackgroundView() + self.button.view.insertSubview(backgroundView, at: 0) + self.backgroundView = backgroundView + } + + var tintColor = UIColor(rgb: 0x1b1d22) + if case .send = component.mode { + tintColor = UIColor(rgb: 0x0187ee) + } + let buttonSize = CGSize(width: 40.0, height: 40.0) + backgroundView.update(size: buttonSize, cornerRadius: buttonSize.height / 2.0, isDark: true, tintColor: tintColor, transition: transition) + backgroundView.frame = CGRect(origin: .zero, size: buttonSize) + } + if self.sendIconView.image == nil || previousComponent?.mode.icon != component.mode.icon { - if let image = component.mode.icon?.image { - if case .send = component.mode { + if let image = component.mode.icon?.image(withBackground: component.style == .legacy) { + switch component.mode { + case .send, .close: if !transition.animation.isImmediate { if let snapshotView = self.sendIconView.snapshotView(afterScreenUpdates: false) { snapshotView.frame = self.sendIconView.frame @@ -509,6 +557,8 @@ public final class MessageInputActionButtonComponent: Component { transition.animateScale(view: self.sendIconView, from: 0.01, to: 1.0) } } + default: + break } self.sendIconView.image = image } else { @@ -674,7 +724,7 @@ public final class MessageInputActionButtonComponent: Component { if previousComponent?.mode != component.mode { switch component.mode { - case .none, .send, .apply, .voiceInput, .attach, .delete, .forward, .unavailableVoiceInput, .more, .like, .repost, .captionUp, .captionDown, .stars: + case .none, .send, .apply, .voiceInput, .attach, .delete, .forward, .unavailableVoiceInput, .more, .like, .repost, .captionUp, .captionDown, .stars, .close: micButton.updateMode(mode: .audio, animated: !transition.animation.isImmediate) case .videoInput, .removeVideoInput: micButton.updateMode(mode: .video, animated: !transition.animation.isImmediate) diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD index 1963f35d45..c6366615c5 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD @@ -41,6 +41,7 @@ swift_library( "//submodules/TelegramUI/Components/ContextReferenceButtonComponent", "//submodules/TelegramUI/Components/Stories/ForwardInfoPanelComponent", "//submodules/TelegramUI/Components/PlainButtonComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index 52492030e1..dcb80f0dea 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -22,6 +22,7 @@ import ContextReferenceButtonComponent import ForwardInfoPanelComponent import MultilineTextComponent import PlainButtonComponent +import GlassBackgroundComponent private var sharedIsReduceTransparencyEnabled = UIAccessibility.isReduceTransparencyEnabled @@ -48,6 +49,7 @@ public final class MessageInputPanelComponent: Component { case story case editor case media + case glass } public enum InputMode: Hashable { @@ -141,7 +143,7 @@ public final class MessageInputPanelComponent: Component { } public final class ExternalState { - public fileprivate(set) var isEditing: Bool = false + public var isEditing: Bool = false public fileprivate(set) var hasText: Bool = false public fileprivate(set) var isKeyboardHidden: Bool = false @@ -154,6 +156,18 @@ public final class MessageInputPanelComponent: Component { } } + public final class SendActionTransition { + public let textSnapshotView: UIView + public let globalFrame: CGRect + public let cornerRadius: CGFloat + + init(textSnapshotView: UIView, globalFrame: CGRect, cornerRadius: CGFloat) { + self.textSnapshotView = textSnapshotView + self.globalFrame = globalFrame + self.cornerRadius = cornerRadius + } + } + public let externalState: ExternalState public let context: AccountContext public let theme: PresentationTheme @@ -170,7 +184,7 @@ public final class MessageInputPanelComponent: Component { public let areVoiceMessagesAvailable: Bool public let presentController: (ViewController) -> Void public let presentInGlobalOverlay: (ViewController) -> Void - public let sendMessageAction: () -> Void + public let sendMessageAction: (SendActionTransition?) -> Void public let sendMessageOptionsAction: ((UIView, ContextGesture?) -> Void)? public let sendStickerAction: (TelegramMediaFile) -> Void public let setMediaRecordingActive: ((Bool, Bool, Bool, UIView?) -> Void)? @@ -229,7 +243,7 @@ public final class MessageInputPanelComponent: Component { areVoiceMessagesAvailable: Bool, presentController: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void, - sendMessageAction: @escaping () -> Void, + sendMessageAction: @escaping (SendActionTransition?) -> Void, sendMessageOptionsAction: ((UIView, ContextGesture?) -> Void)?, sendStickerAction: @escaping (TelegramMediaFile) -> Void, setMediaRecordingActive: ((Bool, Bool, Bool, UIView?) -> Void)?, @@ -464,6 +478,7 @@ public final class MessageInputPanelComponent: Component { public final class View: UIView { private let fieldBackgroundView: BlurredBackgroundView private let fieldBackgroundTint: UIView + private var fieldGlassBackgroundView: GlassBackgroundView? private let gradientView: UIImageView private let bottomGradientView: UIView @@ -773,12 +788,18 @@ public final class MessageInputPanelComponent: Component { insets.right = 41.0 } - var textFieldSideInset = 9.0 - if case .media = component.style { + let textFieldSideInset: CGFloat + switch component.style { + case .media, .glass: textFieldSideInset = 8.0 + default: + textFieldSideInset = 9.0 } - let mediaInsets = UIEdgeInsets(top: insets.top, left: textFieldSideInset, bottom: insets.bottom, right: 41.0) + var mediaInsets = UIEdgeInsets(top: insets.top, left: textFieldSideInset, bottom: insets.bottom, right: 41.0) + if case .glass = component.style { + mediaInsets.right = 54.0 + } let baseFieldHeight: CGFloat = 40.0 @@ -1025,6 +1046,8 @@ public final class MessageInputPanelComponent: Component { var fieldBackgroundFrame: CGRect if hasMediaRecording { fieldBackgroundFrame = CGRect(origin: CGPoint(x: mediaInsets.left, y: insets.top), size: CGSize(width: availableSize.width - mediaInsets.left - mediaInsets.right, height: fieldFrame.height)) + } else if case .glass = component.style { + fieldBackgroundFrame = CGRect(origin: CGPoint(x: mediaInsets.left, y: insets.top), size: CGSize(width: availableSize.width - mediaInsets.left - mediaInsets.right, height: fieldFrame.height)) } else if isEditing || component.style == .editor || component.style == .media { fieldBackgroundFrame = fieldFrame } else { @@ -1042,6 +1065,25 @@ public final class MessageInputPanelComponent: Component { //transition.setFrame(view: self.vibrancyEffectView, frame: CGRect(origin: CGPoint(), size: fieldBackgroundFrame.size)) + switch component.style { + case .glass: + if self.fieldGlassBackgroundView == nil { + let fieldGlassBackgroundView = GlassBackgroundView(frame: fieldBackgroundFrame) + self.insertSubview(fieldGlassBackgroundView, aboveSubview: self.fieldBackgroundView) + self.fieldGlassBackgroundView = fieldGlassBackgroundView + + self.fieldBackgroundView.isHidden = true + self.fieldBackgroundTint.isHidden = true + } + if let fieldGlassBackgroundView = self.fieldGlassBackgroundView { + let backgroundColor = UIColor(rgb: 0x1b1d22) + fieldGlassBackgroundView.update(size: fieldBackgroundFrame.size, cornerRadius: baseFieldHeight * 0.5, isDark: true, tintColor: backgroundColor, transition: transition) + transition.setFrame(view: fieldGlassBackgroundView, frame: fieldBackgroundFrame) + } + default: + break + } + transition.setFrame(view: self.fieldBackgroundView, frame: fieldBackgroundFrame) self.fieldBackgroundView.update(size: fieldBackgroundFrame.size, cornerRadius: headerHeight > 0.0 ? 18.0 : baseFieldHeight * 0.5, transition: transition.containedViewLayoutTransition) transition.setFrame(view: self.fieldBackgroundTint, frame: fieldBackgroundFrame) @@ -1063,7 +1105,7 @@ public final class MessageInputPanelComponent: Component { transition.setAlpha(view: self.bottomGradientView, alpha: component.displayGradient ? 1.0 : 0.0) let placeholderOriginX: CGFloat - if isEditing || component.style == .story { + if isEditing || component.style == .story || component.style == .glass { placeholderOriginX = 16.0 } else { placeholderOriginX = floorToScreenPixels(fieldBackgroundFrame.minX + (fieldBackgroundFrame.width - placeholderSize.width) / 2.0) @@ -1456,6 +1498,7 @@ public final class MessageInputPanelComponent: Component { } } + var inputActionButtonAvailableSize = CGSize(width: 33.0, height: 33.0) var inputActionButtonAlpha = 1.0 let inputActionButtonMode: MessageInputActionButtonComponent.Mode if case .editor = component.style { @@ -1469,12 +1512,19 @@ public final class MessageInputPanelComponent: Component { if !isEditing { inputActionButtonAlpha = 0.0 } + } else if case .glass = component.style { + inputActionButtonAvailableSize = CGSize(width: 40.0, height: 40.0) + if self.textFieldExternalState.hasText { + inputActionButtonMode = .send + } else { + inputActionButtonMode = .close + } } else { if hasMediaEditing { inputActionButtonMode = .send } else { if self.textFieldExternalState.hasText { - if let sendPaidMessageStars = component.sendPaidMessageStars, "".isEmpty { + if let sendPaidMessageStars = component.sendPaidMessageStars, !"".isEmpty { inputActionButtonMode = .stars(sendPaidMessageStars.value) } else { inputActionButtonMode = .send @@ -1494,6 +1544,7 @@ public final class MessageInputPanelComponent: Component { transition: transition, component: AnyComponent(MessageInputActionButtonComponent( mode: inputActionButtonMode, + style: component.style == .glass ? .glass : .legacy, storyId: component.storyItem?.id, action: { [weak self] mode, action, sendAction in guard let self, let component = self.component else { @@ -1503,19 +1554,29 @@ public final class MessageInputPanelComponent: Component { switch mode { case .none: break + case .close: + component.sendMessageAction(nil) case .send, .stars: if case .up = action { if component.recordedAudioPreview != nil { - component.sendMessageAction() + component.sendMessageAction(nil) } else if component.hasRecordedVideoPreview { - component.sendMessageAction() + component.sendMessageAction(nil) } else if case let .text(string) = self.getSendMessageInput(), string.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { } else { if let maxLength = component.maxLength, self.textFieldExternalState.textLength > maxLength { self.animateError() component.presentTextLengthLimitTooltip?() } else { - component.sendMessageAction() + var sendActionTransition: MessageInputPanelComponent.SendActionTransition? + if let snapshotView = self.textClippingView.snapshotView(afterScreenUpdates: false), let backgroundView = self.fieldGlassBackgroundView { + sendActionTransition = MessageInputPanelComponent.SendActionTransition( + textSnapshotView: snapshotView, + globalFrame: backgroundView.convert(backgroundView.bounds, to: nil), + cornerRadius: baseFieldHeight * 0.5 + ) + } + component.sendMessageAction(sendActionTransition) } } } @@ -1525,7 +1586,7 @@ public final class MessageInputPanelComponent: Component { self.animateError() component.presentTextLengthLimitTooltip?() } else { - component.sendMessageAction() + component.sendMessageAction(nil) } } case .voiceInput, .videoInput: @@ -1587,7 +1648,7 @@ public final class MessageInputPanelComponent: Component { hasShadow: component.style == .editor )), environment: {}, - containerSize: CGSize(width: 33.0, height: 33.0) + containerSize: inputActionButtonAvailableSize ) let hasLikeAction: Bool @@ -1617,8 +1678,13 @@ public final class MessageInputPanelComponent: Component { inputActionButtonOriginX -= 46.0 } } else { - if component.setMediaRecordingActive != nil || isEditing { - inputActionButtonOriginX = fieldBackgroundFrame.maxX + floorToScreenPixels((41.0 - inputActionButtonSize.width) * 0.5) + if component.setMediaRecordingActive != nil || isEditing || component.style == .glass { + switch component.style { + case .glass: + inputActionButtonOriginX = fieldBackgroundFrame.maxX + 6.0 + default: + inputActionButtonOriginX = fieldBackgroundFrame.maxX + floorToScreenPixels((41.0 - inputActionButtonSize.width) * 0.5) + } } else { inputActionButtonOriginX = size.width } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD index 9e56ec5e89..46532e538c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD @@ -165,6 +165,7 @@ swift_library( "//submodules/TelegramUI/Components/BottomButtonPanelComponent", "//submodules/TelegramUI/Components/MarqueeComponent", "//submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist", + "//submodules/TelegramUI/Components/TextFieldComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenLabeledValueItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenLabeledValueItem.swift index b4992663c4..9714b3055b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenLabeledValueItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenLabeledValueItem.swift @@ -65,7 +65,9 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem { let id: AnyHashable let context: AccountContext? let label: String + let rightLabel: String? let text: String + let entities: [MessageTextEntity] let additionalText: String? let textColor: PeerInfoScreenLabeledValueTextColor let textBehavior: PeerInfoScreenLabeledValueTextBehavior @@ -83,7 +85,9 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem { id: AnyHashable, context: AccountContext? = nil, label: String, + rightLabel: String? = nil, text: String, + entities: [MessageTextEntity] = [], additionalText: String? = nil, textColor: PeerInfoScreenLabeledValueTextColor = .primary, textBehavior: PeerInfoScreenLabeledValueTextBehavior = .singleLine, @@ -100,7 +104,9 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem { self.id = id self.context = context self.label = label + self.rightLabel = rightLabel self.text = text + self.entities = entities self.additionalText = additionalText self.textColor = textColor self.textBehavior = textBehavior @@ -150,6 +156,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { private let selectionNode: PeerInfoScreenSelectableBackgroundNode private let maskNode: ASImageNode private let labelNode: ImmediateTextNode + private let rightLabelNode: ImmediateTextNode private let textNode: ImmediateTextNode private let additionalTextNode: ImmediateTextNode private let measureTextNode: ImmediateTextNode @@ -200,6 +207,10 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { self.labelNode.displaysAsynchronously = false self.labelNode.isUserInteractionEnabled = false + self.rightLabelNode = ImmediateTextNode() + self.rightLabelNode.displaysAsynchronously = false + self.rightLabelNode.isUserInteractionEnabled = false + self.textNode = ImmediateTextNode() self.textNode.displaysAsynchronously = false self.textNode.isUserInteractionEnabled = false @@ -250,6 +261,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { self.contextSourceNode.contentNode.addSubnode(self.extractedBackgroundImageNode) self.contextSourceNode.contentNode.addSubnode(self.labelNode) + self.contextSourceNode.contentNode.addSubnode(self.rightLabelNode) self.contextSourceNode.contentNode.addSubnode(self.textNode) self.contextSourceNode.contentNode.addSubnode(self.additionalTextNode) @@ -488,6 +500,8 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { self.labelNode.attributedText = NSAttributedString(string: item.label, font: Font.regular(14.0), textColor: presentationData.theme.list.itemPrimaryTextColor) + self.rightLabelNode.attributedText = NSAttributedString(string: item.rightLabel ?? "", font: Font.regular(14.0), textColor: presentationData.theme.list.itemSecondaryTextColor) + if let icon = item.icon { let iconImage: UIImage? switch icon { @@ -541,26 +555,32 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { text = trimToLineCount(text, lineCount: 3) } + let fontSize: CGFloat = 17.0 + let baseFont = Font.regular(fontSize) + let linkFont = baseFont + let boldFont = Font.medium(fontSize) + let italicFont = Font.italic(fontSize) + let boldItalicFont = Font.semiboldItalic(fontSize) + let titleFixedFont = Font.monospace(fontSize) + func createAttributedText(_ text: String) -> NSAttributedString { if enabledEntities.isEmpty { - return NSAttributedString(string: text, font: Font.regular(17.0), textColor: textColorValue) + return NSAttributedString(string: text, font: baseFont, textColor: textColorValue) } else { - let fontSize: CGFloat = 17.0 - - let baseFont = Font.regular(fontSize) - let linkFont = baseFont - let boldFont = Font.medium(fontSize) - let italicFont = Font.italic(fontSize) - let boldItalicFont = Font.semiboldItalic(fontSize) - let titleFixedFont = Font.monospace(fontSize) - let entities = generateTextEntities(text, enabledTypes: enabledEntities) return stringWithAppliedEntities(text, entities: entities, baseColor: textColorValue, linkColor: presentationData.theme.list.itemAccentColor, baseFont: baseFont, linkFont: linkFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: titleFixedFont, blockQuoteFont: baseFont, message: nil) } } self.measureTextNode.maximumNumberOfLines = 0 - self.measureTextNode.attributedText = createAttributedText(originalText) + + if !item.entities.isEmpty { + self.measureTextNode.attributedText = stringWithAppliedEntities(originalText, entities: item.entities, baseColor: textColorValue, linkColor: presentationData.theme.list.itemAccentColor, baseFont: baseFont, linkFont: linkFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: titleFixedFont, blockQuoteFont: baseFont, message: nil) + self.textNode.attributedText = stringWithAppliedEntities(text, entities: item.entities, baseColor: textColorValue, linkColor: presentationData.theme.list.itemAccentColor, baseFont: baseFont, linkFont: linkFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: titleFixedFont, blockQuoteFont: baseFont, message: nil) + } else { + self.measureTextNode.attributedText = createAttributedText(originalText) + self.textNode.attributedText = createAttributedText(text) + } let textLayout = self.measureTextNode.updateLayoutInfo(CGSize(width: width - sideInset * 2.0 - additionalSideInset, height: .greatestFiniteMagnitude)) var collapsedNumberOfLines = 3 @@ -568,7 +588,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { collapsedNumberOfLines = 4 } - self.textNode.attributedText = createAttributedText(text) + maxNumberOfLines = self.isExpanded ? maxLines : collapsedNumberOfLines self.textNode.maximumNumberOfLines = maxNumberOfLines @@ -576,6 +596,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { let labelSize = self.labelNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude)) + let rightLabelSize = self.rightLabelNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude)) let textLayout = self.textNode.updateLayoutInfo(CGSize(width: width - sideInset * 2.0 - additionalSideInset, height: .greatestFiniteMagnitude)) let textSize = textLayout.size @@ -601,6 +622,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { var topOffset = 11.0 var height = topOffset * 2.0 let labelFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: labelSize) + let rightLabelFrame = CGRect(origin: CGPoint(x: width - sideInset - rightLabelSize.width, y: topOffset), size: rightLabelSize) if labelSize.height > 0.0 { topOffset += labelSize.height + 3.0 height += labelSize.height + 3.0 @@ -653,6 +675,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { self.expandBackgroundNode.image = generateExpandBackground(size: expandBackgroundFrame.size, color: presentationData.theme.list.itemBlocksBackgroundColor) transition.updateFrame(node: self.labelNode, frame: labelFrame) + transition.updateFrame(node: self.rightLabelNode, frame: rightLabelFrame) var textTransition = transition if self.textNode.frame.size != textFrame.size { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenNoteListItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenNoteListItem.swift new file mode 100644 index 0000000000..11193b2b15 --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenNoteListItem.swift @@ -0,0 +1,121 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramPresentationData +import TelegramCore +import AccountContext +import ComponentFlow +import TextFieldComponent + +final class PeerInfoScreenNoteListItem: PeerInfoScreenItem { + let id: AnyHashable + let initialValue: NSAttributedString + let valueUpdated: (NSAttributedString) -> Void + let requestLayout: (Bool) -> Void + + init( + id: AnyHashable, + initialValue: NSAttributedString, + valueUpdated: @escaping (NSAttributedString) -> Void, + requestLayout: @escaping (Bool) -> Void + ) { + self.id = id + self.initialValue = initialValue + self.valueUpdated = valueUpdated + self.requestLayout = requestLayout + } + + func node() -> PeerInfoScreenItemNode { + return PeerInfoScreenNoteListItemNode() + } +} + +private final class PeerInfoScreenNoteListItemNode: PeerInfoScreenItemNode { + private let maskNode: ASImageNode + private let textField = ComponentView() + private let textFieldExternalState = TextFieldComponent.ExternalState() + + private let state = EmptyComponentState() + + private let bottomSeparatorNode: ASDisplayNode + + private var item: PeerInfoScreenNoteListItem? + private var presentationData: PresentationData? + private var theme: PresentationTheme? + + override init() { + self.maskNode = ASImageNode() + self.maskNode.isUserInteractionEnabled = false + + self.bottomSeparatorNode = ASDisplayNode() + self.bottomSeparatorNode.isLayerBacked = true + + super.init() + + self.addSubnode(self.bottomSeparatorNode) + + self.addSubnode(self.maskNode) + } + + override func update(context: AccountContext, width: CGFloat, safeInsets: UIEdgeInsets, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, hasCorners: Bool, transition: ContainedViewLayoutTransition) -> CGFloat { + guard let item = item as? PeerInfoScreenNoteListItem else { + return 10.0 + } + + var resetText: NSAttributedString? + if self.item == nil { + resetText = item.initialValue + } + + self.item = item + self.presentationData = presentationData + self.theme = presentationData.theme + + let sideInset: CGFloat = 1.0 + safeInsets.left + + self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor + + self.state._updated = { [weak self] transition, _ in + guard let self else { + return + } + self.item?.requestLayout(!transition.animation.isImmediate) + } + + var characterLimit: Int = 128 + if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["contact_note_length_limit"] as? Double { + characterLimit = Int(value) + } + + self.textField.parentState = self.state + let textFieldSize = self.textField.update( + transition: .immediate, + component: AnyComponent(TextFieldComponent(context: context, theme: presentationData.theme, strings: presentationData.strings, externalState: self.textFieldExternalState, fontSize: 17.0, textColor: presentationData.theme.list.itemPrimaryTextColor, accentColor: presentationData.theme.list.itemAccentColor, insets: UIEdgeInsets(top: 9.0, left: 8.0, bottom: 10.0, right: 8.0), hideKeyboard: false, customInputView: nil, placeholder: NSAttributedString(string: "Add notes", font: Font.regular(17.0), textColor: presentationData.theme.list.itemPlaceholderTextColor), resetText: resetText, isOneLineWhenUnfocused: false, characterLimit: characterLimit, formatMenuAvailability: .available([.bold, .italic, .underline, .strikethrough, .spoiler]), lockedFormatAction: {}, present: { c in }, paste: { _ in })), + environment: {}, + containerSize: CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude) + ) + let textFieldFrame = CGRect(origin: CGPoint(x: sideInset, y: 3.0), size: textFieldSize) + let height: CGFloat = 4.0 + textFieldSize.height + if let textFieldView = self.textField.view { + if textFieldView.superview == nil { + self.view.addSubview(textFieldView) + } + transition.updateFrame(view: textFieldView, frame: textFieldFrame) + } + item.valueUpdated(self.textFieldExternalState.text) + + transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: sideInset, y: height - UIScreenPixel), size: CGSize(width: width - sideInset, height: UIScreenPixel))) + transition.updateAlpha(node: self.bottomSeparatorNode, alpha: bottomItem == nil ? 0.0 : 1.0) + + let hasCorners = hasCorners && (topItem == nil || bottomItem == nil) + let hasTopCorners = hasCorners && topItem == nil + let hasBottomCorners = hasCorners && bottomItem == nil + + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) + self.bottomSeparatorNode.isHidden = hasBottomCorners + + return height + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index 848d9d8a98..6ef23eda0c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -34,6 +34,7 @@ final class PeerInfoState { let paneIsReordering: Bool let updatingAvatar: PeerInfoUpdatingAvatar? let updatingBio: String? + let updatingNote: NSAttributedString? let avatarUploadProgress: AvatarUploadProgress? let highlightedButton: PeerInfoHeaderButtonKey? let isEditingBirthDate: Bool @@ -47,6 +48,7 @@ final class PeerInfoState { paneIsReordering: Bool, updatingAvatar: PeerInfoUpdatingAvatar?, updatingBio: String?, + updatingNote: NSAttributedString?, avatarUploadProgress: AvatarUploadProgress?, highlightedButton: PeerInfoHeaderButtonKey?, isEditingBirthDate: Bool, @@ -59,6 +61,7 @@ final class PeerInfoState { self.paneIsReordering = paneIsReordering self.updatingAvatar = updatingAvatar self.updatingBio = updatingBio + self.updatingNote = updatingNote self.avatarUploadProgress = avatarUploadProgress self.highlightedButton = highlightedButton self.isEditingBirthDate = isEditingBirthDate @@ -74,6 +77,7 @@ final class PeerInfoState { paneIsReordering: self.paneIsReordering, updatingAvatar: self.updatingAvatar, updatingBio: self.updatingBio, + updatingNote: self.updatingNote, avatarUploadProgress: self.avatarUploadProgress, highlightedButton: self.highlightedButton, isEditingBirthDate: self.isEditingBirthDate, @@ -90,6 +94,7 @@ final class PeerInfoState { paneIsReordering: self.paneIsReordering, updatingAvatar: self.updatingAvatar, updatingBio: self.updatingBio, + updatingNote: self.updatingNote, avatarUploadProgress: self.avatarUploadProgress, highlightedButton: self.highlightedButton, isEditingBirthDate: self.isEditingBirthDate, @@ -106,6 +111,7 @@ final class PeerInfoState { paneIsReordering: self.paneIsReordering, updatingAvatar: self.updatingAvatar, updatingBio: self.updatingBio, + updatingNote: self.updatingNote, avatarUploadProgress: self.avatarUploadProgress, highlightedButton: self.highlightedButton, isEditingBirthDate: self.isEditingBirthDate, @@ -122,6 +128,7 @@ final class PeerInfoState { paneIsReordering: paneIsReordering, updatingAvatar: self.updatingAvatar, updatingBio: self.updatingBio, + updatingNote: self.updatingNote, avatarUploadProgress: self.avatarUploadProgress, highlightedButton: self.highlightedButton, isEditingBirthDate: self.isEditingBirthDate, @@ -138,6 +145,7 @@ final class PeerInfoState { paneIsReordering: self.paneIsReordering, updatingAvatar: updatingAvatar, updatingBio: self.updatingBio, + updatingNote: self.updatingNote, avatarUploadProgress: self.avatarUploadProgress, highlightedButton: self.highlightedButton, isEditingBirthDate: self.isEditingBirthDate, @@ -154,6 +162,24 @@ final class PeerInfoState { paneIsReordering: self.paneIsReordering, updatingAvatar: self.updatingAvatar, updatingBio: updatingBio, + updatingNote: self.updatingNote, + avatarUploadProgress: self.avatarUploadProgress, + highlightedButton: self.highlightedButton, + isEditingBirthDate: self.isEditingBirthDate, + updatingBirthDate: self.updatingBirthDate, + personalChannels: self.personalChannels + ) + } + + func withUpdatingNote(_ updatingNote: NSAttributedString?) -> PeerInfoState { + return PeerInfoState( + isEditing: self.isEditing, + selectedMessageIds: self.selectedMessageIds, + selectedStoryIds: self.selectedStoryIds, + paneIsReordering: self.paneIsReordering, + updatingAvatar: self.updatingAvatar, + updatingBio: self.updatingBio, + updatingNote: updatingNote, avatarUploadProgress: self.avatarUploadProgress, highlightedButton: self.highlightedButton, isEditingBirthDate: self.isEditingBirthDate, @@ -170,6 +196,7 @@ final class PeerInfoState { paneIsReordering: self.paneIsReordering, updatingAvatar: self.updatingAvatar, updatingBio: self.updatingBio, + updatingNote: self.updatingNote, avatarUploadProgress: avatarUploadProgress, highlightedButton: self.highlightedButton, isEditingBirthDate: self.isEditingBirthDate, @@ -186,6 +213,7 @@ final class PeerInfoState { paneIsReordering: self.paneIsReordering, updatingAvatar: self.updatingAvatar, updatingBio: self.updatingBio, + updatingNote: self.updatingNote, avatarUploadProgress: self.avatarUploadProgress, highlightedButton: highlightedButton, isEditingBirthDate: self.isEditingBirthDate, @@ -202,6 +230,7 @@ final class PeerInfoState { paneIsReordering: self.paneIsReordering, updatingAvatar: self.updatingAvatar, updatingBio: self.updatingBio, + updatingNote: self.updatingNote, avatarUploadProgress: self.avatarUploadProgress, highlightedButton: self.highlightedButton, isEditingBirthDate: isEditingBirthDate, @@ -218,6 +247,7 @@ final class PeerInfoState { paneIsReordering: self.paneIsReordering, updatingAvatar: self.updatingAvatar, updatingBio: self.updatingBio, + updatingNote: self.updatingNote, avatarUploadProgress: self.avatarUploadProgress, highlightedButton: self.highlightedButton, isEditingBirthDate: self.isEditingBirthDate, @@ -234,6 +264,7 @@ final class PeerInfoState { paneIsReordering: self.paneIsReordering, updatingAvatar: self.updatingAvatar, updatingBio: self.updatingBio, + updatingNote: self.updatingNote, avatarUploadProgress: self.avatarUploadProgress, highlightedButton: self.highlightedButton, isEditingBirthDate: self.isEditingBirthDate, diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 9245f2ae7f..a560bf57da 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -539,6 +539,7 @@ private enum PeerInfoSettingsSection { case logout case rememberPassword case emojiStatus + case profileColor case powerSaving case businessSetup case profile @@ -566,6 +567,7 @@ private final class PeerInfoInteraction { let editingOpenSoundSettings: () -> Void let editingToggleShowMessageText: (Bool) -> Void let requestDeleteContact: () -> Void + let suggestBirthdate: () -> Void let suggestPhoto: () -> Void let setCustomPhoto: () -> Void let resetCustomPhoto: () -> Void @@ -604,6 +606,7 @@ private final class PeerInfoInteraction { let logoutAccount: (AccountRecordId) -> Void let accountContextMenu: (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void let updateBio: (String) -> Void + let updateNote: (NSAttributedString) -> Void let openDeletePeer: () -> Void let openFaq: (String?) -> Void let openAddMember: () -> Void @@ -639,6 +642,7 @@ private final class PeerInfoInteraction { editingOpenSoundSettings: @escaping () -> Void, editingToggleShowMessageText: @escaping (Bool) -> Void, requestDeleteContact: @escaping () -> Void, + suggestBirthdate: @escaping () -> Void, suggestPhoto: @escaping () -> Void, setCustomPhoto: @escaping () -> Void, resetCustomPhoto: @escaping () -> Void, @@ -678,6 +682,7 @@ private final class PeerInfoInteraction { logoutAccount: @escaping (AccountRecordId) -> Void, accountContextMenu: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void, updateBio: @escaping (String) -> Void, + updateNote: @escaping (NSAttributedString) -> Void, openDeletePeer: @escaping () -> Void, openFaq: @escaping (String?) -> Void, openAddMember: @escaping () -> Void, @@ -712,6 +717,7 @@ private final class PeerInfoInteraction { self.editingOpenSoundSettings = editingOpenSoundSettings self.editingToggleShowMessageText = editingToggleShowMessageText self.requestDeleteContact = requestDeleteContact + self.suggestBirthdate = suggestBirthdate self.suggestPhoto = suggestPhoto self.setCustomPhoto = setCustomPhoto self.resetCustomPhoto = resetCustomPhoto @@ -751,6 +757,7 @@ private final class PeerInfoInteraction { self.logoutAccount = logoutAccount self.accountContextMenu = accountContextMenu self.updateBio = updateBio + self.updateNote = updateNote self.openDeletePeer = openDeletePeer self.openFaq = openFaq self.openAddMember = openAddMember @@ -809,13 +816,10 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p } let setPhotoTitle: String - let displaySetPhoto: Bool if let peer = data.peer, !peer.profileImageRepresentations.isEmpty { setPhotoTitle = presentationData.strings.Settings_ChangeProfilePhoto - displaySetPhoto = true } else { setPhotoTitle = presentationData.strings.Settings_SetProfilePhotoOrVideo - displaySetPhoto = true } var setStatusTitle: String = "" @@ -837,15 +841,18 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p items[.edit]!.append(PeerInfoScreenActionItem(id: 0, text: setStatusTitle, icon: UIImage(bundleImageName: hasEmojiStatus ? "Settings/EditEmojiStatus" : "Settings/SetEmojiStatus"), action: { interaction.openSettings(.emojiStatus) })) - } - - if displaySetPhoto { - items[.edit]!.append(PeerInfoScreenActionItem(id: 1, text: setPhotoTitle, icon: UIImage(bundleImageName: "Settings/SetAvatar"), action: { - interaction.openSettings(.avatar) + + items[.edit]!.append(PeerInfoScreenActionItem(id: 1, text: presentationData.strings.PeerInfo_ChangeProfileColor, icon: UIImage(bundleImageName: "Premium/BoostPerk/CoverColor"), action: { + interaction.openSettings(.profileColor) })) } + + items[.edit]!.append(PeerInfoScreenActionItem(id: 2, text: setPhotoTitle, icon: UIImage(bundleImageName: "Settings/SetAvatar"), action: { + interaction.openSettings(.avatar) + })) + if let peer = data.peer, (peer.addressName ?? "").isEmpty { - items[.edit]!.append(PeerInfoScreenActionItem(id: 2, text: presentationData.strings.Settings_SetUsername, icon: UIImage(bundleImageName: "Settings/SetUsername"), action: { + items[.edit]!.append(PeerInfoScreenActionItem(id: 3, text: presentationData.strings.Settings_SetUsername, icon: UIImage(bundleImageName: "Settings/SetUsername"), action: { interaction.openSettings(.username) })) } @@ -1414,6 +1421,10 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese if let about = cachedData.about, !about.isEmpty { hasAbout = true } + var hasNote = false + if let note = cachedData.note, !note.text.isEmpty { + hasNote = true + } var hasWebApp = false if let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) { @@ -1428,7 +1439,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: { animated in interaction.requestLayout(animated) })) - } else if hasAbout || hasWebApp { + } else if hasAbout || hasNote || hasWebApp { var actionButton: PeerInfoScreenLabeledValueItem.Button? if hasWebApp { actionButton = PeerInfoScreenLabeledValueItem.Button(title: presentationData.strings.PeerInfo_OpenAppButton, action: { @@ -1463,16 +1474,23 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese }) } - var label: String = "" - if let about = cachedData.about, !about.isEmpty { - label = user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo + if hasAbout || hasWebApp { + var label: String = "" + if let about = cachedData.about, !about.isEmpty { + label = user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo + } + items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: label, text: cachedData.about ?? "", textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.isPremium ? enabledPublicBioEntities : enabledPrivateBioEntities), action: isMyProfile ? { node, _ in + bioContextAction(node, nil, nil) + } : nil, linkItemAction: bioLinkAction, button: actionButton, contextAction: bioContextAction, requestLayout: { animated in + interaction.requestLayout(animated) + })) } - items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: label, text: cachedData.about ?? "", textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.isPremium ? enabledPublicBioEntities : enabledPrivateBioEntities), action: isMyProfile ? { node, _ in - bioContextAction(node, nil, nil) - } : nil, linkItemAction: bioLinkAction, button: actionButton, contextAction: bioContextAction, requestLayout: { animated in - interaction.requestLayout(animated) - })) + if let note = cachedData.note, !note.text.isEmpty { + items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: "notes", rightLabel: "only visible to you", text: note.text, entities: note.entities, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: []), action: nil, linkItemAction: bioLinkAction, button: nil, contextAction: bioContextAction, requestLayout: { animated in + interaction.requestLayout(animated) + })) + } if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: 800, text: presentationData.strings.PeerInfo_AppFooterAdmin, linkAction: { action in @@ -2066,6 +2084,7 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt case notifications case groupLocation case peerPublicSettings + case peerNote case peerDataSettings case peerVerifySettings case peerSettings @@ -2081,20 +2100,24 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt if let data = data { if let user = data.peer as? TelegramUser { - let ItemSuggest = 0 - let ItemCustom = 1 - let ItemReset = 2 - let ItemInfo = 3 - let ItemDelete = 4 - let ItemUsername = 5 - let ItemAffiliateProgram = 6 + let ItemNote = 0 + let ItemNoteInfo = 1 - let ItemVerify = 8 + let ItemSuggestBirthdate = 2 + let ItemSuggestPhoto = 3 + let ItemCustomPhoto = 4 + let ItemReset = 5 + let ItemInfo = 6 + let ItemDelete = 7 + let ItemUsername = 8 + let ItemAffiliateProgram = 9 - let ItemIntro = 8 - let ItemCommands = 9 - let ItemBotSettings = 10 - let ItemBotInfo = 11 + let ItemVerify = 10 + + let ItemIntro = 11 + let ItemCommands = 12 + let ItemBotSettings = 13 + let ItemBotInfo = 14 if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text("@\(user.addressName ?? "")"), text: presentationData.strings.PeerInfo_Bot_Username, icon: PresentationResourcesSettings.bot, action: { @@ -2143,12 +2166,34 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt } else if !user.flags.contains(.isSupport) { let compactName = EnginePeer(user).compactDisplayTitle - if let cachedData = data.cachedData as? CachedUserData, let _ = cachedData.sendPaidMessageStars { + if let cachedData = data.cachedData as? CachedUserData { + //TODO:localize + items[.peerNote]!.append(PeerInfoScreenNoteListItem( + id: ItemNote, + initialValue: chatInputStateStringWithAppliedEntities(cachedData.note?.text ?? "", entities: cachedData.note?.entities ?? []), + valueUpdated: { value in + interaction.updateNote(value) + }, + requestLayout: { animated in + interaction.requestLayout(animated) + } + )) - } else { - items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemSuggest, text: presentationData.strings.UserInfo_SuggestPhoto(compactName).string, color: .accent, icon: UIImage(bundleImageName: "Peer Info/SuggestAvatar"), action: { - interaction.suggestPhoto() - })) + items[.peerNote]!.append(PeerInfoScreenCommentItem(id: ItemNoteInfo, text: "Notes are only visible to you.")) + + if let _ = cachedData.sendPaidMessageStars { + + } else { + if cachedData.birthday == nil { + items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemSuggestBirthdate, text: presentationData.strings.UserInfo_SuggestBirthdate, color: .accent, icon: UIImage(bundleImageName: "Contact List/AddBirthdayIcon"), action: { + interaction.suggestBirthdate() + })) + } + + items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemSuggestPhoto, text: presentationData.strings.UserInfo_SuggestPhoto(compactName).string, color: .accent, icon: UIImage(bundleImageName: "Peer Info/SuggestAvatar"), action: { + interaction.suggestPhoto() + })) + } } let setText: String @@ -2158,7 +2203,7 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt setText = presentationData.strings.UserInfo_SetCustomPhoto(compactName).string } - items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemCustom, text: setText, color: .accent, icon: UIImage(bundleImageName: "Settings/SetAvatar"), action: { + items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemCustomPhoto, text: setText, color: .accent, icon: UIImage(bundleImageName: "Settings/SetAvatar"), action: { interaction.setCustomPhoto() })) @@ -2970,6 +3015,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro paneIsReordering: false, updatingAvatar: nil, updatingBio: nil, + updatingNote: nil, avatarUploadProgress: nil, highlightedButton: nil, isEditingBirthDate: false, @@ -3093,6 +3139,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro requestDeleteContact: { [weak self] in self?.requestDeleteContact() }, + suggestBirthdate: { [weak self] in + self?.suggestBirthdate() + }, suggestPhoto: { [weak self] in self?.suggestPhoto() }, @@ -3210,6 +3259,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro updateBio: { [weak self] bio in self?.updateBio(bio) }, + updateNote: { [weak self] note in + if let self { + self.state = self.state.withUpdatingNote(note) + } + }, openDeletePeer: { [weak self] in self?.openDeletePeer() }, @@ -4445,7 +4499,22 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let firstName = strongSelf.headerNode.editingContentNode.editingTextForKey(.firstName) ?? "" let lastName = strongSelf.headerNode.editingContentNode.editingTextForKey(.lastName) ?? "" - if (peer.firstName ?? "") != firstName || (peer.lastName ?? "") != lastName { + let note = strongSelf.state.updatingNote + + let firstNameUpdated = (peer.firstName ?? "") != firstName + let lastNameUpdated = (peer.lastName ?? "") != lastName + var noteUpdated = false + + if let cachedData = data.cachedData as? CachedUserData { + if let note { + let updatedEntities = generateChatInputTextEntities(note) + if note.string != (cachedData.note?.text ?? "") || updatedEntities != (cachedData.note?.entities ?? []) { + noteUpdated = true + } + } + } + + if firstNameUpdated || lastNameUpdated || noteUpdated { if firstName.isEmpty && lastName.isEmpty { strongSelf.hapticFeedback.error() strongSelf.headerNode.editingContentNode.shakeTextForKey(.firstName) @@ -4460,8 +4529,32 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } strongSelf.controller?.present(statusController, in: .window(.root)) - strongSelf.activeActionDisposable.set((context.engine.contacts.updateContactName(peerId: peer.id, firstName: firstName, lastName: lastName) - |> deliverOnMainQueue).startStrict(error: { _ in + enum ContactUpdateError { + case generic + } + let nameUpdateSignal: Signal + if firstNameUpdated || lastNameUpdated { + nameUpdateSignal = context.engine.contacts.updateContactName(peerId: peer.id, firstName: firstName, lastName: lastName) + |> mapError { _ -> ContactUpdateError in + return .generic + } + |> ignoreValues + } else { + nameUpdateSignal = .complete() + } + + let noteUpdateSignal: Signal + if noteUpdated, let note { + let entities = generateChatInputTextEntities(note) + noteUpdateSignal = context.engine.contacts.updateContactNote(peerId: peer.id, text: note.string, entities: entities) + |> mapError { _ -> ContactUpdateError in + return .generic + } + } else { + noteUpdateSignal = .complete() + } + + strongSelf.activeActionDisposable.set(combineLatest(queue: Queue.mainQueue(), nameUpdateSignal, noteUpdateSignal).startStrict(error: { _ in dismissStatus?() guard let strongSelf = self else { @@ -4476,7 +4569,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } let context = strongSelf.context - let _ = (getUserPeer(engine: strongSelf.context.engine, peerId: peer.id) + let _ = (getUserPeer(engine: context.engine, peerId: peer.id) |> mapToSignal { peer -> Signal in guard case let .user(peer) = peer, let phone = peer.phone, !phone.isEmpty else { return .complete() @@ -4627,7 +4720,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil) } } else { - strongSelf.state = strongSelf.state.withIsEditing(false).withUpdatingBio(nil).withUpdatingBirthDate(nil).withIsEditingBirthDate(false) + strongSelf.state = strongSelf.state.withIsEditing(false).withUpdatingBio(nil).withUpdatingBirthDate(nil).withIsEditingBirthDate(false).withUpdatingNote(nil) if let (layout, navigationHeight) = strongSelf.validLayout { strongSelf.scrollNode.view.setContentOffset(CGPoint(), animated: false) strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) @@ -7601,7 +7694,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in if savedMessages, let self, action == .info { let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) - |> deliverOnMainQueue).start(next: { [weak self] peer in + |> deliverOnMainQueue).start(next: { [weak self] peer in guard let self, let peer else { return } @@ -10751,6 +10844,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro push(controller) case .emojiStatus: self.headerNode.invokeDisplayPremiumIntro() + case .profileColor: + self.interaction.editingOpenNameColorSetup() case .powerSaving: push(energySavingSettingsScreen(context: self.context)) case .businessSetup: @@ -12208,6 +12303,30 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro peerController.present(controller, in: .window(.root)) } + private func suggestBirthdate() { + let controller = context.sharedContext.makeBirthdaySuggestionScreen( + context: self.context, + peerId: self.peerId, + completion: { [weak self] value in + guard let self else { + return + } + //TODO:release + var text = "\(value.day)_\(value.month)" + if let year = value.year { + text += "_\(year)" + } + let _ = enqueueMessages(account: self.context.account, peerId: self.peerId, messages: [ + .message(text: "[birthdate]\(text)", attributes: [], inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + ]).start() + + self.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil) + self.openChat(peerId: self.peerId) + } + ) + self.controller?.push(controller) + } + private func suggestPhoto() { self.controller?.openAvatarForEditing(mode: .suggest) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index 35ebc2693d..7f4d541535 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -935,7 +935,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } let bottomContentOffset = max(0.0, self.scrollNode.view.contentSize.height - self.scrollNode.view.contentOffset.y - self.scrollNode.view.frame.height) - if interactive, bottomContentOffset < 200.0 { + if bottomContentOffset < 200.0 { self.giftsListView.loadMore() } } diff --git a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/BUILD b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/BUILD index 26290c940d..aaa4fa02d9 100644 --- a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/BUILD @@ -24,8 +24,8 @@ swift_library( "//submodules/AppBundle", "//submodules/Components/SheetComponent", "//submodules/PresentationDataUtils", - "//submodules/Components/SolidRoundedButtonComponent", "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/PlainButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerComponent.swift b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerComponent.swift index 44a581ee4f..b6d17cf88f 100644 --- a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerComponent.swift +++ b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerComponent.swift @@ -8,6 +8,14 @@ import AccountContext import ComponentFlow public final class BirthdayPickerComponent: Component { + public final class TransitionHint { + public let animate: Bool + + public init(animate: Bool) { + self.animate = animate + } + } + public struct Theme: Equatable { let backgroundColor: UIColor let textColor: UIColor @@ -97,7 +105,11 @@ public final class BirthdayPickerComponent: Component { if let year = component.value.year { self.pickerView.selectRow(Int(year - self.minYear), inComponent: PickerComponent.year.rawValue, animated: false) } else { - self.pickerView.selectRow(Int(self.maxYear - self.minYear + 1), inComponent: PickerComponent.year.rawValue, animated: false) + var animate = false + if let hint = transition.userData(TransitionHint.self), hint.animate { + animate = true + } + self.pickerView.selectRow(Int(self.maxYear - self.minYear + 1), inComponent: PickerComponent.year.rawValue, animated: animate) } self.pickerView.selectRow(Int(component.value.month) - 1, inComponent: PickerComponent.month.rawValue, animated: false) self.pickerView.selectRow(Int(component.value.day) - 1, inComponent: PickerComponent.day.rawValue, animated: false) diff --git a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerContentComponent.swift b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerContentComponent.swift index b1fdea8c21..0845c1d338 100644 --- a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerContentComponent.swift +++ b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerContentComponent.swift @@ -7,37 +7,54 @@ import MultilineTextComponent import TelegramPresentationData import AppBundle import BundleIconComponent +import PlainButtonComponent import Markdown import TelegramCore - +import AccountContext + public final class BirthdayPickerContentComponent: Component { + public let context: AccountContext + public let mode: BirthdayPickerScreen.Mode public let theme: PresentationTheme public let strings: PresentationStrings public let settings: Signal public let value: TelegramBirthday + public let canHideYear: Bool public let updateValue: (TelegramBirthday) -> Void public let dismiss: () -> Void public let openSettings: () -> Void + public let requestHideYear: (ComponentTransition) -> Void public init( + context: AccountContext, + mode: BirthdayPickerScreen.Mode, theme: PresentationTheme, strings: PresentationStrings, settings: Signal, value: TelegramBirthday, + canHideYear: Bool, updateValue: @escaping (TelegramBirthday) -> Void, dismiss: @escaping () -> Void, - openSettings: @escaping () -> Void + openSettings: @escaping () -> Void, + requestHideYear: @escaping (ComponentTransition) -> Void ) { + self.context = context + self.mode = mode self.theme = theme self.strings = strings self.settings = settings self.value = value + self.canHideYear = canHideYear self.updateValue = updateValue self.dismiss = dismiss self.openSettings = openSettings + self.requestHideYear = requestHideYear } public static func ==(lhs: BirthdayPickerContentComponent, rhs: BirthdayPickerContentComponent) -> Bool { + if lhs.mode != rhs.mode { + return false + } if lhs.theme !== rhs.theme { return false } @@ -47,6 +64,9 @@ public final class BirthdayPickerContentComponent: Component { if lhs.value != rhs.value { return false } + if lhs.canHideYear != rhs.canHideYear { + return false + } return true } @@ -54,19 +74,23 @@ public final class BirthdayPickerContentComponent: Component { private let title = ComponentView() private let picker = ComponentView() private let mainText = ComponentView() + private let hideYear = ComponentView() private var chevronImage: UIImage? private var component: BirthdayPickerContentComponent? + private var state: EmptyComponentState? private var disposable: Disposable? private var settings: AccountPrivacySettings? + private var peer: EnginePeer? + private var isUpdating = false public override init(frame: CGRect) { super.init(frame: frame) } - + required init(coder: NSCoder) { preconditionFailure() } @@ -88,15 +112,37 @@ public final class BirthdayPickerContentComponent: Component { defer { self.isUpdating = false } - + self.component = component + self.state = state + + if self.disposable == nil { + let peer: Signal + if case let .suggest(peerId) = component.mode { + peer = component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + } else { + peer = .single(nil) + } + self.disposable = combineLatest(queue: Queue.mainQueue(), + component.settings, + peer + ).start(next: { [weak self, weak state] settings, peer in + if let self { + self.settings = settings + self.peer = peer + if !self.isUpdating { + state?.updated() + } + } + }) + } let sideInset: CGFloat = 16.0 var contentHeight: CGFloat = 0.0 let titleString = NSMutableAttributedString() - titleString.append(NSAttributedString(string: component.strings.Birthday_Title, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor)) + titleString.append(NSAttributedString(string: self.peer.flatMap { component.strings.SuggestBirthdate_Suggest_Title($0.compactDisplayTitle).string } ?? component.strings.Birthday_Title, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor)) let titleSize = self.title.update( transition: .immediate, @@ -117,12 +163,20 @@ public final class BirthdayPickerContentComponent: Component { contentHeight += 16.0 let pickerSize = self.picker.update( - transition: .immediate, + transition: transition, component: AnyComponent(BirthdayPickerComponent( theme: BirthdayPickerComponent.Theme(presentationTheme: component.theme), strings: component.strings, value: component.value, - valueUpdated: component.updateValue + valueUpdated: { [weak self] value in + guard let self, let component = self.component else { + return + } + component.updateValue(value) + if component.canHideYear && value.year == nil { + component.requestHideYear(.spring(duration: 0.4)) + } + } )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 226.0) @@ -135,91 +189,110 @@ public final class BirthdayPickerContentComponent: Component { } contentHeight += pickerSize.height contentHeight += 8.0 - - if self.disposable == nil { - self.disposable = (component.settings - |> deliverOnMainQueue).start(next: { [weak self, weak state] settings in - if let self { - self.settings = settings - if !self.isUpdating { - state?.updated() - } + + switch component.mode { + case .generic: + var isContacts = true + if let settings = self.settings { + if case .enableContacts = settings.birthday { + } else { + isContacts = false } - }) - } - var isContacts = true - if let settings = self.settings { - if case .enableContacts = settings.birthday { - } else { - isContacts = false } - } - - let mainText = NSMutableAttributedString() - mainText.append(parseMarkdownIntoAttributedString(isContacts ? component.strings.Birthday_HelpContacts : component.strings.Birthday_Help, attributes: MarkdownAttributes( - body: MarkdownAttributeSet( - font: Font.regular(13.0), - textColor: component.theme.list.itemSecondaryTextColor - ), - bold: MarkdownAttributeSet( - font: Font.semibold(13.0), - textColor: component.theme.list.itemSecondaryTextColor - ), - link: MarkdownAttributeSet( - font: Font.regular(13.0), - textColor: component.theme.list.itemAccentColor, - additionalAttributes: [:] - ), - linkAttribute: { attributes in - return ("URL", "") - } - ))) - if self.chevronImage == nil { - self.chevronImage = UIImage(bundleImageName: "Contact List/SubtitleArrow") - } - if let range = mainText.string.range(of: ">"), let chevronImage = self.chevronImage { - mainText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: mainText.string)) - } - - let mainTextSize = self.mainText.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(mainText), - horizontalAlignment: .center, - maximumNumberOfLines: 0, - lineSpacing: 0.2, - highlightColor: component.theme.list.itemAccentColor.withMultipliedAlpha(0.1), - highlightInset: mainText.string.contains(">") ? UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0) : .zero, - highlightAction: { attributes in - if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { - return NSAttributedString.Key(rawValue: "URL") - } else { - return nil - } - }, - tapAction: { [weak self] _, _ in - guard let self, let component = self.component else { - return - } - component.openSettings() + + let mainText = NSMutableAttributedString() + mainText.append(parseMarkdownIntoAttributedString(isContacts ? component.strings.Birthday_HelpContacts : component.strings.Birthday_Help, attributes: MarkdownAttributes( + body: MarkdownAttributeSet( + font: Font.regular(13.0), + textColor: component.theme.list.itemSecondaryTextColor + ), + bold: MarkdownAttributeSet( + font: Font.semibold(13.0), + textColor: component.theme.list.itemSecondaryTextColor + ), + link: MarkdownAttributeSet( + font: Font.regular(13.0), + textColor: component.theme.list.itemAccentColor, + additionalAttributes: [:] + ), + linkAttribute: { attributes in + return ("URL", "") } - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) - ) - if let mainTextView = self.mainText.view { - if mainTextView.superview == nil { - self.addSubview(mainTextView) + ))) + if self.chevronImage == nil { + self.chevronImage = UIImage(bundleImageName: "Contact List/SubtitleArrow") } - transition.setFrame(view: mainTextView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - mainTextSize.width) * 0.5), y: contentHeight), size: mainTextSize)) + if let range = mainText.string.range(of: ">"), let chevronImage = self.chevronImage { + mainText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: mainText.string)) + } + + let mainTextSize = self.mainText.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(mainText), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2, + highlightColor: component.theme.list.itemAccentColor.withMultipliedAlpha(0.1), + highlightInset: mainText.string.contains(">") ? UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0) : .zero, + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { + return NSAttributedString.Key(rawValue: "URL") + } else { + return nil + } + }, + tapAction: { [weak self] _, _ in + guard let self, let component = self.component else { + return + } + component.openSettings() + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) + ) + if let mainTextView = self.mainText.view { + if mainTextView.superview == nil { + self.addSubview(mainTextView) + } + transition.setFrame(view: mainTextView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - mainTextSize.width) * 0.5), y: contentHeight), size: mainTextSize)) + } + contentHeight += mainTextSize.height + contentHeight += 12.0 + case .acceptSuggestion: + if component.canHideYear { + contentHeight += 21.0 + let hideYearSize = self.hideYear.update( + transition: .immediate, + component: AnyComponent(PlainButtonComponent(content: AnyComponent(Text(text: component.strings.SuggestBirthdate_Accept_HideYear, font: Font.regular(17.0), color: component.theme.list.itemAccentColor)), action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.updateValue(TelegramBirthday(day: component.value.day, month: component.value.month, year: nil)) + component.requestHideYear(.spring(duration: 0.4).withUserData(BirthdayPickerComponent.TransitionHint(animate: true))) + }, animateScale: false)), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) + ) + if let hideYearView = self.hideYear.view { + if hideYearView.superview == nil { + self.addSubview(hideYearView) + } + transition.setFrame(view: hideYearView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - hideYearSize.width) * 0.5), y: contentHeight), size: hideYearSize)) + } + contentHeight += hideYearSize.height + contentHeight += 20.0 + } else if let hideYearView = self.hideYear.view { + transition.setAlpha(view: hideYearView, alpha: 0.0, completion: { _ in + hideYearView.removeFromSuperview() + }) + } + case .suggest: + break } - contentHeight += mainTextSize.height - - contentHeight += 12.0 - - let contentSize = CGSize(width: availableSize.width, height: contentHeight) - - return contentSize + + return CGSize(width: availableSize.width, height: contentHeight) } } diff --git a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerScreen.swift b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerScreen.swift index f3de37cbbe..d768b10f40 100644 --- a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerScreen.swift @@ -12,26 +12,38 @@ import TelegramCore private final class BirthdayPickerSheetContentComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment + let context: AccountContext + let mode: BirthdayPickerScreen.Mode let settings: Signal + let canHideYear: Bool let openSettings: () -> Void let dismiss: () -> Void let action: (TelegramBirthday) -> Void + let requestHideYear: (ComponentTransition) -> Void init( + context: AccountContext, + mode: BirthdayPickerScreen.Mode, settings: Signal, + canHideYear: Bool, openSettings: @escaping () -> Void, dismiss: @escaping () -> Void, - action: @escaping (TelegramBirthday) -> Void + action: @escaping (TelegramBirthday) -> Void, + requestHideYear: @escaping (ComponentTransition) -> Void ) { + self.context = context + self.mode = mode self.settings = settings + self.canHideYear = canHideYear self.openSettings = openSettings self.dismiss = dismiss self.action = action + self.requestHideYear = requestHideYear } static func ==(lhs: BirthdayPickerSheetContentComponent, rhs: BirthdayPickerSheetContentComponent) -> Bool { - if lhs !== rhs { - return true + if lhs.canHideYear != rhs.canHideYear { + return false } return true } @@ -44,6 +56,7 @@ private final class BirthdayPickerSheetContentComponent: Component { private var birthday = TelegramBirthday(day: 1, month: 1, year: nil) private var component: BirthdayPickerSheetContentComponent? + private var state: EmptyComponentState? override init(frame: CGRect) { super.init(frame: frame) @@ -52,10 +65,17 @@ private final class BirthdayPickerSheetContentComponent: Component { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + func update(component: BirthdayPickerSheetContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - self.component = component + if self.component == nil { + if case let .acceptSuggestion(birthday) = component.mode { + self.birthday = birthday + } + } + self.component = component + self.state = state + let environment = environment[EnvironmentType.self].value let sideInset: CGFloat = 16.0 @@ -66,17 +86,21 @@ private final class BirthdayPickerSheetContentComponent: Component { let contentSize = self.content.update( transition: transition, component: AnyComponent(BirthdayPickerContentComponent( + context: component.context, + mode: component.mode, theme: environment.theme, strings: environment.strings, settings: component.settings, value: self.birthday, + canHideYear: component.canHideYear, updateValue: { [weak self] value in if let self { self.birthday = value } }, dismiss: component.dismiss, - openSettings: component.openSettings + openSettings: component.openSettings, + requestHideYear: component.requestHideYear )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height) @@ -111,6 +135,16 @@ private final class BirthdayPickerSheetContentComponent: Component { transition.setFrame(view: cancelView, frame: cancelFrame) } + let buttonTitle: String + switch component.mode { + case .generic: + buttonTitle = environment.strings.Birthday_Save + case .suggest: + buttonTitle = environment.strings.SuggestBirthdate_Suggest_Action + case .acceptSuggestion: + buttonTitle = environment.strings.SuggestBirthdate_Accept_Action + } + let buttonSize = self.button.update( transition: transition, component: AnyComponent(ButtonComponent( @@ -120,7 +154,7 @@ private final class BirthdayPickerSheetContentComponent: Component { pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) ), content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent( - Text(text: environment.strings.Birthday_Save, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor) + Text(text: buttonTitle, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor) )), isEnabled: true, displaysProgress: false, @@ -166,17 +200,20 @@ private final class BirthdayPickerScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let mode: BirthdayPickerScreen.Mode let settings: Signal let openSettings: (() -> Void)? let completion: ((TelegramBirthday) -> Void)? init( context: AccountContext, + mode: BirthdayPickerScreen.Mode, settings: Signal, openSettings: (() -> Void)?, completion: ((TelegramBirthday) -> Void)? ) { self.context = context + self.mode = mode self.settings = settings self.openSettings = openSettings self.completion = completion @@ -193,6 +230,8 @@ private final class BirthdayPickerScreenComponent: Component { private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, SheetComponentEnvironment)>() private let sheetAnimateOut = ActionSlot>() + private var canHideYear = false + private var component: BirthdayPickerScreenComponent? private var environment: EnvironmentType? @@ -206,6 +245,11 @@ private final class BirthdayPickerScreenComponent: Component { private var didAppear = false func update(component: BirthdayPickerScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + if self.component == nil { + if case let .acceptSuggestion(birthday) = component.mode, let _ = birthday.year { + self.canHideYear = true + } + } self.component = component let environment = environment[ViewControllerComponentContainer.Environment.self].value @@ -235,7 +279,10 @@ private final class BirthdayPickerScreenComponent: Component { transition: transition, component: AnyComponent(SheetComponent( content: AnyComponent(BirthdayPickerSheetContentComponent( + context: component.context, + mode: component.mode, settings: component.settings, + canHideYear: self.canHideYear, openSettings: { [weak self] in guard let self, let component = self.component else { return @@ -251,7 +298,8 @@ private final class BirthdayPickerScreenComponent: Component { controller.dismiss(completion: nil) } }) - }, action: { [weak self] value in + }, + action: { [weak self] value in guard let self else { return } @@ -264,9 +312,17 @@ private final class BirthdayPickerScreenComponent: Component { } self.component?.completion?(value) }) + }, + requestHideYear: { [weak self] transition in + guard let self, let controller = self.environment?.controller() as? BirthdayPickerScreen else { + return + } + self.canHideYear = false + controller.requestLayout(forceUpdate: true, transition: transition) } )), backgroundColor: .color(environment.theme.list.plainBackgroundColor), + followContentSizeChanges: true, isScrollEnabled: false, animateOut: self.sheetAnimateOut )), @@ -297,9 +353,15 @@ private final class BirthdayPickerScreenComponent: Component { } public class BirthdayPickerScreen: ViewControllerComponentContainer { - public init(context: AccountContext, settings: Signal, openSettings: (() -> Void)?, completion: ((TelegramBirthday) -> Void)? = nil) { + public enum Mode: Equatable { + case generic + case suggest(EnginePeer.Id) + case acceptSuggestion(TelegramBirthday) + } + public init(context: AccountContext, mode: Mode = .generic, settings: Signal, openSettings: (() -> Void)?, completion: ((TelegramBirthday) -> Void)? = nil) { super.init(context: context, component: BirthdayPickerScreenComponent( context: context, + mode: mode, settings: settings, openSettings: openSettings, completion: completion diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 7689cadd94..73fec31551 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -2905,7 +2905,7 @@ public final class StoryItemSetContainerComponent: Component { } component.presentInGlobalOverlay(c, nil) }, - sendMessageAction: { [weak self] in + sendMessageAction: { [weak self] _ in guard let self else { return } diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallCameraButton.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/CallCameraButton.imageset/Contents.json index 324f180936..b6d9b3ce45 100644 --- a/submodules/TelegramUI/Images.xcassets/Call/CallCameraButton.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Call/CallCameraButton.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "ic_call_camera.pdf", + "filename" : "videocall.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallCameraButton.imageset/ic_call_camera.pdf b/submodules/TelegramUI/Images.xcassets/Call/CallCameraButton.imageset/ic_call_camera.pdf deleted file mode 100644 index 92a55e486d0abfdc8655bb3554b2a1ad768e737c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4032 zcmai%c{r49`^PO)7_yWlQtmuvM3xydwz98@vPE`g!`K^RNm;XG%bqC75+O@bWX--?U7d@hKQmX0J`3IT?+k=Muz zg1{pppB^;|%3wt5OrKqyA?`J%TStWfE}Ns;~|M`!mF#Yr0S;ze%VCi76f7tzwu zsU_j&uKG8OYqWwRzSy!0SQ9)1WiQ{I&N6*m$-bz5W1qWn4suw0qbH}y-_R1g8B$(t zr+7daIJDuv;n$NuEd9VRD?VOK)+l7-l5h>xEHCjH{!`-eb30jK9mV_=^@|@*gexp` zooJ?bYkPE11n#PHREk@Dz<8W#f2zYC8)mNLBfwuhVj56VNcDZI|+@c5_ zf?jFI>-^ad(CJX|I>jz0j^O1YF3Jc!qj;JaN9bga(3{ruT^85fp3b@BJMSMZtpwd< zzuErHK=?$c<00k=tEt0HV~QgJ z*3_h0I)E)8t?B9JNig+7VQ>JgD>S{~fc(z{zvWQ)EyvHsQT!eyeUZ8$X?0rjcu+eB zkk-Qa;xRZAU5)?UQ(3qDE}4$;Mv)ri8~P6m1CVur02bi&*>gM~<7mAQBZkOusIS%m z9d0f$bL^#;^tw^P7;SFW8)4{CmNibJj%H&mZSr`WE6Yl$?AM9Ry-v%?vAOEeF{hQl z(ONpA-7ruK(Skk%nPb7dywGfO_(*TtCc{xqdUgx?ms5_8Al)r3hN0aC<&2C+=Q=7k zmZd_lb?+pajG*2l`@u=V@|q$C>A>2j$5>hHaGur^cn*lb84GnJ%K%TThaMGoqvJTObjSm4_&}DdIwMb z)aAIdT4}nChM!Jty)bpt-+;7U#dZtE7gFwdTbmwY>7L=7v{Wj%v}8Fl>%84GHhCh* zbWgb}fMT`^T_oLCkGpR<>9+V-x`CH&L&WOt@yf7F zPei)eCAWQK6J?EETy*oM&vunM{YjpZ9p>576sfqG(Ys7BKU=`38v2JG4+5u6E+{O} z*LwAejXj-&-P`2cpMI%`|FgSQ;$HSkzSW3NX2zdVS;C{8>mrmwEfS@$;PBw#so zqD=fjS+3m9KK68h5sF4NdQ4rO%5<84Fp7An1~3FJSaZU`U5)k5rAg)92{HkH&Fu)W z+QfKF8K?`J&TgJTQ$K(_xJMrt&UJ@j5OwGc-Sd#KinD^ux-GpQwETJQG4Z{8H+8;-SB1U0g>UNE z7dnXu`<4`DwJv7H0UZnGW2;&ki54RCTqj+Vg!37Sv@;Xg^N+oVKbLf6hVfuj4(iTP z-VXlhD9gAhv^i=7eI7P^2Xn{VfNxgLkrNTR9rh4C{G`A_^s%ZhiyYV8c*!<%hXe=f ziu5OpZ%~V81=%}a@_!Wc3aUA3BjX`k2CC*5jjIb;XgxFqUG=XQpWt2z<7oE&g1&yT z5gaTSd}1;|sG~_Vk;Mw+8pd(*HX!0Aa*0!bd5(E6oT~+C9Qw$RC>+Uowao@C_T<(Z z^?E%)=*1Lny+{#06A`hDs?bY%F2c4@WoX06l$YaQ&_)cw>m0^Qc;Q2sl-4sOn*YxS`(v*@U*R+N7 zYY@ZK(R9Cbd#P+u`;)=v|0>w6VK8o1Yc>tdt1!>enW}10j?mN2%uPezL}#L-(KkNI zwi`q0lS7lM4;vP#%~!mJJUl6&G5GN6>zb+yRcGGf-olRS%I8xCQ|8;(g+4W`v)~{& zYw{_wAX$K1*<*6B1?I4YFyVg~ml9VOm+%!i`+_u0dM8(ox7)85OWPLr5Wk)#oaUHD zpO#Q;VCFf@J)BkSUTio7bAh^`T*h1|!%`*jq(YM4eNvV^a(`ww>T^YHbU@9H%eU6g z%AZpNG6fn1_yx=a(vcmq{Fxq^-I?UfIb>&*vlZ(zkQLc#?wK7dEo1rBB}YhQtYS`S z*;1k8fJ**&iE=6CQKf}b-o?tkBeuy>*7EArHr1wn8ixAP7Si_R#w$50#@VIW`Axb_ z*1I(vFdl5ALYd4G@>#Qx3qk$Gx9*ORjKQG-qpL?r&*O_En>5H$9e4~ zmLyCi7bJu%#Vik4xL19y*lB&+mg#DX3G9q?nZ{K16^ze5D=m_o^le%SJez4-G0t0e zv5vICz9CG3Qs7L=QT|a4?3-_hzNhNK1Bba5K26p1t;S6=iCz`YR*6?R(&*b*5-7ce+GE)B-5LKn z`eklslwt@{06k{;1kMNPfXtaxSY=oaAMgRSHq$rPg$YT&wimG(;$_g-)%1-}Vv%LD z<=OE*DkXt@3|+ihW9A>^?+21ZYint?)tyV|dflZP8_%oH>!;JL+payVdqMk}cDlBH zx`l*g<%~|8RUA;D%qw(^=bDHwp=k5kQIzDlj?;7TtF~LX-6T+Y_|xzKt@JkAXpCN& z9*^EMgQ5Df4?a)r*FtBCQWI12#1KUTMStSMtG22JH6W;%iqp0TWzh!dR%9Mbv$h96 zyli$PS8X84;FPzeW8Ixwe;Ir`7bkKTa>J6f(&g!dlP)g1$M_Z3?D|aRj7RVB?yudH zAX!IQKOd(?-@hVvn`M_>*-Ilv%52`fxaeowTs!rxaUsDSa6dQprB|mn9(QcgBW7aA z_}*1K-fSA7efmLf$7YOj-tB z-X4Fo5x&&$aN{Ax?}IP1-}~|T+OHNFXiK}X{<06YrQ}y@xog#Rmlqq&QPx)+IPeMB zgesXwPaDpcyXX!Ve$M|KDv*#l+wl2X^-|W`&Ud3tlkUNT^zn@COe!b11?z*mHjP|M z)!o}s@24KOk`{75*ETr?+Xrhcom)*J=|<>2>}*N!89qBSD*s46RjwsIx#P+7l}^vS z58pJe9VwYNLgc;J0x6tST>aR&CA4y%O=;|EOknZ6=w9~5^M0!stDKSdBhkLRXuha3mx(I1RWq-% z$|}kxdPCF8?N(n9irzI)cFodDoXQk!&jw}%HY`Q%#gCk*yMNn$F$h2ZY{k7{-m7x& z_H^m3#sh(KJGK<*iH+Jo@~!=>V~nu}cb26ZukG8tSDp`?*c)If;;>VF6`-)^yoXtw z&J_h4ir+6*E!`X58g(pcSYLcF>OiWz>68#zqba?gwq0)6dVo|!I;~Ts^I5l4mlU@h zHy-e0z5PpXT>9X2M3dNG&o|QsO0t9eC=E{N?zS(k0}C0d=O-RDD(;MIxX)of%?$eC zR|*PaCQ2GrH$wMvD46xmr1?d|EyJ|zcvZjaEW2y|Pg*s8ijs`W;wXb zFW{x|D@~(9uAZi*2Fe?U1!w?k0@(fvp+WR7CjOhTy#eV9I4mBe;pq=p!>A$hGSv4w zB>Paw8xBb8Ke_#HEQkNLfQsc<9}M;W zb;=!uF*F6taRhI?rw0IsNx@~MWC5E~8a{Y8EC5&3fy3Z7Vt}y^ia-nis0{xd`~8Vx zRCK3a)rizpT2u}PpVB%b1CxQl4S`go9-j699=|jg5=kxbKQ#nGmg?3&H5g2WTFZaakTTRO z%)e=}GXEvte~VM3*7cuj5!B=VhlWJ{wr3&%g?GadzF&(?@c}sMc>!q?Pfx1lG+U|H zV?7T?Ppbbv##+=4P{Lra@^CoP5spP*u#Q-myaEb^kjEl1a%cn&jaE3=9pp`< SdilN32x?`)5Qw&+4)|XZ-sYzO diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallCameraButton.imageset/videocall.pdf b/submodules/TelegramUI/Images.xcassets/Call/CallCameraButton.imageset/videocall.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0cd16e8fcb5083662168015ce1515de190fb8fd8 GIT binary patch literal 4383 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 diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallMessageButton.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/CallMessageButton.imageset/Contents.json index 9b35d685e3..1aaa094862 100644 --- a/submodules/TelegramUI/Images.xcassets/Call/CallMessageButton.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Call/CallMessageButton.imageset/Contents.json @@ -1,22 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "CallQuickMessageIcon@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "CallQuickMessageIcon@3x.png", - "scale" : "3x" + "filename" : "comment.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallMessageButton.imageset/comment.pdf b/submodules/TelegramUI/Images.xcassets/Call/CallMessageButton.imageset/comment.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c3019c741395538f398f92a867ef659102c63d19 GIT binary patch literal 4370 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 + if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface, let current = rootController.getPrivacySettings() { + settingsPromise = current + } else { + settingsPromise = Promise() + settingsPromise.set(.single(nil) |> then(self.context.engine.privacy.requestAccountPrivacySettings() |> map(Optional.init))) + } + + let controller = self.context.sharedContext.makeBirthdayAcceptSuggestionScreen(context: self.context, birthday: birthday, settings: settingsPromise, openSettings: { [weak self] in + guard let self else { + return + } + self.context.sharedContext.makeBirthdayPrivacyController(context: self.context, settings: settingsPromise, openedFromBirthdayScreen: true, present: { [weak self] c in + self?.push(c) + }) + }, completion: { [weak self] value in + guard let self else { + return + } + let _ = self.context.engine.accountData.updateBirthday(birthday: value).startStandalone() + + self.present(UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: nil, text: self.presentationData.strings.SuggestBirthdate_Accept_Added, cancel: nil, destructive: false), elevatedLayout: false, action: { _ in + return true + }), in: .current) + }) + self.push(controller) case let .suggestedProfilePhoto(image): self.chatDisplayNode.dismissInput() if let image = image { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index eba657d9d2..40fdb504a3 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -435,6 +435,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState } } } + let _ = displayBotStartPanel displayInputTextPanel = true } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 2e6bec97dd..2715308315 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3932,6 +3932,36 @@ public final class SharedAccountContextImpl: SharedAccountContext { public func makeForumSettingsScreen(context: AccountContext, peerId: EnginePeer.Id) -> ViewController { return ForumSettingsScreen(context: context, peerId: peerId) } + + public func makeBirthdayPickerScreen(context: AccountContext, settings: Promise, openSettings: @escaping (() -> Void), completion: @escaping (TelegramBirthday) -> Void) -> ViewController { + return BirthdayPickerScreen( + context: context, + mode: .generic, + settings: settings.get(), + openSettings: openSettings, + completion: completion + ) + } + + public func makeBirthdaySuggestionScreen(context: AccountContext, peerId: EnginePeer.Id, completion: @escaping (TelegramBirthday) -> Void) -> ViewController { + return BirthdayPickerScreen( + context: context, + mode: .suggest(peerId), + settings: .single(nil), + openSettings: nil, + completion: completion + ) + } + + public func makeBirthdayAcceptSuggestionScreen(context: AccountContext, birthday: TelegramBirthday, settings: Promise, openSettings: @escaping () -> Void, completion: @escaping (TelegramBirthday) -> Void) -> ViewController { + return BirthdayPickerScreen( + context: context, + mode: .acceptSuggestion(birthday), + settings: settings.get(), + openSettings: openSettings, + completion: completion + ) + } } private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {