From 1b2b7dc40366a3b90a688a8e98ba0ec4fa305315 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 1 Feb 2024 12:29:07 +0400 Subject: [PATCH] Group boosts --- .../Telegram-iOS/en.lproj/Localizable.strings | 92 +- .../Sources/AccountContext.swift | 2 +- .../Sources/PeerNameColors.swift | 15 +- .../AccountContext/Sources/Premium.swift | 43 +- .../AuthorizationSequenceController.swift | 3 +- submodules/Camera/Sources/Camera.swift | 2 +- submodules/Camera/Sources/CameraOutput.swift | 41 +- submodules/Camera/Sources/VideoRecorder.swift | 188 ++-- .../ChatListUI/Sources/ChatContextMenus.swift | 6 + .../Sources/ChatListController.swift | 11 +- .../Sources/ContactListNode.swift | 4 + .../Sources/ItemListControllerNode.swift | 8 +- .../Sources/TGMediaAvatarMenuMixin.m | 36 +- .../TelegramInitializeLegacyComponents.swift | 4 +- .../Sources/MediaPickerScreen.swift | 2 +- submodules/PeerInfoUI/BUILD | 1 + .../ChannelPermissionsController.swift | 78 +- .../Sources/ChatUnrestrictBoostersItem.swift | 342 +++++++ submodules/PremiumUI/Resources/boost.scn | Bin 0 -> 68031 bytes submodules/PremiumUI/Resources/tag.png | Bin 0 -> 4258 bytes submodules/PremiumUI/Resources/tag.scn | Bin 0 -> 58387 bytes .../PremiumUI/Sources/BadgeLabelView.swift | 21 +- .../PremiumUI/Sources/BadgeStarsView.swift | 54 + .../BoostHeaderBackgroundComponent.swift | 193 ++++ .../Sources/CreateGiveawayController.swift | 37 +- .../Sources/GiftAvatarComponent.swift | 2 +- .../Sources/GiveawayInfoController.swift | 25 +- .../Sources/PageIndicatorComponent.swift | 63 +- .../Sources/PhoneDemoComponent.swift | 8 + .../Sources/PremiumBoostLevelsScreen.swift | 932 ++++++++++++++---- .../PremiumUI/Sources/PremiumDemoScreen.swift | 128 ++- .../PremiumUI/Sources/PremiumGiftScreen.swift | 9 + .../Sources/PremiumIntroScreen.swift | 105 +- .../Sources/PremiumLimitScreen.swift | 39 +- .../Sources/PremiumLimitsListScreen.swift | 59 +- .../Sources/StoriesPageComponent.swift | 39 +- submodules/StatisticsUI/BUILD | 1 + .../StatisticsUI/Sources/BackButton.swift | 268 +++++ .../Sources/BoostHeaderItem.swift | 514 ++++++++++ .../Sources/ChannelStatsController.swift | 103 +- .../Sources/GroupStatsController.swift | 2 +- .../Sources/MessageStatsController.swift | 2 +- .../Sources/StatsOverviewItem.swift | 6 +- submodules/TelegramApi/Sources/Api0.swift | 9 +- submodules/TelegramApi/Sources/Api12.swift | 126 +-- submodules/TelegramApi/Sources/Api2.swift | 28 + submodules/TelegramApi/Sources/Api26.swift | 18 +- submodules/TelegramApi/Sources/Api3.swift | 28 +- submodules/TelegramApi/Sources/Api32.swift | 47 +- .../Sources/ManagedAudioSession.swift | 43 +- .../Sources/Account/AccountManager.swift | 1 + .../ApiUtils/StoreMessage_Telegram.swift | 14 +- .../TelegramCore/Sources/Authorization.swift | 8 +- .../Sources/State/ApplyUpdateMessage.swift | 2 +- ...anagedConsumePersonalMessagesActions.swift | 2 +- .../State/ManagedPeerColorUpdates.swift | 16 +- .../Sources/State/SavedMessageTags.swift | 2 +- .../Sources/State/Serialization.swift | 2 +- .../Sources/State/UpdateMessageService.swift | 4 +- .../Sources/State/UpdatesApiUtils.swift | 12 +- .../SyncCore_BoostCountMessageAttribute.swift | 20 + .../SyncCore/SyncCore_CachedChannelData.swift | 106 +- .../TelegramEngine/Data/PeersData.swift | 27 + .../Peers/ChannelAdminEventLogs.swift | 3 + .../Peers/PeerSpecificStickerPack.swift | 32 + .../Peers/TelegramEnginePeers.swift | 8 + .../Peers/UpdateCachedPeerData.swift | 22 +- .../Peers/UpdateGroupSpecificStickerset.swift | 38 + .../Stickers/SearchStickers.swift | 33 + .../Stickers/TelegramEngineStickers.swift | 4 + .../TelegramNotices/Sources/Notices.swift | 78 ++ .../Sources/ServiceMessageStrings.swift | 7 +- .../CameraScreen/Sources/CameraScreen.swift | 2 +- ...ChatMessageAttachedContentButtonNode.swift | 8 +- .../ChatMessageAttachedContentNode.swift | 1 + .../Sources/ChatMessageBubbleItemNode.swift | 96 +- .../ChatMessageContactBubbleContentNode.swift | 4 +- ...ChatMessageGiveawayBubbleContentNode.swift | 6 +- ...tMessageUnsupportedBubbleContentNode.swift | 2 +- .../ChatMessageWebpageBubbleContentNode.swift | 2 + .../ChatRecentActionsControllerNode.swift | 2 +- .../ChatRecentActionsHistoryTransition.swift | 29 + .../Sources/ChatControllerInteraction.swift | 4 +- .../Sources/ChatEntityKeyboardInputNode.swift | 13 +- .../Sources/EmojiStatusComponent.swift | 2 + .../Sources/EmojiPagerContentSignals.swift | 92 +- .../GroupStickerPackSetupController/BUILD | 27 + .../Info.plist | 22 + .../Sources/GroupStickerPackCurrentItem.swift | 37 +- .../GroupStickerPackSetupController.swift | 106 +- .../Sources/MediaEditorScreen.swift | 2 +- .../Sources/PeerInfoScreen.swift | 52 +- .../Settings/PeerNameColorScreen/BUILD | 1 + .../Sources/ChannelAppearanceScreen.swift | 160 ++- .../Sources/SettingsThemeWallpaperNode.swift | 1 + .../Sources/ShareWithPeersScreen.swift | 18 +- .../Sources/SliderContextItem.swift | 2 + .../Sources/VideoMessageCameraScreen.swift | 15 +- .../AudioTranscription.imageset/Contents.json | 12 + .../voicetotext_30.pdf | 140 +++ .../EmojiPack.imageset/Contents.json | 12 + .../EmojiPack.imageset/customemoji_30.pdf | 207 ++++ .../Boosts/Boost.imageset/Contents.json | 12 + .../Boosts/Boost.imageset/boost_30.pdf | 116 +++ .../Premium/Boosts/Contents.json | 9 + .../Boosts/Features.imageset/Contents.json | 12 + .../Boosts/Features.imageset/about_30.pdf | 85 ++ .../Boosts/Giveaway.imageset/Contents.json | 12 + .../Giveaway.imageset/giveaway_30 (2).pdf | 186 ++++ .../Perk/MessageTags.imageset/Contents.json | 12 + .../Perk/MessageTags.imageset/tag_30 (2).pdf | 164 +++ .../Stories/Quality.imageset/Contents.json | 12 + .../Stories/Quality.imageset/hd_30.pdf | 161 +++ .../TelegramUI/Sources/ChatController.swift | 26 +- .../Sources/ManagedAudioRecorder.swift | 21 +- .../TelegramUI/Sources/OpenResolvedUrl.swift | 30 +- .../OverlayAudioPlayerControllerNode.swift | 2 +- .../Sources/SharedAccountContext.swift | 12 +- submodules/TgVoipWebrtc/tgcalls | 2 +- .../WebUI/Sources/WebAppController.swift | 3 +- 120 files changed, 5295 insertions(+), 887 deletions(-) create mode 100644 submodules/PeerInfoUI/Sources/ChatUnrestrictBoostersItem.swift create mode 100644 submodules/PremiumUI/Resources/boost.scn create mode 100644 submodules/PremiumUI/Resources/tag.png create mode 100644 submodules/PremiumUI/Resources/tag.scn create mode 100644 submodules/PremiumUI/Sources/BoostHeaderBackgroundComponent.swift create mode 100644 submodules/StatisticsUI/Sources/BackButton.swift create mode 100644 submodules/StatisticsUI/Sources/BoostHeaderItem.swift create mode 100644 submodules/TelegramCore/Sources/SyncCore/SyncCore_BoostCountMessageAttribute.swift create mode 100644 submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD create mode 100644 submodules/TelegramUI/Components/GroupStickerPackSetupController/Info.plist rename submodules/{PeerInfoUI => TelegramUI/Components/GroupStickerPackSetupController}/Sources/GroupStickerPackCurrentItem.swift (91%) rename submodules/{PeerInfoUI => TelegramUI/Components/GroupStickerPackSetupController}/Sources/GroupStickerPackSetupController.swift (84%) create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/AudioTranscription.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/AudioTranscription.imageset/voicetotext_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/EmojiPack.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/EmojiPack.imageset/customemoji_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Boosts/Boost.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Boosts/Boost.imageset/boost_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Boosts/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Boosts/Features.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Boosts/Features.imageset/about_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Boosts/Giveaway.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Boosts/Giveaway.imageset/giveaway_30 (2).pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Perk/MessageTags.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Perk/MessageTags.imageset/tag_30 (2).pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Stories/Quality.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Stories/Quality.imageset/hd_30.pdf diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 2668a6c66f..c281d13c72 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10474,12 +10474,6 @@ Sorry for the inconvenience."; "Channel.Info.Stats" = "Statistics and Boosts"; -"BoostGift.StartConfirmation.Title" = "Start Giveaway"; -"BoostGift.StartConfirmation.Text" = "Are you sure you want to start giveaway now?"; -"BoostGift.StartConfirmation.Start" = "Start"; - -"Channel.Info.Stats" = "Statistics and Boosts"; - "Conversation.FreeTranscriptionLimitTooltip_1" = "You have **%@** free voice transcription left this week."; "Conversation.FreeTranscriptionLimitTooltip_any" = "You have **%@** free voice transcriptions left this week."; @@ -10988,6 +10982,8 @@ Sorry for the inconvenience."; "ChannelBoost.MoreBoostsNeeded.Boosts_any" = "**%@** more boosts"; "ChannelBoost.MaxLevelReached.Text" = "**%1$@** reached **Level %2$@**."; +"ChannelBoost.MoreBoostsNeeded.Boosted.Text" = "%@ needed to unlock new features."; + "ContactList.Context.Delete" = "Delete Contact"; "ContactList.Context.Select" = "Select"; @@ -11002,3 +10998,87 @@ Sorry for the inconvenience."; "Login.Announce.Info" = "Notify people on Telegram who know my phone number that I signed up."; "Login.Announce.Notify" = "Notify"; "Login.Announce.DontNotify" = "Do Not Notify"; + +"ChatList.ContextOpenGroup" = "Open Group"; + +"GroupBoost.Title.Other" = "Help Upgrade This Group"; +"GroupBoost.Title.Current" = "Boost Group"; + +"GroupBoost.EnableStoriesText" = "Your group needs %1$@ to enable posting stories.\n\nAsk your **Premium** members to boost your group with this link:"; +"GroupBoost.IncreaseLimitText" = "Your group needs %1$@ to post %2$@.\n\nAsk your **Premium** members to boost your group with this link:"; + +"BoostGift.Group.Description" = "Get more boosts for your group by gifting\nPremium to your subscribers."; + +"BoostGift.Group.AllMembers" = "All members"; +"BoostGift.Group.OnlyNewMembers" = "Only new members"; + +"BoostGift.Group.LimitMembersInfo" = "Choose if you want to limit the giveaway only to those who joined the group after the giveaway started."; +"BoostGift.Group.DateInfo" = "Choose when %1$@ of your group will be randomly selected to receive Telegram Premium."; +"BoostGift.Group.DateInfoMembers_1" = "%@ member"; +"BoostGift.Group.DateInfoMembers_any" = "%@ members"; + +"BoostGift.Group.GiveawayCreated.Text" = "Check your group's [Boosts]() to see how this giveaway boosted your group."; +"BoostGift.Group.PremiumGifted.Text" = "Check your groups's [Boosts]() to see how gifts boosted your group."; + +"BoostGift.Group.Members.Title" = "Gift Premium"; +"BoostGift.Group.Members.Subtitle" = "select up to %@ members"; +"BoostGift.Group.Members.SectionTitle" = "MEMBERS"; +"BoostGift.Group.Members.Joined" = "joined %@"; +"BoostGift.Group.Members.Search" = "Search Members"; +"BoostGift.Group.Members.MaximumReached" = "You can select maximum %@ members."; +"BoostGift.Group.Members.Save" = "Save Recipients"; + +"ChannelBoost.Table.Group.ProfileColor_1" = "%@ Color for Group Cover"; +"ChannelBoost.Table.Group.ProfileColor_any" = "%@ Colors for Group Cover"; +"ChannelBoost.Table.Group.ProfileLogo" = "Custom Logo for Group Cover"; +"ChannelBoost.Table.Group.Wallpaper_1" = "%@ Group Background"; +"ChannelBoost.Table.Group.Wallpaper_any" = "%@ Group Backgrounds"; +"ChannelBoost.Table.Group.CustomWallpaper" = "Custom Group Background"; + +"Premium.Group.BoostByGiftDescription" = "Boost your group by gifting your subscribers Telegram Premium. [Get boosts >]()"; + +"Conversation.BoostGroup" = "BOOST"; + +"Premium.Stories.Quality.Title" = "Higher Quality"; +"Premium.Stories.Quality.Text" = "View video stories in double the resolution."; + +"Premium.MessageTags" = "Tags for Messages"; +"Premium.MessageTagsInfo" = "Organize your Saved Messages with tags for quicker access."; +"Premium.MessageTags.Proceed" = "About Telegram Premium"; + +"Notification.GiveawayStartedGroup" = "%1$@ just started a giveaway of Telegram Premium subscriptions for its members."; + +"Chat.Giveaway.Message.Group.Participants" = "All subscribers of this group:"; + +"Chat.Giveaway.Info.Group.RandomMembers_1" = "**%@** random member"; +"Chat.Giveaway.Info.Group.RandomMembers_any" = "**%@** random members"; + +"Chat.Giveaway.Info.Group.OngoingIntro" = "The giveaway is sponsored by the admins of **%1$@**, who acquired %2$@ for %3$@ for its members."; +"Chat.Giveaway.Info.Group.EndedIntro" = "The giveaway was sponsored by the admins of **%1$@**, who acquired %2$@ for %3$@ for its members."; + +"ChannelBoost.EnableGroupEmojiPackLevelText" = "Your group needs **Level %1$@** to set emoji pack."; + +"Premium.LastSeen" = "Last Seen Times"; +"Premium.LastSeenInfo" = "View the last seen and read times of others even if you hide yours."; +"Premium.LastSeen.Proceed" = "About Telegram Premium"; + +"Premium.MessagePrivacy" = "Message Privacy"; +"Premium.MessagePrivacyInfo" = "Restrict people you don't know from sending you messages."; +"Premium.MessagePrivacy.Proceed" = "About Telegram Premium"; + +"BoostGift.GroupsAndChannelsTitle" = "INCLUDED GROUPS AND CHANNELS"; +"BoostGift.GroupsAndChannelsInfo" = "Choose additional groups or channels users need to join to take part in the giveaway."; +"BoostGift.AddGroupOrChannel" = "Add Group or Channel"; + +"BoostGift.ChannelsAndGroupsTitle" = "INCLUDED CHANNELS AND GROUPS"; +"BoostGift.ChannelsAndGroupsInfo" = "Choose additional channels or groups users need to join to take part in the giveaway."; +"BoostGift.AddChannelOrGroup" = "Add Channel or Group"; + +"BoostGift.GroupBoosts_1" = "this group will receive %@ boost"; +"BoostGift.GroupBoosts_any" = "this group will receive %@ boosts"; + +"Stats.Boosts.PremiumMembers" = "Premium Members"; + +"GroupInfo.Permissions.DontRestrictBoosters" = "Do Not Restrict Boosters"; +"GroupInfo.Permissions.DontRestrictBoostersInfo" = "Choose how many boosts a user must give to the group to bypass restrictions on sending messages."; +"GroupInfo.Permissions.DontRestrictBoostersEnableInfo" = "Turn this on to always allow users who boosted your group to send messages and media."; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index f197f5ab4a..4b07814010 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -739,7 +739,7 @@ public enum InstalledStickerPacksControllerMode { case modal case masks case emoji - case groupEmoji(selectedPack: Int32?, completion: (Int32?) -> Void) + case groupEmoji(selectedPack: StickerPackCollectionInfo?, completion: (StickerPackCollectionInfo?) -> Void) } public let defaultContactLabel: String = "_$!!$_" diff --git a/submodules/AccountContext/Sources/PeerNameColors.swift b/submodules/AccountContext/Sources/PeerNameColors.swift index e8bce68a49..2b66687231 100644 --- a/submodules/AccountContext/Sources/PeerNameColors.swift +++ b/submodules/AccountContext/Sources/PeerNameColors.swift @@ -87,7 +87,8 @@ public class PeerNameColors: Equatable { profileStoryColors: [:], profileStoryDarkColors: [:], profileDisplayOrder: [], - nameColorsChannelMinRequiredBoostLevel: [:] + nameColorsChannelMinRequiredBoostLevel: [:], + nameColorsGroupMinRequiredBoostLevel: [:] ) } @@ -104,6 +105,7 @@ public class PeerNameColors: Equatable { public let profileDisplayOrder: [Int32] public let nameColorsChannelMinRequiredBoostLevel: [Int32: Int32] + public let nameColorsGroupMinRequiredBoostLevel: [Int32: Int32] public func get(_ color: PeerNameColor, dark: Bool = false) -> Colors { if dark, let colors = self.darkColors[color.rawValue] { @@ -155,7 +157,8 @@ public class PeerNameColors: Equatable { profileStoryColors: [Int32: Colors], profileStoryDarkColors: [Int32: Colors], profileDisplayOrder: [Int32], - nameColorsChannelMinRequiredBoostLevel: [Int32: Int32] + nameColorsChannelMinRequiredBoostLevel: [Int32: Int32], + nameColorsGroupMinRequiredBoostLevel: [Int32: Int32] ) { self.colors = colors self.darkColors = darkColors @@ -168,6 +171,7 @@ public class PeerNameColors: Equatable { self.profileStoryDarkColors = profileStoryDarkColors self.profileDisplayOrder = profileDisplayOrder self.nameColorsChannelMinRequiredBoostLevel = nameColorsChannelMinRequiredBoostLevel + self.nameColorsGroupMinRequiredBoostLevel = nameColorsGroupMinRequiredBoostLevel } public static func with(availableReplyColors: EngineAvailableColorOptions, availableProfileColors: EngineAvailableColorOptions) -> PeerNameColors { @@ -183,12 +187,16 @@ public class PeerNameColors: Equatable { var profileDisplayOrder: [Int32] = [] var nameColorsChannelMinRequiredBoostLevel: [Int32: Int32] = [:] + var nameColorsGroupMinRequiredBoostLevel: [Int32: Int32] = [:] if !availableReplyColors.options.isEmpty { for option in availableReplyColors.options { if let requiredChannelMinBoostLevel = option.value.requiredChannelMinBoostLevel { nameColorsChannelMinRequiredBoostLevel[option.key] = requiredChannelMinBoostLevel } + if let requiredGroupMinBoostLevel = option.value.requiredGroupMinBoostLevel { + nameColorsGroupMinRequiredBoostLevel[option.key] = requiredGroupMinBoostLevel + } if let parsedLight = PeerNameColors.Colors(colors: option.value.light.background) { colors[option.key] = parsedLight @@ -249,7 +257,8 @@ public class PeerNameColors: Equatable { profileStoryColors: profileStoryColors, profileStoryDarkColors: profileStoryDarkColors, profileDisplayOrder: profileDisplayOrder, - nameColorsChannelMinRequiredBoostLevel: nameColorsChannelMinRequiredBoostLevel + nameColorsChannelMinRequiredBoostLevel: nameColorsChannelMinRequiredBoostLevel, + nameColorsGroupMinRequiredBoostLevel: nameColorsGroupMinRequiredBoostLevel ) } diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index f4dbb4f557..09ee8ce89a 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -30,12 +30,14 @@ public enum PremiumIntroSource { case storiesFormatting case storiesExpirationDurations case storiesSuggestedReactions + case storiesHigherQuality case channelBoost(EnginePeer.Id) case nameColor case similarChannels case wallpapers case presence case readTime + case messageTags } public enum PremiumGiftSource: Equatable { @@ -65,6 +67,9 @@ public enum PremiumDemoSubject { case stories case colors case wallpapers + case messageTags + case lastSeen + case messagePrivacy } public enum PremiumLimitSubject { @@ -103,7 +108,13 @@ public struct PremiumConfiguration { minChannelProfileIconLevel: 7, minChannelEmojiStatusLevel: 8, minChannelWallpaperLevel: 9, - minChannelCustomWallpaperLevel: 10 + minChannelCustomWallpaperLevel: 10, + minGroupProfileIconLevel: 7, + minGroupEmojiStatusLevel: 8, + minGroupWallpaperLevel: 9, + minGroupCustomWallpaperLevel: 9, + minGroupEmojiPackLevel: 9, + minGroupAudioTranscriptionLevel: 9 ) } @@ -122,6 +133,13 @@ public struct PremiumConfiguration { public let minChannelWallpaperLevel: Int32 public let minChannelCustomWallpaperLevel: Int32 + public let minGroupProfileIconLevel: Int32 + public let minGroupEmojiStatusLevel: Int32 + public let minGroupWallpaperLevel: Int32 + public let minGroupCustomWallpaperLevel: Int32 + public let minGroupEmojiPackLevel: Int32 + public let minGroupAudioTranscriptionLevel: Int32 + fileprivate init( isPremiumDisabled: Bool, showPremiumGiftInAttachMenu: Bool, @@ -136,8 +154,13 @@ public struct PremiumConfiguration { minChannelProfileIconLevel: Int32, minChannelEmojiStatusLevel: Int32, minChannelWallpaperLevel: Int32, - minChannelCustomWallpaperLevel: Int32 - + minChannelCustomWallpaperLevel: Int32, + minGroupProfileIconLevel: Int32, + minGroupEmojiStatusLevel: Int32, + minGroupWallpaperLevel: Int32, + minGroupCustomWallpaperLevel: Int32, + minGroupEmojiPackLevel: Int32, + minGroupAudioTranscriptionLevel: Int32 ) { self.isPremiumDisabled = isPremiumDisabled self.showPremiumGiftInAttachMenu = showPremiumGiftInAttachMenu @@ -153,6 +176,12 @@ public struct PremiumConfiguration { self.minChannelEmojiStatusLevel = minChannelEmojiStatusLevel self.minChannelWallpaperLevel = minChannelWallpaperLevel self.minChannelCustomWallpaperLevel = minChannelCustomWallpaperLevel + self.minGroupProfileIconLevel = minGroupProfileIconLevel + self.minGroupEmojiStatusLevel = minGroupEmojiStatusLevel + self.minGroupWallpaperLevel = minGroupWallpaperLevel + self.minGroupCustomWallpaperLevel = minGroupCustomWallpaperLevel + self.minGroupEmojiPackLevel = minGroupEmojiPackLevel + self.minGroupAudioTranscriptionLevel = minGroupAudioTranscriptionLevel } public static func with(appConfiguration: AppConfiguration) -> PremiumConfiguration { @@ -175,7 +204,13 @@ public struct PremiumConfiguration { minChannelProfileIconLevel: get(data["channel_profile_bg_icon_level_min"]) ?? defaultValue.minChannelProfileIconLevel, minChannelEmojiStatusLevel: get(data["channel_emoji_status_level_min"]) ?? defaultValue.minChannelEmojiStatusLevel, minChannelWallpaperLevel: get(data["channel_wallpaper_level_min"]) ?? defaultValue.minChannelWallpaperLevel, - minChannelCustomWallpaperLevel: get(data["channel_custom_wallpaper_level_min"]) ?? defaultValue.minChannelCustomWallpaperLevel + minChannelCustomWallpaperLevel: get(data["channel_custom_wallpaper_level_min"]) ?? defaultValue.minChannelCustomWallpaperLevel, + minGroupProfileIconLevel: get(data["group_profile_bg_icon_level_min "]) ?? defaultValue.minGroupProfileIconLevel, + minGroupEmojiStatusLevel: get(data["group_emoji_status_level_min"]) ?? defaultValue.minGroupEmojiStatusLevel, + minGroupWallpaperLevel: get(data["group_wallpaper_level_min"]) ?? defaultValue.minGroupWallpaperLevel, + minGroupCustomWallpaperLevel: get(data["group_custom_wallpaper_level_min"]) ?? defaultValue.minGroupCustomWallpaperLevel, + minGroupEmojiPackLevel: get(data["group_emoji_stickers_level_min"]) ?? defaultValue.minGroupEmojiPackLevel, + minGroupAudioTranscriptionLevel: get(data["group_transcribe_level_min"]) ?? defaultValue.minGroupAudioTranscriptionLevel ) } else { return defaultValue diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift index 976d425dd0..1acff73a7e 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift @@ -1097,8 +1097,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth avatarVideo = nil } - //TODO: pass signup announcement - strongSelf.actionDisposable.set((signUpWithName(accountManager: strongSelf.sharedContext.accountManager, account: strongSelf.account, firstName: firstName, lastName: lastName, avatarData: avatarData, avatarVideo: avatarVideo, videoStartTimestamp: videoStartTimestamp, forcedPasswordSetupNotice: { value in + strongSelf.actionDisposable.set((signUpWithName(accountManager: strongSelf.sharedContext.accountManager, account: strongSelf.account, firstName: firstName, lastName: lastName, avatarData: avatarData, avatarVideo: avatarVideo, videoStartTimestamp: videoStartTimestamp, disableJoinNotifications: !announceSignUp, forcedPasswordSetupNotice: { value in guard let entry = CodableEntry(ApplicationSpecificCounterNotice(value: value)) else { return nil } diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index 609f9c2608..9d0c54d93c 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -85,7 +85,7 @@ final class CameraDeviceContext { } private func maxDimensions(additional: Bool, preferWide: Bool) -> CMVideoDimensions { - if self.isRoundVideo && !Camera.isDualCameraSupported { + if self.isRoundVideo && self.exclusive { return CMVideoDimensions(width: 640, height: 480) } else { if additional || preferWide { diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index ab889bf248..6f5c21942a 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -95,7 +95,9 @@ final class CameraOutput: NSObject { private var roundVideoFilter: CameraRoundVideoFilter? private let semaphore = DispatchSemaphore(value: 1) - private let queue = DispatchQueue(label: "") + private let videoQueue = DispatchQueue(label: "", qos: .userInitiated) + private let audioQueue = DispatchQueue(label: "") + private let metadataQueue = DispatchQueue(label: "") private var photoCaptureRequests: [Int64: PhotoCaptureContext] = [:] @@ -114,7 +116,7 @@ final class CameraOutput: NSObject { self.isVideoMessage = use32BGRA super.init() - + if #available(iOS 13.0, *) { self.photoOutput.maxPhotoQualityPrioritization = .balanced } @@ -135,13 +137,13 @@ final class CameraOutput: NSObject { } else { session.session.addOutput(self.videoOutput) } - self.videoOutput.setSampleBufferDelegate(self, queue: self.queue) + self.videoOutput.setSampleBufferDelegate(self, queue: self.videoQueue) } else { Logger.shared.log("Camera", "Can't add video output") } if audio, session.session.canAddOutput(self.audioOutput) { session.session.addOutput(self.audioOutput) - self.audioOutput.setSampleBufferDelegate(self, queue: self.queue) + self.audioOutput.setSampleBufferDelegate(self, queue: self.audioQueue) } if photo, session.session.canAddOutput(self.photoOutput) { if session.hasMultiCam { @@ -305,6 +307,8 @@ final class CameraOutput: NSObject { return .complete() } + Logger.shared.log("CameraOutput", "startRecording") + self.currentMode = mode self.lastSampleTimestamp = nil self.captureOrientation = orientation @@ -449,18 +453,19 @@ final class CameraOutput: NSObject { transitionFactor = 1.0 - max(0.0, (currentTimestamp - self.lastSwitchTimestamp) / duration) } } - if let processedSampleBuffer = self.processRoundVideoSampleBuffer(sampleBuffer, additional: fromAdditionalOutput, transitionFactor: transitionFactor) { - let presentationTime = CMSampleBufferGetPresentationTimeStamp(processedSampleBuffer) - if let lastSampleTimestamp = self.lastSampleTimestamp, lastSampleTimestamp > presentationTime { - - } else { - if (transitionFactor == 1.0 && fromAdditionalOutput) || (transitionFactor == 0.0 && !fromAdditionalOutput) || (transitionFactor > 0.0 && transitionFactor < 1.0) { + + if (transitionFactor == 1.0 && fromAdditionalOutput) + || (transitionFactor == 0.0 && !fromAdditionalOutput) + || (transitionFactor > 0.0 && transitionFactor < 1.0) { + if let processedSampleBuffer = self.processRoundVideoSampleBuffer(sampleBuffer, additional: fromAdditionalOutput, transitionFactor: transitionFactor) { + let presentationTime = CMSampleBufferGetPresentationTimeStamp(processedSampleBuffer) + if let lastSampleTimestamp = self.lastSampleTimestamp, lastSampleTimestamp > presentationTime { + + } else { videoRecorder.appendSampleBuffer(processedSampleBuffer) self.lastSampleTimestamp = presentationTime } } - } else { - videoRecorder.appendSampleBuffer(sampleBuffer) } } else { var additional = self.currentPosition == .front @@ -518,7 +523,7 @@ final class CameraOutput: NSObject { return nil } self.semaphore.wait() - + let mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription) let extensions = CMFormatDescriptionGetExtensions(formatDescription) as! [String: Any] @@ -528,6 +533,7 @@ final class CameraOutput: NSObject { var newFormatDescription: CMFormatDescription? var status = CMVideoFormatDescriptionCreate(allocator: nil, codecType: mediaSubType, width: videoMessageDimensions.width, height: videoMessageDimensions.height, extensions: updatedExtensions as CFDictionary, formatDescriptionOut: &newFormatDescription) guard status == noErr, let newFormatDescription else { + self.semaphore.signal() return nil } @@ -539,8 +545,9 @@ final class CameraOutput: NSObject { self.roundVideoFilter = filter } if !filter.isPrepared { - filter.prepare(with: newFormatDescription, outputRetainedBufferCountHint: 3) + filter.prepare(with: newFormatDescription, outputRetainedBufferCountHint: 4) } + guard let newPixelBuffer = filter.render(pixelBuffer: videoPixelBuffer, additional: additional, captureOrientation: self.captureOrientation, transitionFactor: transitionFactor) else { self.semaphore.signal() return nil @@ -592,7 +599,7 @@ extension CameraOutput: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureA guard CMSampleBufferDataIsReady(sampleBuffer) else { return } - + if let videoPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { self.processSampleBuffer?(sampleBuffer, videoPixelBuffer, connection) } else { @@ -607,7 +614,9 @@ extension CameraOutput: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureA } func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { - + if #available(iOS 13.0, *) { + Logger.shared.log("VideoRecorder", "Dropped sample buffer \(sampleBuffer.attachments)") + } } } diff --git a/submodules/Camera/Sources/VideoRecorder.swift b/submodules/Camera/Sources/VideoRecorder.swift index 1d5f9352cc..e785db1834 100644 --- a/submodules/Camera/Sources/VideoRecorder.swift +++ b/submodules/Camera/Sources/VideoRecorder.swift @@ -40,11 +40,12 @@ private final class VideoRecorderImpl { private var pendingAudioSampleBuffers: [CMSampleBuffer] = [] - private var _duration: CMTime = .zero + private var _duration = Atomic(value: .zero) public var duration: CMTime { - self.queue.sync { _duration } + return self._duration.with { $0 } } + private var startedSession = false private var lastVideoSampleTime: CMTime = .invalid private var recordingStartSampleTime: CMTime = .invalid private var recordingStopSampleTime: CMTime = .invalid @@ -59,7 +60,11 @@ private final class VideoRecorderImpl { private let error = Atomic(value: nil) - private var stopped = false + private var _stopped = Atomic(value: false) + private var stopped: Bool { + return self._stopped.with { $0 } + } + private var hasAllVideoBuffers = false private var hasAllAudioBuffers = false @@ -113,20 +118,21 @@ private final class VideoRecorderImpl { } } + + private var previousPresentationTime: Double? + private var previousAppendTime: Double? + public func appendVideoSampleBuffer(_ sampleBuffer: CMSampleBuffer) { - if let _ = self.hasError() { - return - } - - guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer), CMFormatDescriptionGetMediaType(formatDescription) == kCMMediaType_Video else { - return - } - - let presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) self.queue.async { - guard !self.stopped && self.error.with({ $0 }) == nil else { + guard self.hasError() == nil && !self.stopped else { return } + + guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer), CMFormatDescriptionGetMediaType(formatDescription) == kCMMediaType_Video else { + return + } + let presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) + var failed = false if self.videoInput == nil { Logger.shared.log("VideoRecorder", "Try adding video input") @@ -159,36 +165,56 @@ private final class VideoRecorderImpl { return } if self.videoInput != nil && (self.audioInput != nil || !self.configuration.hasAudio) { + print("startWriting") + let start = CACurrentMediaTime() if !self.assetWriter.startWriting() { if let error = self.assetWriter.error { self.transitionToFailedStatus(error: .avError(error)) - return } } - - self.assetWriter.startSession(atSourceTime: presentationTime) - self.recordingStartSampleTime = presentationTime - self.lastVideoSampleTime = presentationTime + print("started In \(CACurrentMediaTime() - start)") + return } + } else if self.assetWriter.status == .writing && !self.startedSession { + print("Started session at \(presentationTime)") + self.assetWriter.startSession(atSourceTime: presentationTime) + self.recordingStartSampleTime = presentationTime + self.lastVideoSampleTime = presentationTime + self.startedSession = true } if self.recordingStartSampleTime == .invalid || sampleBuffer.presentationTimestamp < self.recordingStartSampleTime { return } - if self.assetWriter.status == .writing { + if self.assetWriter.status == .writing && self.startedSession { if self.recordingStopSampleTime != .invalid && sampleBuffer.presentationTimestamp > self.recordingStopSampleTime { self.hasAllVideoBuffers = true self.maybeFinish() return } - - if let videoInput = self.videoInput, videoInput.isReadyForMoreMediaData { + + if let videoInput = self.videoInput { + while (!videoInput.isReadyForMoreMediaData) + { + let maxDate = Date(timeIntervalSinceNow: 0.05) + RunLoop.current.run(until: maxDate) + } + } + + if let videoInput = self.videoInput { + let time = CACurrentMediaTime() + if let previousPresentationTime = self.previousPresentationTime, let previousAppendTime = self.previousAppendTime { + print("appending \(presentationTime.seconds) (\(presentationTime.seconds - previousPresentationTime) ) on \(time) (\(time - previousAppendTime)") + } + self.previousPresentationTime = presentationTime.seconds + self.previousAppendTime = time + if videoInput.append(sampleBuffer) { self.lastVideoSampleTime = presentationTime let startTime = self.recordingStartSampleTime let duration = presentationTime - startTime - self._duration = duration + let _ = self._duration.modify { _ in return duration } } if !self.savedTransitionImage, let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { @@ -220,16 +246,12 @@ private final class VideoRecorderImpl { } public func appendAudioSampleBuffer(_ sampleBuffer: CMSampleBuffer) { - if let _ = self.hasError() { - return - } - - guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer), CMFormatDescriptionGetMediaType(formatDescription) == kCMMediaType_Audio else { - return - } - self.queue.async { - guard !self.stopped && self.error.with({ $0 }) == nil else { + guard self.hasError() == nil && !self.stopped else { + return + } + + guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer), CMFormatDescriptionGetMediaType(formatDescription) == kCMMediaType_Audio else { return } @@ -274,7 +296,7 @@ private final class VideoRecorderImpl { return } - if self.recordingStartSampleTime != .invalid { //self.assetWriter.status == .writing { + if self.recordingStartSampleTime != .invalid { if sampleBuffer.presentationTimestamp < self.recordingStartSampleTime { return } @@ -304,7 +326,7 @@ private final class VideoRecorderImpl { } return } - self.stopped = true + let _ = self._stopped.modify { _ in return true } self.pendingAudioSampleBuffers = [] if self.assetWriter.status == .writing { self.assetWriter.cancelWriting() @@ -318,7 +340,7 @@ private final class VideoRecorderImpl { } public var isRecording: Bool { - self.queue.sync { !(self.hasAllVideoBuffers && self.hasAllAudioBuffers) } + return !self.stopped } public func stopRecording() { @@ -334,60 +356,58 @@ private final class VideoRecorderImpl { } } - public func maybeFinish() { - self.queue.async { - guard self.hasAllVideoBuffers && self.hasAllVideoBuffers else { - return - } - self.stopped = true - self.finish() + private func maybeFinish() { + dispatchPrecondition(condition: .onQueue(self.queue)) + guard self.hasAllVideoBuffers && self.hasAllVideoBuffers && !self.stopped else { + return } + let _ = self._stopped.modify { _ in return true } + self.finish() } - public func finish() { - self.queue.async { - let completion = self.completion - if self.recordingStopSampleTime == .invalid { - DispatchQueue.main.async { - completion(false, nil, nil) - } - return + private func finish() { + dispatchPrecondition(condition: .onQueue(self.queue)) + let completion = self.completion + if self.recordingStopSampleTime == .invalid { + DispatchQueue.main.async { + completion(false, nil, nil) } - - if let _ = self.error.with({ $0 }) { - DispatchQueue.main.async { - completion(false, nil, nil) - } - return + return + } + + if let _ = self.error.with({ $0 }) { + DispatchQueue.main.async { + completion(false, nil, nil) } - - if !self.tryAppendingPendingAudioBuffers() { - DispatchQueue.main.async { - completion(false, nil, nil) - } - return + return + } + + if !self.tryAppendingPendingAudioBuffers() { + DispatchQueue.main.async { + completion(false, nil, nil) } - - if self.assetWriter.status == .writing { - self.assetWriter.finishWriting { - if let _ = self.assetWriter.error { - DispatchQueue.main.async { - completion(false, nil, nil) - } - } else { - DispatchQueue.main.async { - completion(true, self.transitionImage, self.positionChangeTimestamps) - } + return + } + + if self.assetWriter.status == .writing { + self.assetWriter.finishWriting { + if let _ = self.assetWriter.error { + DispatchQueue.main.async { + completion(false, nil, nil) + } + } else { + DispatchQueue.main.async { + completion(true, self.transitionImage, self.positionChangeTimestamps) } } - } else if let _ = self.assetWriter.error { - DispatchQueue.main.async { - completion(false, nil, nil) - } - } else { - DispatchQueue.main.async { - completion(false, nil, nil) - } + } + } else if let _ = self.assetWriter.error { + DispatchQueue.main.async { + completion(false, nil, nil) + } + } else { + DispatchQueue.main.async { + completion(false, nil, nil) } } } @@ -423,7 +443,13 @@ private final class VideoRecorderImpl { } private func internalAppendAudioSampleBuffer(_ sampleBuffer: CMSampleBuffer) -> Bool { - if let audioInput = self.audioInput, audioInput.isReadyForMoreMediaData { + if self.startedSession, let audioInput = self.audioInput { + while (!audioInput.isReadyForMoreMediaData) + { + let maxDate = Date(timeIntervalSinceNow: 0.05) + RunLoop.current.run(until: maxDate) + } + if !audioInput.append(sampleBuffer) { if let _ = self.assetWriter.error { return false diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index a1b840bd7d..4573379393 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -878,9 +878,15 @@ public func savedMessagesPeerMenuItems(context: AccountContext, threadId: Int64, |> deliverOnMainQueue).startStandalone(error: { error in switch error { case let .limitReached(count): + var replaceImpl: ((ViewController) -> Void)? let controller = PremiumLimitScreen(context: context, subject: .pinnedSavedPeers, count: Int32(count), action: { + let controller = PremiumIntroScreen(context: context, source: .pinnedChats) + replaceImpl?(controller) return true }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } parentController?.push(controller) default: break diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index bbe64500c2..2737f69fd5 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -2875,8 +2875,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.push(PeerInfoStoryGridScreen(context: self.context, peerId: self.context.account.peerId, scope: .archive)) }) }))) - } else if case .channel = peer { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_ContextOpenChannel, icon: { theme in + } else if case let .channel(channel) = peer { + let openTitle: String + switch channel.info { + case .broadcast: + openTitle = self.presentationData.strings.ChatList_ContextOpenChannel + case .group: + openTitle = self.presentationData.strings.ChatList_ContextOpenGroup + } + items.append(.action(ContextMenuActionItem(text: openTitle, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in c.dismiss(completion: { diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index 731b7eb7a4..4d2ca355f5 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -985,6 +985,7 @@ public final class ContactListNode: ASDisplayNode { let processingQueue = Queue() let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil) let previousSelectionState = Atomic(value: nil) + let previousPendingRemovalPeerIds = Atomic?>(value: nil) let interaction = ContactListNodeInteraction(activateSearch: { [weak self] in self?.activateSearch?() @@ -1517,6 +1518,7 @@ public final class ContactListNode: ASDisplayNode { let entries = contactListNodeEntries(accountPeer: view.1, peers: peers, presences: view.0.presences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: view.2, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons, storySubscriptions: storySubscriptions, topPeers: topPeers, interaction: interaction) let previous = previousEntries.swap(entries) let previousSelection = previousSelectionState.swap(selectionState) + let previousPendingRemovalPeerIds = previousPendingRemovalPeerIds.swap(pendingRemovalPeerIds) var hadPermissionInfo = false if let previous = previous { @@ -1538,6 +1540,8 @@ public final class ContactListNode: ASDisplayNode { let animation: ContactListAnimation if (previousSelection == nil) != (selectionState == nil) { animation = .insertion + } else if previousPendingRemovalPeerIds != pendingRemovalPeerIds { + animation = .insertion } else if hadPermissionInfo != hasPermissionInfo { animation = .insertion } else { diff --git a/submodules/ItemListUI/Sources/ItemListControllerNode.swift b/submodules/ItemListUI/Sources/ItemListControllerNode.swift index 3ce8365f56..b8dcdcf313 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerNode.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerNode.swift @@ -585,7 +585,11 @@ open class ItemListControllerNode: ASDisplayNode { if let headerItemNode = self.headerItemNode { let headerHeight = headerItemNode.updateLayout(layout: layout, transition: transition) headerItemNode.frame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: 56.0)) - insets.top += headerHeight + if headerHeight > 300.0 { + insets.top = headerHeight + } else { + insets.top += headerHeight + } } var footerHeight: CGFloat = 0.0 @@ -986,7 +990,7 @@ open class ItemListControllerNode: ASDisplayNode { if updateSearchItem { self.requestLayout?(.animated(duration: 0.3, curve: .spring)) - } else if updateToolbarItem || updateFooterItem, let (layout, navigationBarHeight, additionalInsets) = self.validLayout { + } else if updateToolbarItem || updateHeaderItem || updateFooterItem, let (layout, navigationBarHeight, additionalInsets) = self.validLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .spring), additionalInsets: additionalInsets) } } diff --git a/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m b/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m index 6d41c82b63..ebd4719cfb 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m +++ b/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m @@ -248,24 +248,24 @@ [itemViews addObject:viewItem]; } - if (_hasSearchButton) - { - TGMenuSheetButtonItemView *viewItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"ProfilePhoto.SearchWeb") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^ - { - __strong TGMediaAvatarMenuMixin *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - __strong TGMenuSheetController *strongController = weakController; - if (strongController == nil) - return; - - [strongController dismissAnimated:true]; - if (strongSelf != nil) - strongSelf.requestSearchController(nil); - }]; - [itemViews addObject:viewItem]; - } +// if (_hasSearchButton) +// { +// TGMenuSheetButtonItemView *viewItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"ProfilePhoto.SearchWeb") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^ +// { +// __strong TGMediaAvatarMenuMixin *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// __strong TGMenuSheetController *strongController = weakController; +// if (strongController == nil) +// return; +// +// [strongController dismissAnimated:true]; +// if (strongSelf != nil) +// strongSelf.requestSearchController(nil); +// }]; +// [itemViews addObject:viewItem]; +// } if (_hasViewButton) { diff --git a/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift b/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift index 4f0a7df81a..079d7e8842 100644 --- a/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift +++ b/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift @@ -183,9 +183,9 @@ private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyCompone switch type { case TGAudioSessionTypePlayAndRecord, TGAudioSessionTypePlayAndRecordHeadphones: if legacyContext.sharedContext.currentMediaInputSettings.with({ $0 }).pauseMusicOnRecording { - convertedType = .record(speaker: false, withOthers: false) + convertedType = .record(speaker: false, video: true, withOthers: false) } else { - convertedType = .recordWithOthers + convertedType = .record(speaker: false, video: true, withOthers: true) } default: convertedType = .play(mixWithOthers: false) diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 6823185ab5..d167cd1139 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -1831,7 +1831,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { guard let self else { return } - let items = items.filter { $0.count > 0 } + let items = items.filter { $0.count > 0 || $0.collection.assetCollectionSubtype == .smartAlbumAllHidden } var dismissImpl: (() -> Void)? let content: ContextControllerItemsContent = MediaGroupsContextMenuContent( context: self.context, diff --git a/submodules/PeerInfoUI/BUILD b/submodules/PeerInfoUI/BUILD index 94da92e765..51a5895d90 100644 --- a/submodules/PeerInfoUI/BUILD +++ b/submodules/PeerInfoUI/BUILD @@ -77,6 +77,7 @@ swift_library( "//submodules/Components/ReactionImageComponent:ReactionImageComponent", "//submodules/Components/LottieAnimationComponent:LottieAnimationComponent", "//submodules/TelegramUI/Components/SendInviteLinkScreen", + "//submodules/TelegramUI/Components/GroupStickerPackSetupController", ], visibility = [ "//visibility:public", diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index 3aab406787..5ca187fa5f 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -33,9 +33,10 @@ private final class ChannelPermissionsControllerArguments { let presentConversionToBroadcastGroup: () -> Void let openChannelExample: () -> Void let updateSlowmode: (Int32) -> Void + let updateUnrestrictBoosters: (Int32) -> Void let toggleIsOptionExpanded: (TelegramChatBannedRightsFlags) -> Void - init(context: AccountContext, updatePermission: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, openPeer: @escaping (ChannelParticipant) -> Void, openPeerInfo: @escaping (EnginePeer) -> Void, openKicked: @escaping () -> Void, presentRestrictedPermissionAlert: @escaping (TelegramChatBannedRightsFlags) -> Void, presentConversionToBroadcastGroup: @escaping () -> Void, openChannelExample: @escaping () -> Void, updateSlowmode: @escaping (Int32) -> Void, toggleIsOptionExpanded: @escaping (TelegramChatBannedRightsFlags) -> Void) { + init(context: AccountContext, updatePermission: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, openPeer: @escaping (ChannelParticipant) -> Void, openPeerInfo: @escaping (EnginePeer) -> Void, openKicked: @escaping () -> Void, presentRestrictedPermissionAlert: @escaping (TelegramChatBannedRightsFlags) -> Void, presentConversionToBroadcastGroup: @escaping () -> Void, openChannelExample: @escaping () -> Void, updateSlowmode: @escaping (Int32) -> Void, updateUnrestrictBoosters: @escaping (Int32) -> Void, toggleIsOptionExpanded: @escaping (TelegramChatBannedRightsFlags) -> Void) { self.context = context self.updatePermission = updatePermission self.addPeer = addPeer @@ -48,6 +49,7 @@ private final class ChannelPermissionsControllerArguments { self.presentConversionToBroadcastGroup = presentConversionToBroadcastGroup self.openChannelExample = openChannelExample self.updateSlowmode = updateSlowmode + self.updateUnrestrictBoosters = updateUnrestrictBoosters self.toggleIsOptionExpanded = toggleIsOptionExpanded } } @@ -79,7 +81,8 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { case slowmodeHeader(PresentationTheme, String) case slowmode(PresentationTheme, PresentationStrings, Int32) case slowmodeInfo(PresentationTheme, String) - case unrestrictBoosters(PresentationTheme, String, Bool) + case unrestrictBoostersSwitch(PresentationTheme, String, Bool) + case unrestrictBoosters(PresentationTheme, PresentationStrings, Int32) case unrestrictBoostersInfo(PresentationTheme, String) case conversionHeader(PresentationTheme, String) case conversion(PresentationTheme, String) @@ -97,7 +100,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { return ChannelPermissionsSection.slowmode.rawValue case .conversionHeader, .conversion, .conversionInfo: return ChannelPermissionsSection.conversion.rawValue - case .unrestrictBoosters, .unrestrictBoostersInfo: + case .unrestrictBoostersSwitch, .unrestrictBoosters, .unrestrictBoostersInfo: return ChannelPermissionsSection.unrestrictBoosters.rawValue case .kicked: return ChannelPermissionsSection.kicked.rawValue @@ -118,22 +121,24 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { return .index(999) case .conversionInfo: return .index(1000) - case .slowmodeHeader: + case .unrestrictBoostersSwitch: return .index(1001) - case .slowmode: - return .index(1002) - case .slowmodeInfo: - return .index(1003) case .unrestrictBoosters: - return .index(1004) + return .index(1002) case .unrestrictBoostersInfo: + return .index(1003) + case .slowmodeHeader: + return .index(1004) + case .slowmode: return .index(1005) - case .kicked: + case .slowmodeInfo: return .index(1006) - case .exceptionsHeader: + case .kicked: return .index(1007) - case .add: + case .exceptionsHeader: return .index(1008) + case .add: + return .index(1009) case let .peerItem(_, _, _, _, _, participant, _, _, _, _): return .peer(participant.peer.id) } @@ -189,8 +194,14 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { } else { return false } - case let .unrestrictBoosters(lhsTheme, lhsText, lhsValue): - if case let .unrestrictBoosters(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + case let .unrestrictBoostersSwitch(lhsTheme, lhsTitle, lhsValue): + if case let .unrestrictBoostersSwitch(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue { + return true + } else { + return false + } + case let .unrestrictBoosters(lhsTheme, lhsStrings, lhsValue): + if case let .unrestrictBoosters(rhsTheme, rhsStrings, rhsValue) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsValue == rhsValue { return true } else { return false @@ -346,9 +357,13 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { return ItemListTextItem(presentationData: presentationData, text: .markdown(value), sectionId: self.section) { _ in arguments.openChannelExample() } - case let .unrestrictBoosters(_, title, value): + case let .unrestrictBoostersSwitch(_, title, value): return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in - + arguments.updateUnrestrictBoosters(value ? 1 : 0) + }) + case let .unrestrictBoosters(theme, strings, value): + return ChatUnrestrictBoostersItem(theme: theme, strings: strings, value: value, enabled: true, sectionId: self.section, updated: { value in + arguments.updateUnrestrictBoosters(value) }) case let .unrestrictBoostersInfo(_, value): return ItemListTextItem(presentationData: presentationData, text: .plain(value), sectionId: self.section) @@ -406,6 +421,7 @@ private struct ChannelPermissionsControllerState: Equatable { var searchingMembers: Bool = false var modifiedRightsFlags: TelegramChatBannedRightsFlags? var modifiedSlowmodeTimeout: Int32? + var modifiedUnrestrictBoosters: Int32? var expandedPermissions = Set() } @@ -591,6 +607,7 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen } entries.append(.permissionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_SectionTitle)) + var rightIndex: Int = 0 for (rights, correspondingAdminRight) in allGroupPermissionList(peer: .channel(channel), expandMedia: false) { var enabled = true @@ -629,18 +646,28 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen entries.append(.conversionInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_BroadcastConvertInfo(presentationStringsFormattedNumber(participantsLimit, presentationData.dateTimeFormat.groupingSeparator)).string)) } + let canSendText = !effectiveRightsFlags.contains(.banSendText) + let canSendMedia = banSendMediaSubList().allSatisfy({ !effectiveRightsFlags.contains($0.0) }) let slowModeTimeout = state.modifiedSlowmodeTimeout ?? (cachedData.slowModeTimeout ?? 0) + + if !canSendText || !canSendMedia || slowModeTimeout > 0 { + let unrestrictBoosters = state.modifiedUnrestrictBoosters ?? (cachedData.boostsUnrestrict ?? 0) + let unrestrictEnabled = unrestrictBoosters > 0 + + entries.append(.unrestrictBoostersSwitch(presentationData.theme, presentationData.strings.GroupInfo_Permissions_DontRestrictBoosters, unrestrictEnabled)) + if unrestrictEnabled { + entries.append(.unrestrictBoosters(presentationData.theme, presentationData.strings, max(1, unrestrictBoosters))) + entries.append(.unrestrictBoostersInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_DontRestrictBoostersInfo)) + } else { + entries.append(.unrestrictBoostersInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_DontRestrictBoostersEnableInfo)) + } + } + entries.append(.slowmodeHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_SlowmodeHeader)) entries.append(.slowmode(presentationData.theme, presentationData.strings, slowModeTimeout)) entries.append(.slowmodeInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_SlowmodeInfo)) - //TODO:localize - if slowModeTimeout > 0 { - entries.append(.unrestrictBoosters(presentationData.theme, "Do Not Restrict Boosters", false)) - entries.append(.unrestrictBoostersInfo(presentationData.theme, "Turn this on to always allow users who boosted your group to send messages and media.")) - } - entries.append(.kicked(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Removed, cachedData.participantsSummary.kickedCount.flatMap({ $0 == 0 ? "" : "\($0)" }) ?? "")) entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions)) entries.append(.add(presentationData.theme, presentationData.strings.GroupInfo_Permissions_AddException)) @@ -1110,6 +1137,13 @@ public func channelPermissionsController(context: AccountContext, updatedPresent })) } }) + }, updateUnrestrictBoosters: { value in + updateState { state in + var state = state + state.modifiedUnrestrictBoosters = value + return state + } + }, toggleIsOptionExpanded: { flags in updateState { state in var state = state diff --git a/submodules/PeerInfoUI/Sources/ChatUnrestrictBoostersItem.swift b/submodules/PeerInfoUI/Sources/ChatUnrestrictBoostersItem.swift new file mode 100644 index 0000000000..320a91126b --- /dev/null +++ b/submodules/PeerInfoUI/Sources/ChatUnrestrictBoostersItem.swift @@ -0,0 +1,342 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramCore +import TelegramUIPreferences +import TelegramPresentationData +import LegacyComponents +import ItemListUI +import PresentationDataUtils + +class ChatUnrestrictBoostersItem: ListViewItem, ItemListItem { + let theme: PresentationTheme + let strings: PresentationStrings + let value: Int32 + let sectionId: ItemListSectionId + let updated: (Int32) -> Void + + init(theme: PresentationTheme, strings: PresentationStrings, value: Int32, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) { + self.theme = theme + self.strings = strings + self.value = value + self.sectionId = sectionId + self.updated = updated + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = ChatUnrestrictBoostersItemNode() + let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + + node.contentSize = layout.contentSize + node.insets = layout.insets + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in apply() }) + }) + } + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + if let nodeValue = node() as? ChatUnrestrictBoostersItemNode { + let makeLayout = nodeValue.asyncLayout() + + async { + let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + Queue.mainQueue().async { + completion(layout, { _ in + apply() + }) + } + } + } + } + } +} + +private let allowedValues: [Int32] = [1, 2, 3, 4, 5] + +class ChatUnrestrictBoostersItemNode: ListViewItemNode { + private let backgroundNode: ASDisplayNode + private let topStripeNode: ASDisplayNode + private let bottomStripeNode: ASDisplayNode + private let maskNode: ASImageNode + + private let iconNodes: [ASImageNode] + private let textNodes: [TextNode] + private var sliderView: TGPhotoEditorSliderView? + + private var item: ChatUnrestrictBoostersItem? + private var layoutParams: ListViewItemLayoutParams? + private var reportedValue: Int32? + + init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + + self.maskNode = ASImageNode() + + self.topStripeNode = ASDisplayNode() + self.topStripeNode.isLayerBacked = true + + self.bottomStripeNode = ASDisplayNode() + self.bottomStripeNode.isLayerBacked = true + + self.iconNodes = allowedValues.map { _ -> ASImageNode in + let iconNode = ASImageNode() + iconNode.isUserInteractionEnabled = false + iconNode.displaysAsynchronously = false + return iconNode + } + + self.textNodes = allowedValues.map { _ -> TextNode in + let textNode = TextNode() + textNode.isUserInteractionEnabled = false + textNode.displaysAsynchronously = false + return textNode + } + + super.init(layerBacked: false, dynamicBounce: false) + + self.iconNodes.forEach(self.addSubnode) + self.textNodes.forEach(self.addSubnode) + } + + func forceSetValue(_ value: Int32) { + if let sliderView = self.sliderView { + sliderView.value = CGFloat(value) + } + } + + func updateSliderView() { + if let sliderView = self.sliderView, let item = self.item { + sliderView.maximumValue = CGFloat(allowedValues.count - 1) + sliderView.positionsCount = allowedValues.count + var value: Int32 = 0 + for i in 0 ..< allowedValues.count { + if allowedValues[i] >= item.value { + value = Int32(i) + break + } + } + + sliderView.value = CGFloat(value) + + sliderView.isUserInteractionEnabled = true + sliderView.alpha = 1.0 + sliderView.layer.allowsGroupOpacity = false + } + } + + override func didLoad() { + super.didLoad() + + self.view.disablesInteractiveTransitionGestureRecognizer = true + + let sliderView = TGPhotoEditorSliderView() + sliderView.limitValueChangedToLatestState = true + sliderView.enablePanHandling = true + sliderView.trackCornerRadius = 2.0 + sliderView.lineSize = 4.0 + sliderView.dotSize = 5.0 + sliderView.minimumValue = 0.0 + sliderView.maximumValue = CGFloat(allowedValues.count - 1) + sliderView.positionsCount = allowedValues.count + sliderView.startValue = 0.0 + sliderView.disablesInteractiveTransitionGestureRecognizer = true + sliderView.useLinesForPositions = true + if let item = self.item, let params = self.layoutParams { + var value: Int32 = 0 + for i in 0 ..< allowedValues.count { + if allowedValues[i] >= item.value { + value = Int32(i) + break + } + } + + sliderView.value = CGFloat(value) + self.reportedValue = item.value + sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor + sliderView.backColor = item.theme.list.itemSwitchColors.frameColor + sliderView.startColor = item.theme.list.itemSwitchColors.frameColor + sliderView.trackColor = item.theme.list.itemAccentColor + sliderView.knobImage = PresentationResourcesItemList.knobImage(item.theme) + + sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0)) + sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX) + } + self.view.addSubview(sliderView) + sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged) + self.sliderView = sliderView + + self.updateSliderView() + } + + func asyncLayout() -> (_ item: ChatUnrestrictBoostersItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { + let currentItem = self.item + let makeTextLayouts = self.textNodes.map(TextNode.asyncLayout) + + return { item, params, neighbors in + var themeUpdated = false + if currentItem?.theme !== item.theme { + themeUpdated = true + } + + let contentSize: CGSize + let insets: UIEdgeInsets + let separatorHeight = UIScreenPixel + + var textLayoutAndApply: [(TextNodeLayout, () -> TextNode)] = [] + + for i in 0 ..< allowedValues.count { + let value = allowedValues[i] + + let valueString: String + if value == 0 { + valueString = item.strings.GroupInfo_Permissions_SlowmodeValue_Off + } else { + valueString = "\(value)" //shortTimeIntervalString(strings: item.strings, value: value) + } + let (textLayout, textApply) = makeTextLayouts[i](TextNodeLayoutArguments(attributedString: NSAttributedString(string: valueString, font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets())) + textLayoutAndApply.append((textLayout, textApply)) + } + + contentSize = CGSize(width: params.width, height: 88.0) + insets = itemListNeighborsGroupedInsets(neighbors, params) + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + let layoutSize = layout.size + + return (layout, { [weak self] in + if let strongSelf = self { + strongSelf.item = item + strongSelf.layoutParams = params + + strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor + strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor + strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor + + if strongSelf.backgroundNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) + } + if strongSelf.topStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1) + } + if strongSelf.bottomStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) + } + if strongSelf.maskNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.maskNode, at: 3) + } + let hasCorners = itemListHasRoundedBlockLayout(params) + var hasTopCorners = false + var hasBottomCorners = false + switch neighbors.top { + case .sameSection(false): + strongSelf.topStripeNode.isHidden = true + default: + hasTopCorners = true + strongSelf.topStripeNode.isHidden = hasCorners + } + let bottomStripeInset: CGFloat + let bottomStripeOffset: CGFloat + switch neighbors.bottom { + case .sameSection(false): + bottomStripeInset = 0.0 + bottomStripeOffset = -separatorHeight + strongSelf.bottomStripeNode.isHidden = false + default: + bottomStripeInset = 0.0 + hasBottomCorners = true + strongSelf.bottomStripeNode.isHidden = hasCorners + bottomStripeOffset = 0.0 + } + + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) + strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) + strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + + for (_, apply) in textLayoutAndApply { + let _ = apply() + } + + let textNodes: [(TextNode, CGSize)] = textLayoutAndApply.map { layout, apply -> (TextNode, CGSize) in + let node = apply() + return (node, layout.size) + } + + let delta = (params.width - params.leftInset - params.rightInset - 18.0 * 2.0) / CGFloat(textNodes.count - 1) + for i in 0 ..< textNodes.count { + let iconNode = strongSelf.iconNodes[i] + let (textNode, textSize) = textNodes[i] + + if themeUpdated { + iconNode.image = generateTintedImage(image: i == 0 ? UIImage(bundleImageName: "Chat/Message/Boost") : UIImage(bundleImageName: "Chat/Message/Boosts"), color: item.theme.list.itemSecondaryTextColor) + } + + var position = params.leftInset + 18.0 + delta * CGFloat(i) + var iconOffset: CGFloat = 9.0 + let textOffset: CGFloat = 5.0 + if i == textNodes.count - 1 { + position -= textSize.width + 8.0 + } else if i > 0 { + position -= textSize.width / 2.0 + } else if i == 0 { + position += textSize.width + iconOffset = 6.0 + } + + if let icon = iconNode.image { + iconNode.frame = CGRect(origin: CGPoint(x: position - iconOffset, y: 16.0), size: icon.size) + textNode.frame = CGRect(origin: CGPoint(x: position + textOffset, y: 15.0), size: textSize) + } + } + + if let sliderView = strongSelf.sliderView { + if themeUpdated { + sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor + sliderView.backColor = item.theme.list.itemSwitchColors.frameColor + sliderView.startColor = item.theme.list.itemSwitchColors.frameColor + sliderView.trackColor = item.theme.list.itemAccentColor + sliderView.knobImage = PresentationResourcesItemList.knobImage(item.theme) + } + + sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0)) + sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX) + + strongSelf.updateSliderView() + } + } + }) + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } + + @objc func sliderValueChanged() { + guard let item = self.item, let sliderView = self.sliderView else { + return + } + + let position = Int(sliderView.value) + let value: Int32 = allowedValues[max(0, min(allowedValues.count - 1, position))] + if self.reportedValue != value { + self.reportedValue = value + item.updated(value) + } + } +} diff --git a/submodules/PremiumUI/Resources/boost.scn b/submodules/PremiumUI/Resources/boost.scn new file mode 100644 index 0000000000000000000000000000000000000000..d8f41739bf73e8f742131f69414ad1c1ecd65a2f GIT binary patch literal 68031 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 literal 0 HcmV?d00001 diff --git a/submodules/PremiumUI/Resources/tag.png b/submodules/PremiumUI/Resources/tag.png new file mode 100644 index 0000000000000000000000000000000000000000..a3a71e26298566990a2c0448092e571beae6a864 GIT binary patch literal 4258 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 literal 0 HcmV?d00001 diff --git a/submodules/PremiumUI/Sources/BadgeLabelView.swift b/submodules/PremiumUI/Sources/BadgeLabelView.swift index af32cf3bce..315551c5f0 100644 --- a/submodules/PremiumUI/Sources/BadgeLabelView.swift +++ b/submodules/PremiumUI/Sources/BadgeLabelView.swift @@ -14,6 +14,14 @@ final class BadgeLabelView: UIView { var currentValue: Int32 = 0 + var color: UIColor = .white { + didSet { + for view in self.labels { + view.textColor = self.color + } + } + } + init() { super.init(frame: CGRect(origin: .zero, size: labelSize)) @@ -25,7 +33,7 @@ final class BadgeLabelView: UIView { } else { label.text = "\(i)" } - label.textColor = .white + label.textColor = self.color label.font = font label.textAlignment = .center label.frame = CGRect(x: 0, y: height, width: labelWidth, height: labelHeight) @@ -81,6 +89,15 @@ final class BadgeLabelView: UIView { fatalError("init(coder:) has not been implemented") } + var color: UIColor = .white { + didSet { + self.staticLabel.textColor = self.color + for (_, view) in self.itemViews { + view.color = self.color + } + } + } + func update(value: String, transition: Transition) -> CGSize { if value.contains(" ") { for (_, view) in self.itemViews { @@ -88,7 +105,7 @@ final class BadgeLabelView: UIView { } if self.staticLabel.superview == nil { - self.staticLabel.textColor = .white + self.staticLabel.textColor = self.color self.staticLabel.font = font self.addSubview(self.staticLabel) diff --git a/submodules/PremiumUI/Sources/BadgeStarsView.swift b/submodules/PremiumUI/Sources/BadgeStarsView.swift index 6ca220ce3a..f8f80a116d 100644 --- a/submodules/PremiumUI/Sources/BadgeStarsView.swift +++ b/submodules/PremiumUI/Sources/BadgeStarsView.swift @@ -111,3 +111,57 @@ final class EmojiStarsView: UIView, PhoneDemoDecorationView { self.sceneView.frame = CGRect(origin: .zero, size: frame.size) } } + +final class TagStarsView: UIView, PhoneDemoDecorationView { + private let sceneView: SCNView + + private var leftParticles: SCNNode? + private var rightParticles: SCNNode? + + override init(frame: CGRect) { + self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size)) + self.sceneView.backgroundColor = .clear + if let url = getAppBundle().url(forResource: "tag", withExtension: "scn") { + self.sceneView.scene = try? SCNScene(url: url, options: nil) + } + self.sceneView.isUserInteractionEnabled = false + self.sceneView.preferredFramesPerSecond = 60 + + super.init(frame: frame) + + self.alpha = 0.0 + + self.addSubview(self.sceneView) + + self.leftParticles = self.sceneView.scene?.rootNode.childNode(withName: "leftParticles", recursively: false) + self.rightParticles = self.sceneView.scene?.rootNode.childNode(withName: "rightParticles", recursively: false) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setVisible(_ visible: Bool) { + if visible, let leftParticles = self.leftParticles, let rightParticles = self.rightParticles, leftParticles.parent == nil { + self.sceneView.scene?.rootNode.addChildNode(leftParticles) + self.sceneView.scene?.rootNode.addChildNode(rightParticles) + } + + let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear) + transition.updateAlpha(layer: self.layer, alpha: visible ? 0.5 : 0.0, completion: { [weak self] finished in + if let strongSelf = self, finished && !visible && strongSelf.leftParticles?.parent != nil { + strongSelf.leftParticles?.removeFromParentNode() + strongSelf.rightParticles?.removeFromParentNode() + } + }) + } + + func resetAnimation() { + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.sceneView.frame = CGRect(origin: .zero, size: frame.size) + } +} diff --git a/submodules/PremiumUI/Sources/BoostHeaderBackgroundComponent.swift b/submodules/PremiumUI/Sources/BoostHeaderBackgroundComponent.swift new file mode 100644 index 0000000000..64e5a28de4 --- /dev/null +++ b/submodules/PremiumUI/Sources/BoostHeaderBackgroundComponent.swift @@ -0,0 +1,193 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import SwiftSignalKit +import SceneKit +import GZip +import AppBundle +import LegacyComponents +import AvatarNode +import TelegramCore +import MultilineTextComponent +import TelegramPresentationData + +private let sceneVersion: Int = 3 + +public final class BoostHeaderBackgroundComponent: Component { + let isVisible: Bool + let hasIdleAnimations: Bool + + public init(isVisible: Bool, hasIdleAnimations: Bool) { + self.isVisible = isVisible + self.hasIdleAnimations = hasIdleAnimations + } + + public static func ==(lhs: BoostHeaderBackgroundComponent, rhs: BoostHeaderBackgroundComponent) -> Bool { + return lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations + } + + public final class View: UIView, SCNSceneRendererDelegate { + private var _ready = Promise() + var ready: Signal { + return self._ready.get() + } + + private let sceneView: SCNView + + private var previousInteractionTimestamp: Double = 0.0 + private var hasIdleAnimations = false + + override init(frame: CGRect) { + self.sceneView = SCNView(frame: CGRect(origin: .zero, size: CGSize(width: 64.0, height: 64.0))) + self.sceneView.backgroundColor = .clear + self.sceneView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) + self.sceneView.isUserInteractionEnabled = false + self.sceneView.preferredFramesPerSecond = 60 + + super.init(frame: frame) + + self.addSubview(self.sceneView) + + self.setup() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + private func setup() { + guard let url = getAppBundle().url(forResource: "boost", withExtension: "scn"), let scene = try? SCNScene(url: url, options: nil) else { + return + } + + self.sceneView.scene = scene + self.sceneView.delegate = self + + let _ = self.sceneView.snapshot() + } + + private var didSetReady = false + public func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) { + if !self.didSetReady { + self.didSetReady = true + + Queue.mainQueue().justDispatch { + self._ready.set(.single(true)) + self.onReady() + } + } + } + + private func onReady() { + self.playAppearanceAnimation(explode: true) + } + + + private func playAppearanceAnimation(velocity: CGFloat? = nil, smallAngle: Bool = false, mirror: Bool = false, explode: Bool = false) { + guard let scene = self.sceneView.scene else { + return + } + + let currentTime = CACurrentMediaTime() + self.previousInteractionTimestamp = currentTime + + if explode, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particlesLeft = scene.rootNode.childNode(withName: "particles_left", recursively: false), let particlesRight = scene.rootNode.childNode(withName: "particles_right", recursively: false), let particlesBottomLeft = scene.rootNode.childNode(withName: "particles_left_bottom", recursively: false), let particlesBottomRight = scene.rootNode.childNode(withName: "particles_right_bottom", recursively: false) { + if let leftParticleSystem = particlesLeft.particleSystems?.first, let rightParticleSystem = particlesRight.particleSystems?.first, let leftBottomParticleSystem = particlesBottomLeft.particleSystems?.first, let rightBottomParticleSystem = particlesBottomRight.particleSystems?.first { + leftParticleSystem.speedFactor = 2.0 + leftParticleSystem.particleVelocity = 1.6 + leftParticleSystem.birthRate = 60.0 + leftParticleSystem.particleLifeSpan = 4.0 + + rightParticleSystem.speedFactor = 2.0 + rightParticleSystem.particleVelocity = 1.6 + rightParticleSystem.birthRate = 60.0 + rightParticleSystem.particleLifeSpan = 4.0 + +// leftBottomParticleSystem.speedFactor = 2.0 + leftBottomParticleSystem.particleVelocity = 1.6 + leftBottomParticleSystem.birthRate = 24.0 + leftBottomParticleSystem.particleLifeSpan = 7.0 + +// rightBottomParticleSystem.speedFactor = 2.0 + rightBottomParticleSystem.particleVelocity = 1.6 + rightBottomParticleSystem.birthRate = 24.0 + rightBottomParticleSystem.particleLifeSpan = 7.0 + + node.physicsField?.isActive = true + Queue.mainQueue().after(1.0) { + node.physicsField?.isActive = false + + leftParticleSystem.birthRate = 12.0 + leftParticleSystem.particleVelocity = 1.2 + leftParticleSystem.particleLifeSpan = 3.0 + + rightParticleSystem.birthRate = 12.0 + rightParticleSystem.particleVelocity = 1.2 + rightParticleSystem.particleLifeSpan = 3.0 + + leftBottomParticleSystem.particleVelocity = 1.2 + leftBottomParticleSystem.birthRate = 7.0 + leftBottomParticleSystem.particleLifeSpan = 5.0 + + rightBottomParticleSystem.particleVelocity = 1.2 + rightBottomParticleSystem.birthRate = 7.0 + rightBottomParticleSystem.particleLifeSpan = 5.0 + + let leftAnimation = POPBasicAnimation() + leftAnimation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in + property?.readBlock = { particleSystem, values in + values?.pointee = (particleSystem as! SCNParticleSystem).speedFactor + } + property?.writeBlock = { particleSystem, values in + (particleSystem as! SCNParticleSystem).speedFactor = values!.pointee + } + property?.threshold = 0.01 + }) as! POPAnimatableProperty) + leftAnimation.fromValue = 1.2 as NSNumber + leftAnimation.toValue = 0.85 as NSNumber + leftAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + leftAnimation.duration = 0.5 + leftParticleSystem.pop_add(leftAnimation, forKey: "speedFactor") + + let rightAnimation = POPBasicAnimation() + rightAnimation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in + property?.readBlock = { particleSystem, values in + values?.pointee = (particleSystem as! SCNParticleSystem).speedFactor + } + property?.writeBlock = { particleSystem, values in + (particleSystem as! SCNParticleSystem).speedFactor = values!.pointee + } + property?.threshold = 0.01 + }) as! POPAnimatableProperty) + rightAnimation.fromValue = 1.2 as NSNumber + rightAnimation.toValue = 0.85 as NSNumber + rightAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + rightAnimation.duration = 0.5 + rightParticleSystem.pop_add(rightAnimation, forKey: "speedFactor") + } + } + } + } + + func update(component: BoostHeaderBackgroundComponent, availableSize: CGSize, transition: Transition) -> CGSize { + self.sceneView.bounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width * 2.0, height: availableSize.height)) + if self.sceneView.superview == self { + self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0) + } + + self.hasIdleAnimations = component.hasIdleAnimations + + return availableSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, transition: transition) + } +} diff --git a/submodules/PremiumUI/Sources/CreateGiveawayController.swift b/submodules/PremiumUI/Sources/CreateGiveawayController.swift index e4d93d125e..d3e9787af2 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayController.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayController.swift @@ -432,7 +432,11 @@ private enum CreateGiveawayEntry: ItemListNodeEntry { case let .channelsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .channel(_, _, peer, boosts, isRevealed): - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: boosts.flatMap { .text(presentationData.strings.BoostGift_ChannelsBoosts($0), .secondary) } ?? .none, label: .none, editing: ItemListPeerItemEditing(editable: boosts == nil, editing: false, revealed: isRevealed), switchValue: nil, enabled: true, selectable: peer.id != arguments.context.account.peerId, sectionId: self.section, action: { + var isGroup = false + if case let .channel(channel) = peer, case .group = channel.info { + isGroup = true + } + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: boosts.flatMap { .text(isGroup ? presentationData.strings.BoostGift_GroupBoosts($0) : presentationData.strings.BoostGift_ChannelsBoosts($0), .secondary) } ?? .none, label: .none, editing: ItemListPeerItemEditing(editable: boosts == nil, editing: false, revealed: isRevealed), switchValue: nil, enabled: true, selectable: peer.id != arguments.context.account.peerId, sectionId: self.section, action: { }, setPeerIdWithRevealedOptions: { lhs, rhs in arguments.setItemIdWithRevealedOptions(lhs, rhs) }, removePeer: { id in @@ -634,6 +638,11 @@ private func createGiveawayControllerEntries( minDate: Int32, maxDate: Int32 ) -> [CreateGiveawayEntry] { + var isGroup = false + if let peer = peers[peerId], case let .channel(channel) = peer, case .group = channel.info { + isGroup = true + } + var entries: [CreateGiveawayEntry] = [] switch subject { @@ -719,7 +728,7 @@ private func createGiveawayControllerEntries( entries.append(.subscriptionsInfo(presentationData.theme, presentationData.strings.BoostGift_QuantityInfo)) } - entries.append(.channelsHeader(presentationData.theme, presentationData.strings.BoostGift_ChannelsTitle.uppercased())) + entries.append(.channelsHeader(presentationData.theme, isGroup ? presentationData.strings.BoostGift_GroupsAndChannelsTitle.uppercased() : presentationData.strings.BoostGift_ChannelsAndGroupsTitle.uppercased())) var index: Int32 = 0 let channels = [peerId] + state.channels for channelId in channels { @@ -728,8 +737,8 @@ private func createGiveawayControllerEntries( } index += 1 } - entries.append(.channelAdd(presentationData.theme, presentationData.strings.BoostGift_AddChannel)) - entries.append(.channelsInfo(presentationData.theme, presentationData.strings.BoostGift_ChannelsInfo)) + entries.append(.channelAdd(presentationData.theme, isGroup ? presentationData.strings.BoostGift_AddGroupOrChannel : presentationData.strings.BoostGift_AddChannelOrGroup)) + entries.append(.channelsInfo(presentationData.theme, isGroup ? presentationData.strings.BoostGift_GroupsAndChannelsInfo : presentationData.strings.BoostGift_ChannelsAndGroupsInfo)) entries.append(.usersHeader(presentationData.theme, presentationData.strings.BoostGift_UsersTitle.uppercased())) @@ -752,9 +761,9 @@ private func createGiveawayControllerEntries( countriesText = presentationData.strings.BoostGift_FromAllCountries } - entries.append(.usersAll(presentationData.theme, presentationData.strings.BoostGift_AllSubscribers, countriesText, !state.onlyNewEligible)) - entries.append(.usersNew(presentationData.theme, presentationData.strings.BoostGift_OnlyNewSubscribers, countriesText, state.onlyNewEligible)) - entries.append(.usersInfo(presentationData.theme, presentationData.strings.BoostGift_LimitSubscribersInfo)) + entries.append(.usersAll(presentationData.theme, isGroup ? presentationData.strings.BoostGift_Group_AllMembers : presentationData.strings.BoostGift_AllSubscribers, countriesText, !state.onlyNewEligible)) + entries.append(.usersNew(presentationData.theme, isGroup ? presentationData.strings.BoostGift_Group_OnlyNewMembers : presentationData.strings.BoostGift_OnlyNewSubscribers, countriesText, state.onlyNewEligible)) + entries.append(.usersInfo(presentationData.theme, isGroup ? presentationData.strings.BoostGift_Group_LimitMembersInfo : presentationData.strings.BoostGift_LimitSubscribersInfo)) if case .generic = subject { appendDurationEntries() @@ -782,7 +791,12 @@ private func createGiveawayControllerEntries( entries.append(.timeHeader(presentationData.theme, presentationData.strings.BoostGift_DateTitle.uppercased())) entries.append(.timeCustomPicker(presentationData.theme, presentationData.dateTimeFormat, state.time, minDate, maxDate, state.pickingExpiryDate, state.pickingExpiryTime)) - entries.append(.timeInfo(presentationData.theme, presentationData.strings.BoostGift_DateInfo(presentationData.strings.BoostGift_DateInfoSubscribers(Int32(state.subscriptions))).string)) + + if isGroup { + entries.append(.timeInfo(presentationData.theme, presentationData.strings.BoostGift_Group_DateInfo(presentationData.strings.BoostGift_Group_DateInfoMembers(Int32(state.subscriptions))).string)) + } else { + entries.append(.timeInfo(presentationData.theme, presentationData.strings.BoostGift_DateInfo(presentationData.strings.BoostGift_DateInfoSubscribers(Int32(state.subscriptions))).string)) + } case .gift: appendDurationEntries() } @@ -948,8 +962,13 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio presentationData = presentationData.withUpdated(theme: updatedTheme) let (state, peersMap) = stateAndPeersMap + + var isGroup = false + if let peer = peersMap[peerId], case let .channel(channel) = peer, case .group = channel.info { + isGroup = true + } - let headerItem = CreateGiveawayHeaderItem(theme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.BoostGift_Title, text: presentationData.strings.BoostGift_Description, cancel: { + let headerItem = CreateGiveawayHeaderItem(theme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.BoostGift_Title, text: isGroup ? presentationData.strings.BoostGift_Group_Description : presentationData.strings.BoostGift_Description, cancel: { dismissImpl?() }) diff --git a/submodules/PremiumUI/Sources/GiftAvatarComponent.swift b/submodules/PremiumUI/Sources/GiftAvatarComponent.swift index e3fd75b997..6a1b879649 100644 --- a/submodules/PremiumUI/Sources/GiftAvatarComponent.swift +++ b/submodules/PremiumUI/Sources/GiftAvatarComponent.swift @@ -16,7 +16,7 @@ import TelegramPresentationData private let sceneVersion: Int = 3 -class GiftAvatarComponent: Component { +final class GiftAvatarComponent: Component { let context: AccountContext let theme: PresentationTheme let peers: [EnginePeer] diff --git a/submodules/PremiumUI/Sources/GiveawayInfoController.swift b/submodules/PremiumUI/Sources/GiveawayInfoController.swift index fc605e2462..0fdedc54e2 100644 --- a/submodules/PremiumUI/Sources/GiveawayInfoController.swift +++ b/submodules/PremiumUI/Sources/GiveawayInfoController.swift @@ -111,6 +111,8 @@ public func presentGiveawayInfoController( additionalPrizes = "\n\n" + presentationData.strings.Chat_Giveaway_Info_AdditionalPrizes(peerName, "\(quantity) \(prizeDescription)").string } + let isGroup = "".isEmpty + switch giveawayInfo { case let .ongoing(start, status): let startDate = presentationData.strings.Chat_Giveaway_Info_FullDate( @@ -122,9 +124,17 @@ public func presentGiveawayInfoController( let intro: String if case .almostOver = status { - intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + if isGroup { + intro = presentationData.strings.Chat_Giveaway_Info_Group_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + } else { + intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + } } else { - intro = presentationData.strings.Chat_Giveaway_Info_OngoingIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + if isGroup { + intro = presentationData.strings.Chat_Giveaway_Info_Group_OngoingIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + } else { + intro = presentationData.strings.Chat_Giveaway_Info_OngoingIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + } } let ending: String @@ -136,7 +146,7 @@ public func presentGiveawayInfoController( ending = presentationData.strings.Chat_Giveaway_Info_OngoingNew(untilDate, randomUsers, peerName, startDate).string } } else { - let randomSubscribers = presentationData.strings.Chat_Giveaway_Info_RandomSubscribers(quantity) + let randomSubscribers = isGroup ? presentationData.strings.Chat_Giveaway_Info_Group_RandomMembers(quantity) : presentationData.strings.Chat_Giveaway_Info_RandomSubscribers(quantity) if channelsCount > 1 { ending = presentationData.strings.Chat_Giveaway_Info_OngoingMany(untilDate, randomSubscribers, peerName, presentationData.strings.Chat_Giveaway_Info_OtherChannels(Int32(channelsCount - 1))).string } else { @@ -190,7 +200,12 @@ public func presentGiveawayInfoController( let finishDate = stringForDate(timestamp: finish, timeZone: timeZone, strings: presentationData.strings) title = presentationData.strings.Chat_Giveaway_Info_EndedTitle - let intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + let intro: String + if isGroup { + intro = presentationData.strings.Chat_Giveaway_Info_Group_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + } else { + intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + } var ending: String if onlyNewSubscribers { @@ -201,7 +216,7 @@ public func presentGiveawayInfoController( ending = presentationData.strings.Chat_Giveaway_Info_EndedNew(finishDate, randomUsers, peerName, startDate).string } } else { - let randomSubscribers = presentationData.strings.Chat_Giveaway_Info_RandomSubscribers(quantity) + let randomSubscribers = isGroup ? presentationData.strings.Chat_Giveaway_Info_Group_RandomMembers(quantity) : presentationData.strings.Chat_Giveaway_Info_RandomSubscribers(quantity) if channelsCount > 1 { ending = presentationData.strings.Chat_Giveaway_Info_EndedMany(finishDate, randomSubscribers, peerName).string } else { diff --git a/submodules/PremiumUI/Sources/PageIndicatorComponent.swift b/submodules/PremiumUI/Sources/PageIndicatorComponent.swift index e8ea2d7380..66f0142f76 100644 --- a/submodules/PremiumUI/Sources/PageIndicatorComponent.swift +++ b/submodules/PremiumUI/Sources/PageIndicatorComponent.swift @@ -55,10 +55,11 @@ public final class PageIndicatorComponent: Component { } public func update(component: PageIndicatorComponent, availableSize: CGSize, transition: Transition) -> CGSize { + let isFirstTime = self.component == nil self.component = component self.indicatorView.pageCount = component.pageCount - self.indicatorView.setProgress(progress: component.position) + self.indicatorView.setProgress(progress: component.position, animated: !isFirstTime) self.indicatorView.activeColor = component.activeColor self.indicatorView.inactiveColor = component.inactiveColor @@ -155,9 +156,9 @@ private final class PageIndicatorView: UIView { return CGSize(width: self.itemSize * CGFloat(self.displayCount), height: self.itemSize) } - public func setProgress(progress: CGFloat) { + public func setProgress(progress: CGFloat, animated: Bool = true) { let currentPage = Int(round(progress * CGFloat(self.pageCount - 1))) - self.setCurrentPage(at: currentPage, animated: true) + self.setCurrentPage(at: currentPage, animated: animated) } public func updateViewSize() { @@ -186,8 +187,7 @@ private final class PageIndicatorView: UIView { if currentPage < self.displayCount { self.items = (-2..<(self.displayCount + 2)) .map { ItemView(itemSize: self.itemSize, dotSize: self.dotSize, smallDotSizeRatio: self.smallDotSizeRatio, mediumDotSizeRatio: self.mediumDotSizeRatio, index: $0) } - } - else { + } else { guard let firstItem = self.items.first else { return } guard let lastItem = self.items.last else { return } self.items = (firstItem.index...lastItem.index) @@ -231,21 +231,34 @@ private final class PageIndicatorView: UIView { private func updateDotPosition(currentPage: Int, animated: Bool) { let duration = animated ? self.animationDuration : 0 - if currentPage == 0 { - let x = -self.scrollView.contentInset.left - self.moveScrollView(x: x, duration: duration) - } - else if currentPage == self.pageCount - 1 { - let x = self.scrollView.contentSize.width - self.scrollView.bounds.width + self.scrollView.contentInset.right - self.moveScrollView(x: x, duration: duration) - } - else if CGFloat(currentPage) * self.itemSize <= self.scrollView.contentOffset.x + self.itemSize { - let x = self.scrollView.contentOffset.x - self.itemSize - self.moveScrollView(x: x, duration: duration) - } - else if CGFloat(currentPage) * self.itemSize + self.itemSize >= self.scrollView.contentOffset.x + self.scrollView.bounds.width - self.itemSize { - let x = self.scrollView.contentOffset.x + self.itemSize - self.moveScrollView(x: x, duration: duration) + if !animated { + if currentPage == 0 { + let x = -self.scrollView.contentInset.left + self.moveScrollView(x: x) + } else { + for _ in 0 ..< currentPage { + let x = self.scrollView.contentOffset.x + self.itemSize + self.moveScrollView(x: x, duration: duration) + } + if currentPage == self.pageCount - 1 { + let x = self.scrollView.contentSize.width - self.scrollView.bounds.width + self.scrollView.contentInset.right + self.moveScrollView(x: x, duration: duration) + } + } + } else { + if currentPage == 0 { + let x = -self.scrollView.contentInset.left + self.moveScrollView(x: x, duration: duration) + } else if currentPage == self.pageCount - 1 { + let x = self.scrollView.contentSize.width - self.scrollView.bounds.width + self.scrollView.contentInset.right + self.moveScrollView(x: x, duration: duration) + } else if CGFloat(currentPage) * self.itemSize <= self.scrollView.contentOffset.x + self.itemSize { + let x = self.scrollView.contentOffset.x - self.itemSize + self.moveScrollView(x: x, duration: duration) + } else if CGFloat(currentPage) * self.itemSize + self.itemSize >= self.scrollView.contentOffset.x + self.scrollView.bounds.width - self.itemSize { + let x = self.scrollView.contentOffset.x + self.itemSize + self.moveScrollView(x: x, duration: duration) + } } } @@ -274,12 +287,16 @@ private final class PageIndicatorView: UIView { } } - private func moveScrollView(x: CGFloat, duration: TimeInterval) { + private func moveScrollView(x: CGFloat, duration: TimeInterval = 0.0) { let direction = self.behaviorDirection(x: x) self.reusedView(direction: direction) - UIView.animate(withDuration: duration, animations: { [unowned self] in + if duration > 0.0 { + UIView.animate(withDuration: duration, animations: { [unowned self] in + self.scrollView.contentOffset.x = x + }) + } else { self.scrollView.contentOffset.x = x - }) + } } private enum Direction { diff --git a/submodules/PremiumUI/Sources/PhoneDemoComponent.swift b/submodules/PremiumUI/Sources/PhoneDemoComponent.swift index 51be389a87..5a918550c9 100644 --- a/submodules/PremiumUI/Sources/PhoneDemoComponent.swift +++ b/submodules/PremiumUI/Sources/PhoneDemoComponent.swift @@ -370,6 +370,7 @@ final class PhoneDemoComponent: Component { case badgeStars case emoji case hello + case tag } enum Model { @@ -539,6 +540,13 @@ final class PhoneDemoComponent: Component { self.decorationView = starsView self.decorationContainerView.addSubview(starsView) } + case .tag: + if let _ = self.decorationView as? TagStarsView { + } else { + let starsView = TagStarsView(frame: self.decorationContainerView.bounds) + self.decorationView = starsView + self.decorationContainerView.addSubview(starsView) + } } self.phoneView.setup(context: component.context, videoFile: component.videoFile, position: component.position) diff --git a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift index b4aaafd297..68e9e37211 100644 --- a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift @@ -21,30 +21,39 @@ import BlurredBackgroundComponent import UndoUI import ConfettiEffect -func requiredBoostSubjectLevel(subject: BoostSubject, context: AccountContext, configuration: PremiumConfiguration) -> Int32 { +func requiredBoostSubjectLevel(subject: BoostSubject, group: Bool, context: AccountContext, configuration: PremiumConfiguration) -> Int32 { switch subject { case .stories: return 1 case let .channelReactions(reactionCount): return reactionCount case let .nameColors(colors): - if let value = context.peerNameColors.nameColorsChannelMinRequiredBoostLevel[colors.rawValue] { - return value + if group { + if let value = context.peerNameColors.nameColorsGroupMinRequiredBoostLevel[colors.rawValue] { + return value + } } else { - return 1 + if let value = context.peerNameColors.nameColorsChannelMinRequiredBoostLevel[colors.rawValue] { + return value + } } + return 1 case .nameIcon: return configuration.minChannelNameIconLevel case .profileColors: return configuration.minChannelProfileColorLevel case .profileIcon: - return configuration.minChannelProfileIconLevel + return group ? configuration.minGroupProfileIconLevel : configuration.minChannelProfileIconLevel case .emojiStatus: - return configuration.minChannelEmojiStatusLevel + return group ? configuration.minGroupEmojiStatusLevel : configuration.minChannelEmojiStatusLevel case .wallpaper: - return configuration.minChannelWallpaperLevel + return group ? configuration.minGroupWallpaperLevel : configuration.minChannelWallpaperLevel case .customWallpaper: - return configuration.minChannelCustomWallpaperLevel + return group ? configuration.minGroupCustomWallpaperLevel : configuration.minChannelCustomWallpaperLevel + case .audioTranscription: + return configuration.minGroupAudioTranscriptionLevel + case .emojiPack: + return configuration.minGroupEmojiPackLevel } } @@ -58,9 +67,11 @@ public enum BoostSubject: Equatable { case emojiStatus case wallpaper case customWallpaper + case audioTranscription + case emojiPack - public func requiredLevel(context: AccountContext, configuration: PremiumConfiguration) -> Int32 { - return requiredBoostSubjectLevel(subject: self, context: context, configuration: configuration) + public func requiredLevel(group: Bool, context: AccountContext, configuration: PremiumConfiguration) -> Int32 { + return requiredBoostSubjectLevel(subject: self, group: group, context: context, configuration: configuration) } } @@ -233,8 +244,10 @@ private final class LevelSectionComponent: CombinedComponent { case emojiStatus case wallpaper(Int32) case customWallpaper + case audioTranscription + case emojiPack - func title(strings: PresentationStrings) -> String { + func title(strings: PresentationStrings, isGroup: Bool) -> String { switch self { case let .story(value): return strings.ChannelBoost_Table_StoriesPerDay(value) @@ -243,9 +256,9 @@ private final class LevelSectionComponent: CombinedComponent { case let .nameColor(value): return strings.ChannelBoost_Table_NameColor(value) case let .profileColor(value): - return strings.ChannelBoost_Table_ProfileColor(value) + return isGroup ? strings.ChannelBoost_Table_Group_ProfileColor(value) : strings.ChannelBoost_Table_ProfileColor(value) case .profileIcon: - return strings.ChannelBoost_Table_ProfileLogo + return isGroup ? strings.ChannelBoost_Table_Group_ProfileLogo : strings.ChannelBoost_Table_ProfileLogo case let .linkColor(value): return strings.ChannelBoost_Table_StyleForHeaders(value) case .linkIcon: @@ -253,9 +266,13 @@ private final class LevelSectionComponent: CombinedComponent { case .emojiStatus: return strings.ChannelBoost_Table_EmojiStatus case let .wallpaper(value): - return strings.ChannelBoost_Table_Wallpaper(value) + return isGroup ? strings.ChannelBoost_Table_Group_Wallpaper(value) : strings.ChannelBoost_Table_Wallpaper(value) case .customWallpaper: - return strings.ChannelBoost_Table_CustomWallpaper + return isGroup ? strings.ChannelBoost_Table_Group_CustomWallpaper : strings.ChannelBoost_Table_CustomWallpaper + case .audioTranscription: + return "Voice-to-Text Conversion" + case .emojiPack: + return "Custom Emojipack" } } @@ -281,6 +298,10 @@ private final class LevelSectionComponent: CombinedComponent { return "Premium/BoostPerk/Wallpaper" case .customWallpaper: return "Premium/BoostPerk/CustomWallpaper" + case .audioTranscription: + return "Premium/BoostPerk/AudioTranscription" + case .emojiPack: + return "Premium/BoostPerk/EmojiPack" } } } @@ -290,19 +311,22 @@ private final class LevelSectionComponent: CombinedComponent { let level: Int32 let isFirst: Bool let perks: [Perk] + let isGroup: Bool init( theme: PresentationTheme, strings: PresentationStrings, level: Int32, isFirst: Bool, - perks: [Perk] + perks: [Perk], + isGroup: Bool ) { self.theme = theme self.strings = strings self.level = level self.isFirst = isFirst self.perks = perks + self.isGroup = isGroup } static func ==(lhs: LevelSectionComponent, rhs: LevelSectionComponent) -> Bool { @@ -318,6 +342,9 @@ private final class LevelSectionComponent: CombinedComponent { if lhs.perks != rhs.perks { return false } + if lhs.isGroup != rhs.isGroup { + return false + } return true } @@ -342,7 +369,7 @@ private final class LevelSectionComponent: CombinedComponent { LevelPerkComponent( theme: component.theme, iconName: value.iconName, - text: value.title(strings: component.strings) + text: value.title(strings: component.strings, isGroup: component.isGroup) ) ) ) @@ -371,12 +398,15 @@ private final class SheetContent: CombinedComponent { let peerId: EnginePeer.Id let mode: PremiumBoostLevelsScreen.Mode - let status: ChannelBoostStatus + let status: ChannelBoostStatus? + let boostState: InternalBoostState.DisplayData? + let boost: () -> Void let copyLink: (String) -> Void let dismiss: () -> Void let openStats: (() -> Void)? let openGift: (() -> Void)? + let openPeer: ((EnginePeer) -> Void)? init(context: AccountContext, theme: PresentationTheme, @@ -384,11 +414,14 @@ private final class SheetContent: CombinedComponent { insets: UIEdgeInsets, peerId: EnginePeer.Id, mode: PremiumBoostLevelsScreen.Mode, - status: ChannelBoostStatus, + status: ChannelBoostStatus?, + boostState: InternalBoostState.DisplayData?, + boost: @escaping () -> Void, copyLink: @escaping (String) -> Void, dismiss: @escaping () -> Void, openStats: (() -> Void)?, - openGift: (() -> Void)? + openGift: (() -> Void)?, + openPeer: ((EnginePeer) -> Void)? ) { self.context = context self.theme = theme @@ -397,10 +430,13 @@ private final class SheetContent: CombinedComponent { self.peerId = peerId self.mode = mode self.status = status + self.boostState = boostState + self.boost = boost self.copyLink = copyLink self.dismiss = dismiss self.openStats = openStats self.openGift = openGift + self.openPeer = openPeer } static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool { @@ -422,6 +458,9 @@ private final class SheetContent: CombinedComponent { if lhs.status != rhs.status { return false } + if lhs.boostState != rhs.boostState { + return false + } return true } @@ -465,7 +504,7 @@ private final class SheetContent: CombinedComponent { func makeState() -> State { var userId: EnginePeer.Id? - if case let .user(mode) = mode, case let .groupPeer(peerId) = mode { + if case let .user(mode) = mode, case let .groupPeer(peerId, _) = mode { userId = peerId } return State(context: self.context, peerId: self.peerId, userId: userId) @@ -474,7 +513,7 @@ private final class SheetContent: CombinedComponent { static var body: Body { let peerShortcut = Child(Button.self) let text = Child(BalancedTextComponent.self) - let alternateText = Child(BalancedTextComponent.self) + let alternateText = Child(List.self) let limit = Child(PremiumLimitDisplayComponent.self) let linkButton = Child(SolidRoundedButtonComponent.self) let boostButton = Child(SolidRoundedButtonComponent.self) @@ -499,84 +538,150 @@ private final class SheetContent: CombinedComponent { let textSideInset: CGFloat = 32.0 // + environment.safeInsets.left let iconName = "Premium/Boost" - let badgeText = "\(component.status.boosts)" - let peerName = state.peer?.compactDisplayTitle ?? "" - var remaining: Int? - if let nextLevelBoosts = component.status.nextLevelBoosts { - remaining = nextLevelBoosts - component.status.boosts + var isGroup = false + if let peer = state.peer, case let .channel(channel) = peer, case .group = channel.info { + isGroup = true } - var textString = "" + let level: Int + let boosts: Int + let remaining: Int? + let progress: CGFloat + let myBoostCount: Int + if let boostState = component.boostState { + level = Int(boostState.level) + boosts = Int(boostState.boosts) + if let nextLevelBoosts = boostState.nextLevelBoosts { + remaining = Int(nextLevelBoosts - boostState.boosts) + progress = CGFloat(boostState.boosts - boostState.currentLevelBoosts) / CGFloat(nextLevelBoosts - boostState.currentLevelBoosts) + } else { + remaining = nil + progress = 1.0 + } + myBoostCount = Int(boostState.myBoostCount) + } else if let status = component.status { + level = status.level + boosts = status.boosts + if let nextLevelBoosts = status.nextLevelBoosts { + remaining = nextLevelBoosts - status.boosts + progress = CGFloat(status.boosts - status.currentLevelBoosts) / CGFloat(nextLevelBoosts - status.currentLevelBoosts) + } else { + remaining = nil + progress = 1.0 + } + myBoostCount = 0 + } else { + level = 0 + boosts = 0 + remaining = nil + progress = 0.0 + myBoostCount = 0 + } + let badgeText = "\(boosts)" + + var textString = "" + + var isCurrent = false switch component.mode { case let .owner(subject): if let remaining { var needsSecondParagraph = true - let storiesString = strings.ChannelBoost_StoriesPerDay(Int32(component.status.level) + 1) - let valueString = strings.ChannelBoost_MoreBoosts(Int32(remaining)) - switch subject { - case .stories: - if component.status.level == 0 { - textString = strings.ChannelBoost_EnableStoriesText(valueString).string - } else { - textString = strings.ChannelBoost_IncreaseLimitText(valueString, storiesString).string + + if let subject { + let storiesString = strings.ChannelBoost_StoriesPerDay(Int32(level) + 1) + let valueString = strings.ChannelBoost_MoreBoosts(Int32(remaining)) + switch subject { + case .stories: + if level == 0 { + textString = strings.ChannelBoost_EnableStoriesText(valueString).string + } else { + textString = strings.ChannelBoost_IncreaseLimitText(valueString, storiesString).string + } + needsSecondParagraph = false + case let .channelReactions(reactionCount): + textString = strings.ChannelBoost_CustomReactionsText("\(reactionCount)", "\(reactionCount)").string + needsSecondParagraph = false + case .nameColors: + let colorLevel = subject.requiredLevel(group: isGroup, context: context.component.context, configuration: premiumConfiguration) + textString = strings.ChannelBoost_EnableNameColorLevelText("\(colorLevel)").string + case .nameIcon: + textString = strings.ChannelBoost_EnableNameIconLevelText("\(premiumConfiguration.minChannelNameIconLevel)").string + case .profileColors: + textString = strings.ChannelBoost_EnableProfileColorLevelText("\(premiumConfiguration.minChannelProfileColorLevel)").string + case .profileIcon: + textString = strings.ChannelBoost_EnableProfileIconLevelText("\(premiumConfiguration.minChannelProfileIconLevel)").string + case .emojiStatus: + textString = strings.ChannelBoost_EnableEmojiStatusLevelText("\(premiumConfiguration.minChannelEmojiStatusLevel)").string + case .wallpaper: + textString = strings.ChannelBoost_EnableWallpaperLevelText("\(premiumConfiguration.minChannelWallpaperLevel)").string + case .customWallpaper: + textString = strings.ChannelBoost_EnableCustomWallpaperLevelText("\(premiumConfiguration.minChannelCustomWallpaperLevel)").string + case .audioTranscription: + textString = "" + case .emojiPack: + textString = strings.ChannelBoost_EnableGroupEmojiPackLevelText("\(premiumConfiguration.minGroupEmojiPackLevel)").string + } + } else { + let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining)) + if myBoostCount > 0 { + textString = strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string + } else { + textString = strings.ChannelBoost_MoreBoostsNeeded_Text(peerName, boostsString).string } - needsSecondParagraph = false - case let .channelReactions(reactionCount): - textString = strings.ChannelBoost_CustomReactionsText("\(reactionCount)", "\(reactionCount)").string - needsSecondParagraph = false - case .nameColors: - let colorLevel = subject.requiredLevel(context: context.component.context, configuration: premiumConfiguration) - textString = strings.ChannelBoost_EnableNameColorLevelText("\(colorLevel)").string - case .nameIcon: - textString = strings.ChannelBoost_EnableNameIconLevelText("\(premiumConfiguration.minChannelNameIconLevel)").string - case .profileColors: - textString = strings.ChannelBoost_EnableProfileColorLevelText("\(premiumConfiguration.minChannelProfileColorLevel)").string - case .profileIcon: - textString = strings.ChannelBoost_EnableProfileIconLevelText("\(premiumConfiguration.minChannelProfileIconLevel)").string - case .emojiStatus: - textString = strings.ChannelBoost_EnableEmojiStatusLevelText("\(premiumConfiguration.minChannelEmojiStatusLevel)").string - case .wallpaper: - textString = strings.ChannelBoost_EnableWallpaperLevelText("\(premiumConfiguration.minChannelWallpaperLevel)").string - case .customWallpaper: - textString = strings.ChannelBoost_EnableCustomWallpaperLevelText("\(premiumConfiguration.minChannelCustomWallpaperLevel)").string } if needsSecondParagraph { textString += "\n\n\(strings.ChannelBoost_AskToBoost)" } } else { - let storiesString = strings.ChannelBoost_StoriesPerDay(Int32(component.status.level)) - textString = strings.ChannelBoost_MaxLevelReachedTextAuthor("\(component.status.level)", storiesString).string + textString = strings.ChannelBoost_MaxLevelReached_Text(peerName, "\(level)").string +// let storiesString = strings.ChannelBoost_StoriesPerDay(Int32(level)) +// textString = strings.ChannelBoost_MaxLevelReachedTextAuthor("\(level)", storiesString).string } case let .user(mode): - if case .groupPeer = mode { + switch mode { + case let .groupPeer(_, peerBoostCount): let memberName = state.memberPeer?.compactDisplayTitle ?? "" - textString = "**\(memberName)** boosted the group **\(2)** times. Boost **\(peerName)** to help it unlock new features and get a booster **badge** for your messages." - } else { + //TODO:localize + if myBoostCount > 0 { + if let remaining { + let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining)) + textString = "**\(memberName)** boosted the group **\(peerBoostCount)** times. \(strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string)" + } else { + textString = "**\(memberName)** boosted the group **\(peerBoostCount)** times." + } + } else { + textString = "**\(memberName)** boosted the group **\(peerBoostCount)** times. Boost **\(peerName)** to help it unlock new features and get a booster **badge** for your messages." + } + isCurrent = true + case let .unrestrict(unrestrictCount): + textString = "Boost the group \(unrestrictCount) times to remove messaging restrictions. Your boosts will help \(peerName) to unlock new features." + isCurrent = true + default: if let remaining { let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining)) - textString = strings.ChannelBoost_MoreBoostsNeeded_Text(peerName, boostsString).string + if myBoostCount > 0 { + textString = strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string + } else { + textString = strings.ChannelBoost_MoreBoostsNeeded_Text(peerName, boostsString).string + } } else { - textString = strings.ChannelBoost_MaxLevelReached_Text(peerName, "\(component.status.level)").string + textString = strings.ChannelBoost_MaxLevelReached_Text(peerName, "\(level)").string } + isCurrent = mode == .current } + case .features: + textString = "" } - let defaultTitle = strings.ChannelBoost_Level("\(component.status.level)").string + let defaultTitle = strings.ChannelBoost_Level("\(level)").string let defaultValue = "" - let premiumValue = strings.ChannelBoost_Level("\(component.status.level + 1)").string + let premiumValue = strings.ChannelBoost_Level("\(level + 1)").string let premiumTitle = "" - let progress: CGFloat - if let nextLevelBoosts = component.status.nextLevelBoosts { - progress = CGFloat(component.status.boosts - component.status.currentLevelBoosts) / CGFloat(nextLevelBoosts - component.status.currentLevelBoosts) - } else { - progress = 1.0 - } - var contentSize: CGSize = CGSize(width: context.availableSize.width, height: 44.0) let textFont = Font.regular(15.0) @@ -612,10 +717,10 @@ private final class SheetContent: CombinedComponent { ) ), action: { -// component.dismiss() -// Queue.mainQueue().after(0.35) { -// component.openPeer(peer) -// } + component.dismiss() + Queue.mainQueue().after(0.35) { + component.openPeer?(peer) + } } ), availableSize: CGSize(width: context.availableSize.width - 32.0, height: context.availableSize.height), @@ -627,71 +732,119 @@ private final class SheetContent: CombinedComponent { contentSize.height += peerShortcut.size.height + 2.0 } - let limit = limit.update( - component: PremiumLimitDisplayComponent( - inactiveColor: theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.3), - activeColors: gradientColors, - inactiveTitle: defaultTitle, - inactiveValue: defaultValue, - inactiveTitleColor: theme.list.itemPrimaryTextColor, - activeTitle: premiumTitle, - activeValue: premiumValue, - activeTitleColor: .white, - badgeIconName: iconName, - badgeText: badgeText, - badgePosition: progress, - badgeGraphPosition: progress, - invertProgress: true, - isPremiumDisabled: false - ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), - transition: context.transition - ) - context.add(limit - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + limit.size.height / 2.0)) - ) - - contentSize.height += limit.size.height + 23.0 - - let textChild: _ConcreteChildComponent - if component.status.boosts % 2 == 0 { - textChild = text + if case .features = component.mode { + contentSize.height += 17.0 } else { - textChild = alternateText + let limit = limit.update( + component: PremiumLimitDisplayComponent( + inactiveColor: theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.3), + activeColors: gradientColors, + inactiveTitle: defaultTitle, + inactiveValue: defaultValue, + inactiveTitleColor: theme.list.itemPrimaryTextColor, + activeTitle: premiumTitle, + activeValue: premiumValue, + activeTitleColor: .white, + badgeIconName: iconName, + badgeText: badgeText, + badgePosition: progress, + badgeGraphPosition: progress, + invertProgress: true, + isPremiumDisabled: false + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), + transition: context.transition + ) + context.add(limit + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + limit.size.height / 2.0)) + ) + + contentSize.height += limit.size.height + 23.0 + + if myBoostCount > 0 { + let alternateTitle = isCurrent ? strings.ChannelBoost_YouBoostedChannelText(peerName).string : strings.ChannelBoost_YouBoostedOtherChannelText + + var alternateBadge: String? + if myBoostCount > 1 { + alternateBadge = "X\(myBoostCount)" + } + + let alternateText = alternateText.update( + component: List( + [ + AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + BoostedTitleContent(text: NSAttributedString(string: alternateTitle, font: Font.semibold(15.0), textColor: textColor), badge: alternateBadge) + ) + ), + AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + BalancedTextComponent( + text: .markdown(text: textString, attributes: markdownAttributes), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.1 + ) + ) + ) + ], + centerAlignment: true + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(alternateText + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + alternateText.size.height / 2.0)) + .appear(Transition.Appear({ _, view, transition in + transition.animatePosition(view: view, from: CGPoint(x: 0.0, y: 64.0), to: .zero, additive: true) + transition.animateAlpha(view: view, from: 0.0, to: 1.0) + })) + .disappear(Transition.Disappear({ view, transition, completion in + view.superview?.sendSubviewToBack(view) + transition.animatePosition(view: view, from: .zero, to: CGPoint(x: 0.0, y: -64.0), additive: true) + transition.setAlpha(view: view, alpha: 0.0, completion: { _ in + completion() + }) + })) + ) + contentSize.height += alternateText.size.height + 13.0 + } else { + let text = text.update( + component: BalancedTextComponent( + text: .markdown(text: textString, attributes: markdownAttributes), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(text + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0)) + .appear(Transition.Appear({ _, view, transition in + transition.animatePosition(view: view, from: CGPoint(x: 0.0, y: 64.0), to: .zero, additive: true) + transition.animateAlpha(view: view, from: 0.0, to: 1.0) + })) + .disappear(Transition.Disappear({ view, transition, completion in + view.superview?.sendSubviewToBack(view) + transition.animatePosition(view: view, from: .zero, to: CGPoint(x: 0.0, y: -64.0), additive: true) + transition.setAlpha(view: view, alpha: 0.0, completion: { _ in + completion() + }) + })) + ) + contentSize.height += text.size.height + 13.0 + } } - - let text = textChild.update( - component: BalancedTextComponent( - text: .markdown(text: textString, attributes: markdownAttributes), - horizontalAlignment: .center, - maximumNumberOfLines: 0, - lineSpacing: 0.2 - ), - availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), - transition: .immediate - ) - context.add(text - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0)) - .appear(Transition.Appear({ _, view, transition in - transition.animatePosition(view: view, from: CGPoint(x: 0.0, y: 64.0), to: .zero, additive: true) - transition.animateAlpha(view: view, from: 0.0, to: 1.0) - })) - .disappear(Transition.Disappear({ view, transition, completion in - view.superview?.sendSubviewToBack(view) - transition.animatePosition(view: view, from: .zero, to: CGPoint(x: 0.0, y: -64.0), additive: true) - transition.setAlpha(view: view, alpha: 0.0, completion: { _ in - completion() - }) - })) - ) - contentSize.height += text.size.height + 13.0 - - if case .owner = component.mode { + + if case .owner = component.mode, let status = component.status { contentSize.height += 7.0 let linkButton = linkButton.update( component: SolidRoundedButtonComponent( - title: component.status.url.replacingOccurrences(of: "https://", with: ""), + title: status.url.replacingOccurrences(of: "https://", with: ""), theme: SolidRoundedButtonComponent.Theme( backgroundColor: theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.3), backgroundColors: [], @@ -702,7 +855,7 @@ private final class SheetContent: CombinedComponent { height: 50.0, cornerRadius: 10.0, action: { - component.copyLink(component.status.url) + component.copyLink(status.url) component.dismiss() } ), @@ -732,7 +885,7 @@ private final class SheetContent: CombinedComponent { animationName: nil, iconPosition: .left, action: { - + component.boost() component.dismiss() } ), @@ -757,7 +910,7 @@ private final class SheetContent: CombinedComponent { animationName: nil, iconPosition: .left, action: { - component.copyLink(component.status.url) + component.copyLink(status.url) component.dismiss() } ), @@ -807,7 +960,7 @@ private final class SheetContent: CombinedComponent { state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme) } - let giftString = strings.Premium_BoostByGiftDescription2 + let giftString = isGroup ? strings.Premium_Group_BoostByGiftDescription : strings.Premium_BoostByGiftDescription2 let giftAttributedString = parseMarkdownIntoAttributedString(giftString, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString if let range = giftAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 { @@ -838,15 +991,10 @@ private final class SheetContent: CombinedComponent { } var nextLevels: ClosedRange? - if component.status.level < 10 { - nextLevels = Int32(component.status.level) + 1 ... 10 + if level < 10 { + nextLevels = Int32(level) + 1 ... 10 } - - var isGroup = false - if case let .user(mode) = component.mode, case .groupPeer = mode { - isGroup = true - } - + var levelItems: [AnyComponentWithIdentity] = [] var nameColorsAtLevel: [(Int32, Int32)] = [] @@ -863,10 +1011,16 @@ private final class SheetContent: CombinedComponent { for (key, value) in nameColorsCountMap { nameColorsAtLevel.append((key, value)) } + + var isFeatures = false + if case .features = component.mode { + isFeatures = true + } if let nextLevels { for level in nextLevels { var perks: [LevelSectionComponent.Perk] = [] + perks.append(.story(level)) if !isGroup { @@ -883,6 +1037,10 @@ private final class SheetContent: CombinedComponent { perks.append(.nameColor(nameColorsCount)) } + if isGroup && level >= premiumConfiguration.minGroupAudioTranscriptionLevel { + perks.append(.audioTranscription) + } + if level >= premiumConfiguration.minChannelProfileColorLevel { let delta = min(level - premiumConfiguration.minChannelProfileColorLevel + 1, 2) perks.append(.profileColor(8 * delta)) @@ -891,6 +1049,10 @@ private final class SheetContent: CombinedComponent { perks.append(.profileIcon) } + if isGroup && level >= premiumConfiguration.minGroupAudioTranscriptionLevel { + perks.append(.emojiPack) + } + var linkColorsCount: Int32 = 0 for (colorLevel, count) in nameColorsAtLevel { if level >= colorLevel { @@ -921,8 +1083,9 @@ private final class SheetContent: CombinedComponent { theme: component.theme, strings: component.strings, level: level, - isFirst: levelItems.isEmpty, - perks: perks + isFirst: !isFeatures && levelItems.isEmpty, + perks: perks.reversed(), + isGroup: isGroup ) ) ) @@ -955,11 +1118,14 @@ private final class BoostLevelsContainerComponent: CombinedComponent { let peerId: EnginePeer.Id let mode: PremiumBoostLevelsScreen.Mode - let status: ChannelBoostStatus + let status: ChannelBoostStatus? + let boostState: InternalBoostState.DisplayData? + let boost: () -> Void let copyLink: (String) -> Void let dismiss: () -> Void let openStats: (() -> Void)? let openGift: (() -> Void)? + let openPeer: ((EnginePeer) -> Void)? init( context: AccountContext, @@ -967,11 +1133,14 @@ private final class BoostLevelsContainerComponent: CombinedComponent { strings: PresentationStrings, peerId: EnginePeer.Id, mode: PremiumBoostLevelsScreen.Mode, - status: ChannelBoostStatus, + status: ChannelBoostStatus?, + boostState: InternalBoostState.DisplayData?, + boost: @escaping () -> Void, copyLink: @escaping (String) -> Void, dismiss: @escaping () -> Void, openStats: (() -> Void)?, - openGift: (() -> Void)? + openGift: (() -> Void)?, + openPeer: ((EnginePeer) -> Void)? ) { self.context = context self.theme = theme @@ -979,10 +1148,13 @@ private final class BoostLevelsContainerComponent: CombinedComponent { self.peerId = peerId self.mode = mode self.status = status + self.boostState = boostState + self.boost = boost self.copyLink = copyLink self.dismiss = dismiss self.openStats = openStats self.openGift = openGift + self.openPeer = openPeer } static func ==(lhs: BoostLevelsContainerComponent, rhs: BoostLevelsContainerComponent) -> Bool { @@ -1001,6 +1173,9 @@ private final class BoostLevelsContainerComponent: CombinedComponent { if lhs.status != rhs.status { return false } + if lhs.boostState != rhs.boostState { + return false + } return true } @@ -1008,10 +1183,30 @@ private final class BoostLevelsContainerComponent: CombinedComponent { var topContentOffset: CGFloat = 0.0 var cachedStatsImage: (UIImage, PresentationTheme)? var cachedCloseImage: (UIImage, PresentationTheme)? + + private var disposable: Disposable? + private(set) var peer: EnginePeer? + + init(context: AccountContext, peerId: EnginePeer.Id) { + super.init() + + self.disposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).startStrict(next: { [weak self] peer in + guard let self else { + return + } + self.peer = peer + self.updated() + }) + } + + deinit { + self.disposable?.dispose() + } } func makeState() -> State { - return State() + return State(context: self.context, peerId: self.peerId) } static var body: Body { @@ -1033,6 +1228,11 @@ private final class BoostLevelsContainerComponent: CombinedComponent { let component = context.component + var isGroup = false + if let peer = state.peer, case let .channel(channel) = peer, case .group = channel.info { + isGroup = true + } + let scroll = scroll.update( component: ScrollComponent( content: AnyComponent( @@ -1044,10 +1244,13 @@ private final class BoostLevelsContainerComponent: CombinedComponent { peerId: component.peerId, mode: component.mode, status: component.status, + boostState: component.boostState, + boost: component.boost, copyLink: component.copyLink, dismiss: component.dismiss, openStats: component.openStats, - openGift: component.openGift + openGift: component.openGift, + openPeer: component.openPeer ) ), contentInsets: UIEdgeInsets(top: topInset, left: 0.0, bottom: 34.0, right: 0.0), @@ -1095,52 +1298,61 @@ private final class BoostLevelsContainerComponent: CombinedComponent { let titleString: String switch component.mode { case let .owner(subject): - if let _ = component.status.nextLevelBoosts { - switch subject { - case .stories: - if component.status.level == 0 { - titleString = strings.ChannelBoost_EnableStories - } else { - titleString = strings.ChannelBoost_IncreaseLimit + if let status = component.status, let _ = status.nextLevelBoosts { + if let subject { + switch subject { + case .stories: + if status.level == 0 { + titleString = strings.ChannelBoost_EnableStories + } else { + titleString = strings.ChannelBoost_IncreaseLimit + } + case .nameColors: + titleString = strings.ChannelBoost_NameColor + case .nameIcon: + titleString = strings.ChannelBoost_NameIcon + case .profileColors: + titleString = strings.ChannelBoost_ProfileColor + case .profileIcon: + titleString = strings.ChannelBoost_ProfileIcon + case .channelReactions: + titleString = strings.ChannelBoost_CustomReactions + case .emojiStatus: + titleString = strings.ChannelBoost_EmojiStatus + case .wallpaper: + titleString = strings.ChannelBoost_Wallpaper + case .customWallpaper: + titleString = strings.ChannelBoost_CustomWallpaper + case .audioTranscription: + //TODO:localize + titleString = "Audio Transcription" + case .emojiPack: + titleString = "Set Group Emoji Pack" } - case .nameColors: - titleString = strings.ChannelBoost_NameColor - case .nameIcon: - titleString = strings.ChannelBoost_NameIcon - case .profileColors: - titleString = strings.ChannelBoost_ProfileColor - case .profileIcon: - titleString = strings.ChannelBoost_ProfileIcon - case .channelReactions: - titleString = strings.ChannelBoost_CustomReactions - case .emojiStatus: - titleString = strings.ChannelBoost_EmojiStatus - case .wallpaper: - titleString = strings.ChannelBoost_Wallpaper - case .customWallpaper: - titleString = strings.ChannelBoost_CustomWallpaper + } else { + titleString = isGroup ? strings.GroupBoost_Title_Current : strings.ChannelBoost_Title_Current } } else { titleString = strings.ChannelBoost_MaxLevelReached } case let .user(mode): var remaining: Int? - if let nextLevelBoosts = component.status.nextLevelBoosts { - remaining = nextLevelBoosts - component.status.boosts + if let status = component.status, let nextLevelBoosts = status.nextLevelBoosts { + remaining = nextLevelBoosts - status.boosts } if let _ = remaining { if case .current = mode { - titleString = "Boost Group" - //titleString = strings.ChannelBoost_Title_Current - } else if case .groupPeer = mode { - titleString = "Boost Group" + titleString = isGroup ? strings.GroupBoost_Title_Current : strings.ChannelBoost_Title_Current } else { - titleString = strings.ChannelBoost_Title_Other + titleString = isGroup ? strings.GroupBoost_Title_Other : strings.ChannelBoost_Title_Other } } else { titleString = strings.ChannelBoost_MaxLevelReached } + case .features: + //TODO:localize + titleString = "Features" } let title = title.update( @@ -1222,10 +1434,12 @@ public class PremiumBoostLevelsScreen: ViewController { public enum UserMode: Equatable { case external case current - case groupPeer(EnginePeer.Id) + case groupPeer(EnginePeer.Id, Int) + case unrestrict(Int) } case user(mode: UserMode) - case owner(subject: BoostSubject) + case owner(subject: BoostSubject?) + case features } final class Node: ViewControllerTracingNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { @@ -1280,9 +1494,53 @@ public class PremiumBoostLevelsScreen: ViewController { self.wrappingView.addSubview(self.containerView) self.containerView.addSubview(self.contentView) - if case .user = controller.mode { + if case .user = controller.mode, let status = controller.status { self.containerView.addSubview(self.footerContainerView) self.footerContainerView.addSubview(self.footerView) + + var myBoostCount: Int32 = 0 + var currentMyBoostCount: Int32 = 0 + var availableBoosts: [MyBoostStatus.Boost] = [] + var occupiedBoosts: [MyBoostStatus.Boost] = [] + if let myBoostStatus = controller.myBoostStatus { + for boost in myBoostStatus.boosts { + if let boostPeer = boost.peer { + if boostPeer.id == controller.peerId { + myBoostCount += 1 + } else { + occupiedBoosts.append(boost) + } + } else { + availableBoosts.append(boost) + } + } + } + + let boosts = max(Int32(status.boosts), myBoostCount) + let initialState = InternalBoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: boosts) + self.boostState = initialState.displayData(myBoostCount: myBoostCount, currentMyBoostCount: 0, replacedBoosts: controller.replacedBoosts?.0) + + self.updatedState.set(.single(InternalBoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: boosts + 1))) + + if let (replacedBoosts, inChannels) = controller.replacedBoosts { + currentMyBoostCount += 1 + + self.boostState = initialState.displayData(myBoostCount: myBoostCount, currentMyBoostCount: 1) + Queue.mainQueue().justDispatch { + self.updated(transition: .easeInOut(duration: 0.2)) + } + + Queue.mainQueue().after(0.3) { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let undoController = UndoOverlayController(presentationData: presentationData, content: .image(image: generateTintedImage(image: UIImage(bundleImageName: "Premium/BoostReplaceIcon"), color: .white)!, title: nil, text: presentationData.strings.ReassignBoost_Success(presentationData.strings.ReassignBoost_Boosts(replacedBoosts), presentationData.strings.ReassignBoost_OtherChannels(inChannels)).string, round: false, undoText: nil), elevatedLayout: false, position: .top, action: { _ in return true }) + controller.present(undoController, in: .current) + } + } + + self.availableBoosts = availableBoosts + self.occupiedBoosts = occupiedBoosts + self.myBoostCount = myBoostCount + self.currentMyBoostCount = currentMyBoostCount } } @@ -1444,7 +1702,7 @@ public class PremiumBoostLevelsScreen: ViewController { self.updated(transition: transition) } - private var updatedStatus: ChannelBoostStatus? + private var boostState: InternalBoostState.DisplayData? func updated(transition: Transition) { guard let controller = self.controller, let layout = self.currentLayout else { return @@ -1458,7 +1716,25 @@ public class PremiumBoostLevelsScreen: ViewController { strings: self.presentationData.strings, peerId: controller.peerId, mode: controller.mode, - status: self.updatedStatus ?? controller.status, + status: controller.status, + boostState: self.boostState, + boost: { [weak controller] in + guard let controller, let navigationController = controller.navigationController else { + return + } + + controller.dismiss(animated: true) + + let boostController = PremiumBoostLevelsScreen( + context: controller.context, + peerId: controller.peerId, + mode: .user(mode: .current), + status: controller.status, + myBoostStatus: nil, + forceDark: controller.forceDark + ) + navigationController.pushViewController(boostController, animated: true) + }, copyLink: { [weak self, weak controller] link in guard let self else { return @@ -1473,7 +1749,8 @@ public class PremiumBoostLevelsScreen: ViewController { controller?.dismiss(animated: true) }, openStats: controller.openStats, - openGift: controller.openGift + openGift: controller.openGift, + openPeer: controller.openPeer ) ), environment: {}, @@ -1490,16 +1767,12 @@ public class PremiumBoostLevelsScreen: ViewController { FooterComponent( context: controller.context, theme: self.presentationData.theme, - title: self.updatedStatus != nil ? "Boost Again" : "Boost Group", + title: self.currentMyBoostCount > 0 ? "Boost Again" : "Boost Group", action: { [weak self] in guard let self else { return } - if let status = self.controller?.status { - self.updatedStatus = status.withUpdated(boosts: status.boosts + 1) - } - self.animateSuccess() - self.updated(transition: .easeInOut(duration: 0.2)) + self.buttonPressed() } ) ), @@ -1751,7 +2024,219 @@ public class PremiumBoostLevelsScreen: ViewController { self.containerLayoutUpdated(layout: layout, transition: Transition(transition)) } - public func animateSuccess() { + private var currentMyBoostCount: Int32 = 0 + private var myBoostCount: Int32 = 0 + private var availableBoosts: [MyBoostStatus.Boost] = [] + private var occupiedBoosts: [MyBoostStatus.Boost] = [] + private let updatedState = Promise() + + private func updateBoostState() { + guard let controller = self.controller else { + return + } + let context = controller.context + let peerId = controller.peerId + let mode = controller.mode + let status = controller.status + let isPremium = controller.context.isPremium + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with({ $0 })) + let canBoostAgain = premiumConfiguration.boostsPerGiftCount > 0 + let presentationData = self.presentationData + let forceDark = controller.forceDark + + if let _ = status?.nextLevelBoosts { + if let availableBoost = self.availableBoosts.first { + self.currentMyBoostCount += 1 + self.myBoostCount += 1 + + let _ = (context.engine.peers.applyChannelBoost(peerId: peerId, slots: [availableBoost.slot]) + |> deliverOnMainQueue).startStandalone(completed: { [weak self] in + self?.updatedState.set(context.engine.peers.getChannelBoostStatus(peerId: peerId) + |> map { status in + if let status { + return InternalBoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: Int32(status.boosts + 1)) + } else { + return nil + } + }) + }) + + let _ = (self.updatedState.get() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] state in + guard let self, let state else { + return + } + self.boostState = state.displayData(myBoostCount: self.myBoostCount, currentMyBoostCount: self.currentMyBoostCount) + self.updated(transition: .easeInOut(duration: 0.2)) + + self.animateSuccess() + }) + + self.availableBoosts.removeFirst() + } else if !self.occupiedBoosts.isEmpty, let myBoostStatus = controller.myBoostStatus { + if canBoostAgain { + let navigationController = controller.navigationController + let openPeer = controller.openPeer + + var dismissReplaceImpl: (() -> Void)? + let replaceController = ReplaceBoostScreen(context: context, peerId: peerId, myBoostStatus: myBoostStatus, replaceBoosts: { slots in + var channelIds = Set() + for boost in myBoostStatus.boosts { + if slots.contains(boost.slot) { + if let peer = boost.peer { + channelIds.insert(peer.id) + } + } + } + + let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: slots).startStandalone(completed: { + let _ = combineLatest( + queue: Queue.mainQueue(), + context.engine.peers.getChannelBoostStatus(peerId: peerId), + context.engine.peers.getMyBoostStatus() + ).startStandalone(next: { boostStatus, myBoostStatus in + dismissReplaceImpl?() + + let levelsController = PremiumBoostLevelsScreen( + context: context, + peerId: peerId, + mode: mode, + status: status, + myBoostStatus: myBoostStatus, + replacedBoosts: (Int32(slots.count), Int32(channelIds.count)), + openStats: nil, openGift: nil, openPeer: openPeer, forceDark: forceDark + ) + if let navigationController { + navigationController.pushViewController(levelsController, animated: true) + } + }) + }) + }) + + if let navigationController = controller.navigationController { + controller.dismiss(animated: true) + navigationController.pushViewController(replaceController, animated: true) + } + + dismissReplaceImpl = { [weak replaceController] in + replaceController?.dismiss(animated: true) + } + } else if let boost = self.occupiedBoosts.first, let occupiedPeer = boost.peer { + if let cooldown = boost.cooldownUntil { + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + let timeout = cooldown - currentTime + let valueText = timeIntervalString(strings: presentationData.strings, value: timeout, usage: .afterTime, preferLowerValue: false) + let alertController = textAlertController( + sharedContext: context.sharedContext, + updatedPresentationData: nil, + title: presentationData.strings.ChannelBoost_Error_BoostTooOftenTitle, + text: presentationData.strings.ChannelBoost_Error_BoostTooOftenText(valueText).string, + actions: [ + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}) + ], + parseMarkdown: true + ) + controller.present(alertController, in: .window(.root)) + } else { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { [weak controller] peer in + guard let peer, let controller else { + return + } + let replaceController = replaceBoostConfirmationController(context: context, fromPeers: [occupiedPeer], toPeer: peer, commit: { [weak self] in + self?.currentMyBoostCount += 1 + self?.myBoostCount += 1 + let _ = (context.engine.peers.applyChannelBoost(peerId: peerId, slots: [boost.slot]) + |> deliverOnMainQueue).startStandalone(completed: { [weak self] in + guard let self else { + return + } + let _ = (self.updatedState.get() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] state in + guard let self, let state else { + return + } + self.boostState = state.displayData(myBoostCount: self.myBoostCount, currentMyBoostCount: self.currentMyBoostCount) + self.updated(transition: .easeInOut(duration: 0.2)) + + self.animateSuccess() + }) + }) + }) + controller.present(replaceController, in: .window(.root)) + }) + } + } else { + controller.dismiss(animated: true, completion: nil) + } + } else { + if isPremium { + if !canBoostAgain { + controller.dismiss(animated: true, completion: nil) + } else { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { [weak controller] peer in + guard let peer, let controller else { + return + } + let alertController = textAlertController( + sharedContext: context.sharedContext, + updatedPresentationData: nil, + title: presentationData.strings.ChannelBoost_MoreBoosts_Title, + text: presentationData.strings.ChannelBoost_MoreBoosts_Text(peer.compactDisplayTitle, "\(premiumConfiguration.boostsPerGiftCount)").string, + actions: [ + TextAlertAction(type: .defaultAction, title: presentationData.strings.ChannelBoost_MoreBoosts_Gift, action: { [weak controller] in + if let navigationController = controller?.navigationController { + controller?.dismiss(animated: true, completion: nil) + + Queue.mainQueue().after(0.4) { + let giftController = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost) + navigationController.pushViewController(giftController, animated: true) + } + } + }), + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Close, action: {}) + ], + actionLayout: .vertical, + parseMarkdown: true + ) + controller.present(alertController, in: .window(.root)) + }) + } + } else { + let alertController = textAlertController( + sharedContext: context.sharedContext, + updatedPresentationData: nil, + title: presentationData.strings.ChannelBoost_Error_PremiumNeededTitle, + text: presentationData.strings.ChannelBoost_Error_PremiumNeededText, + actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { [weak controller] in + if let navigationController = controller?.navigationController { + controller?.dismiss(animated: true) + + let premiumController = context.sharedContext.makePremiumIntroController(context: context, source: .channelBoost(peerId), forceDark: forceDark, dismissed: nil) + navigationController.pushViewController(premiumController, animated: true) + } + }) + ], + parseMarkdown: true + ) + controller.present(alertController, in: .window(.root)) + } + } + } else { + controller.dismiss(animated: true) + } + } + + func buttonPressed() { + self.updateBoostState() + } + + private func animateSuccess() { self.hapticFeedback.impact() self.view.addSubview(ConfettiView(frame: self.view.bounds)) @@ -1768,10 +2253,12 @@ public class PremiumBoostLevelsScreen: ViewController { private let context: AccountContext private let peerId: EnginePeer.Id private let mode: Mode - private let status: ChannelBoostStatus + private let status: ChannelBoostStatus? private let myBoostStatus: MyBoostStatus? + private let replacedBoosts: (Int32, Int32)? private let openStats: (() -> Void)? private let openGift: (() -> Void)? + private let openPeer: ((EnginePeer) -> Void)? private let forceDark: Bool private var currentLayout: ContainerViewLayout? @@ -1782,10 +2269,12 @@ public class PremiumBoostLevelsScreen: ViewController { context: AccountContext, peerId: EnginePeer.Id, mode: Mode, - status: ChannelBoostStatus, + status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus? = nil, + replacedBoosts: (Int32, Int32)? = nil, openStats: (() -> Void)? = nil, openGift: (() -> Void)? = nil, + openPeer: ((EnginePeer) -> Void)? = nil, forceDark: Bool = false ) { self.context = context @@ -1793,8 +2282,10 @@ public class PremiumBoostLevelsScreen: ViewController { self.mode = mode self.status = status self.myBoostStatus = myBoostStatus + self.replacedBoosts = replacedBoosts self.openStats = openStats self.openGift = openGift + self.openPeer = openPeer self.forceDark = forceDark super.init(navigationBarPresentationData: nil) @@ -1812,11 +2303,7 @@ public class PremiumBoostLevelsScreen: ViewController { deinit { self.disposed() } - - @objc private func cancelPressed() { - self.dismiss(animated: true, completion: nil) - } - + override open func loadDisplayNode() { self.displayNode = Node(context: self.context, controller: self) self.displayNodeDidLoad() @@ -1974,3 +2461,42 @@ private final class FooterComponent: Component { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } + +private struct InternalBoostState: Equatable { + let level: Int32 + let currentLevelBoosts: Int32 + let nextLevelBoosts: Int32? + let boosts: Int32 + + struct DisplayData: Equatable { + let level: Int32 + let boosts: Int32 + let currentLevelBoosts: Int32 + let nextLevelBoosts: Int32? + let myBoostCount: Int32 + } + + func displayData(myBoostCount: Int32, currentMyBoostCount: Int32, replacedBoosts: Int32? = nil) -> DisplayData { + var currentLevel = self.level + var nextLevelBoosts = self.nextLevelBoosts + var currentLevelBoosts = self.currentLevelBoosts + var boosts = self.boosts + if let replacedBoosts { + boosts = max(currentLevelBoosts, boosts - replacedBoosts) + } + + if currentMyBoostCount > 0 && self.boosts == currentLevelBoosts { + currentLevel = max(0, currentLevel - 1) + nextLevelBoosts = currentLevelBoosts + currentLevelBoosts = max(0, currentLevelBoosts - 1) + } + + return DisplayData( + level: currentLevel, + boosts: boosts, + currentLevelBoosts: currentLevelBoosts, + nextLevelBoosts: nextLevelBoosts, + myBoostCount: myBoostCount + ) + } +} diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index 575aa94ab4..470f4d2f62 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -18,7 +18,7 @@ import BlurredBackgroundComponent import Markdown import TelegramUIPreferences -final class GradientBackgroundComponent: Component { +public final class PremiumGradientBackgroundComponent: Component { public let colors: [UIColor] public init( @@ -27,7 +27,7 @@ final class GradientBackgroundComponent: Component { self.colors = colors } - public static func ==(lhs: GradientBackgroundComponent, rhs: GradientBackgroundComponent) -> Bool { + public static func ==(lhs: PremiumGradientBackgroundComponent, rhs: PremiumGradientBackgroundComponent) -> Bool { if lhs.colors != rhs.colors { return false } @@ -38,7 +38,7 @@ final class GradientBackgroundComponent: Component { private let clipLayer: CALayer private let gradientLayer: CAGradientLayer - private var component: GradientBackgroundComponent? + private var component: PremiumGradientBackgroundComponent? override init(frame: CGRect) { self.clipLayer = CALayer() @@ -58,7 +58,7 @@ final class GradientBackgroundComponent: Component { } - func update(component: GradientBackgroundComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + func update(component: PremiumGradientBackgroundComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { self.clipLayer.frame = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: availableSize.height + 10.0)) self.gradientLayer.frame = CGRect(origin: .zero, size: availableSize) @@ -462,7 +462,6 @@ private final class DemoSheetContent: CombinedComponent { let context: AccountContext let subject: PremiumDemoScreen.Subject let source: PremiumDemoScreen.Source - let order: [PremiumPerk] let action: () -> Void let dismiss: () -> Void @@ -470,14 +469,12 @@ private final class DemoSheetContent: CombinedComponent { context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, - order: [PremiumPerk]?, action: @escaping () -> Void, dismiss: @escaping () -> Void ) { self.context = context self.subject = subject self.source = source - self.order = order ?? [.moreUpload, .fasterDownload, .voiceToText, .noAds, .uniqueReactions, .premiumStickers, .animatedEmoji, .advancedChatManagement, .profileBadge, .animatedUserpics, .appIcons, .translation, .stories, .colors, .wallpapers] self.action = action self.dismiss = dismiss } @@ -492,9 +489,6 @@ private final class DemoSheetContent: CombinedComponent { if lhs.source != rhs.source { return false } - if lhs.order != rhs.order { - return false - } return true } @@ -663,7 +657,7 @@ private final class DemoSheetContent: CombinedComponent { static var body: Body { let closeButton = Child(Button.self) - let background = Child(GradientBackgroundComponent.self) + let background = Child(PremiumGradientBackgroundComponent.self) let pager = Child(DemoPagerComponent.self) let button = Child(SolidRoundedButtonComponent.self) let measureText = Child(MultilineTextComponent.self) @@ -679,7 +673,7 @@ private final class DemoSheetContent: CombinedComponent { let sideInset: CGFloat = 16.0 + environment.safeInsets.left let background = background.update( - component: GradientBackgroundComponent(colors: [ + component: PremiumGradientBackgroundComponent(colors: [ UIColor(rgb: 0x0077ff), UIColor(rgb: 0x6b93ff), UIColor(rgb: 0x8878ff), @@ -981,17 +975,70 @@ private final class DemoSheetContent: CombinedComponent { ) ) ) + availableItems[.messageTags] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.messageTags, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .top, + model: .island, + videoFile: configuration.videos["saved_tags"], + decoration: .tag + )), + title: strings.Premium_MessageTags, + text: strings.Premium_MessageTagsInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.lastSeen] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.lastSeen, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .top, + model: .island, + videoFile: configuration.videos["last_seen"], + decoration: .tag + )), + title: strings.Premium_LastSeen, + text: strings.Premium_LastSeenInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.messagePrivacy] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.messagePrivacy, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .top, + model: .island, + videoFile: configuration.videos["message_privacy"], + decoration: .tag + )), + title: strings.Premium_MessagePrivacy, + text: strings.Premium_MessagePrivacyInfo, + textColor: textColor + ) + ) + ) + ) - var items: [DemoPagerComponent.Item] = component.order.compactMap { availableItems[$0] } - let index: Int - switch component.source { - case .intro, .gift: - index = items.firstIndex(where: { (component.subject as AnyHashable) == $0.content.id }) ?? 0 - case .other: - items = items.filter { item in - return item.content.id == (component.subject as AnyHashable) - } - index = 0 + let index: Int = 0 + var items: [DemoPagerComponent.Item] = [] + if let item = availableItems.first(where: { $0.value.content.id == component.subject as AnyHashable }) { + items.append(item.value) + } else { + fatalError() } let pager = pager.update( @@ -1083,6 +1130,12 @@ private final class DemoSheetContent: CombinedComponent { buttonText = strings.Premium_Wallpaper_Proceed case .colors: buttonText = strings.Premium_Colors_Proceed + case .messageTags: + buttonText = strings.Premium_MessageTags_Proceed + case .lastSeen: + buttonText = strings.Premium_LastSeen_Proceed + case .messagePrivacy: + buttonText = strings.Premium_MessagePrivacy_Proceed default: buttonText = strings.Common_OK } @@ -1118,9 +1171,13 @@ private final class DemoSheetContent: CombinedComponent { text = strings.Premium_ColorsInfo case .wallpapers: text = strings.Premium_WallpapersInfo - case .doubleLimits: - text = "" - case .stories: + case .messageTags: + text = strings.Premium_MessageTagsInfo + case .lastSeen: + text = strings.Premium_LastSeenInfo + case .messagePrivacy: + text = strings.Premium_MessagePrivacyInfo + case .doubleLimits, .stories: text = "" } @@ -1227,14 +1284,12 @@ private final class DemoSheetComponent: CombinedComponent { let context: AccountContext let subject: PremiumDemoScreen.Subject let source: PremiumDemoScreen.Source - let order: [PremiumPerk]? let action: () -> Void - init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, order: [PremiumPerk]?, action: @escaping () -> Void) { + init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, action: @escaping () -> Void) { self.context = context self.subject = subject self.source = source - self.order = order self.action = action } @@ -1248,10 +1303,6 @@ private final class DemoSheetComponent: CombinedComponent { if lhs.source != rhs.source { return false } - if lhs.order != rhs.order { - return false - } - return true } @@ -1270,7 +1321,6 @@ private final class DemoSheetComponent: CombinedComponent { context: context.component.context, subject: context.component.subject, source: context.component.source, - order: context.component.order, action: context.component.action, dismiss: { animateOut.invoke(Action { _ in @@ -1338,6 +1388,9 @@ public class PremiumDemoScreen: ViewControllerComponentContainer { case stories case colors case wallpapers + case messageTags + case lastSeen + case messagePrivacy } public enum Source: Equatable { @@ -1354,12 +1407,8 @@ public class PremiumDemoScreen: ViewControllerComponentContainer { return self._ready } - public convenience init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, forceDark: Bool = false, action: @escaping () -> Void) { - self.init(context: context, subject: subject, source: source, order: nil, forceDark: forceDark, action: action) - } - - init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, order: [PremiumPerk]?, forceDark: Bool = false, action: @escaping () -> Void) { - super.init(context: context, component: DemoSheetComponent(context: context, subject: subject, source: source, order: order, action: action), navigationBarAppearance: .none, theme: forceDark ? .dark : .default) + public init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, forceDark: Bool = false, action: @escaping () -> Void) { + super.init(context: context, component: DemoSheetComponent(context: context, subject: subject, source: source, action: action), navigationBarAppearance: .none, theme: forceDark ? .dark : .default) self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) @@ -1399,4 +1448,3 @@ public class PremiumDemoScreen: ViewControllerComponentContainer { } } } - diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index f217d65bb1..e646ae1ee0 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -424,14 +424,17 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { UIColor(rgb: 0xef6922), UIColor(rgb: 0xe95a2c), UIColor(rgb: 0xe74e33), + UIColor(rgb: 0xe54937), UIColor(rgb: 0xe3433c), UIColor(rgb: 0xdb374b), UIColor(rgb: 0xcb3e6d), UIColor(rgb: 0xbc4395), UIColor(rgb: 0xab4ac4), + UIColor(rgb: 0xa34cd7), UIColor(rgb: 0x9b4fed), UIColor(rgb: 0x8958ff), UIColor(rgb: 0x676bff), + UIColor(rgb: 0x6172ff), UIColor(rgb: 0x5b79ff), UIColor(rgb: 0x4492ff), UIColor(rgb: 0x429bd5), @@ -521,6 +524,12 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { demoSubject = .colors case .wallpapers: demoSubject = .wallpapers + case .messageTags: + demoSubject = .messageTags + case .lastSeen: + demoSubject = .lastSeen + case .messagePrivacy: + demoSubject = .messagePrivacy } let buttonText: String diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 28dec4dc42..1a4b54db11 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -229,6 +229,12 @@ public enum PremiumSource: Equatable { } else { return false } + case .storiesHigherQuality: + if case .storiesHigherQuality = rhs { + return true + } else { + return false + } case let .channelBoost(peerId): if case .channelBoost(peerId) = rhs { return true @@ -265,6 +271,12 @@ public enum PremiumSource: Equatable { } else { return false } + case .messageTags: + if case .messageTags = rhs { + return true + } else { + return false + } } } @@ -301,12 +313,14 @@ public enum PremiumSource: Equatable { case storiesFormatting case storiesExpirationDurations case storiesSuggestedReactions + case storiesHigherQuality case channelBoost(EnginePeer.Id) case nameColor case similarChannels case wallpapers case presence case readTime + case messageTags var identifier: String? { switch self { @@ -378,6 +392,8 @@ public enum PremiumSource: Equatable { return "stories__expiration_durations" case .storiesSuggestedReactions: return "stories__suggested_reactions" + case .storiesHigherQuality: + return "stories__quality" case let .channelBoost(peerId): return "channel_boost__\(peerId.id._internalGetInt64Value())" case .nameColor: @@ -390,6 +406,8 @@ public enum PremiumSource: Equatable { return "presence" case .readTime: return "read_time" + case .messageTags: + return "saved_tags" } } } @@ -412,6 +430,9 @@ public enum PremiumPerk: CaseIterable { case stories case colors case wallpapers + case messageTags + case lastSeen + case messagePrivacy public static var allCases: [PremiumPerk] { return [ @@ -431,7 +452,10 @@ public enum PremiumPerk: CaseIterable { .translation, .stories, .colors, - .wallpapers + .wallpapers, + .messageTags, + .lastSeen, + .messagePrivacy ] } @@ -481,6 +505,12 @@ public enum PremiumPerk: CaseIterable { return "peer_colors" case .wallpapers: return "wallpapers" + case .messageTags: + return "saved_tags" + case .lastSeen: + return "last_seen" + case .messagePrivacy: + return "message_privacy" } } @@ -520,6 +550,12 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_Colors case .wallpapers: return strings.Premium_Wallpapers + case .messageTags: + return strings.Premium_MessageTags + case .lastSeen: + return strings.Premium_LastSeen + case .messagePrivacy: + return strings.Premium_MessagePrivacy } } @@ -559,6 +595,12 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_ColorsInfo case .wallpapers: return strings.Premium_WallpapersInfo + case .messageTags: + return strings.Premium_MessageTagsInfo + case .lastSeen: + return strings.Premium_LastSeenInfo + case .messagePrivacy: + return strings.Premium_MessagePrivacyInfo } } @@ -598,6 +640,12 @@ public enum PremiumPerk: CaseIterable { return "Premium/Perk/Colors" case .wallpapers: return "Premium/Perk/Wallpapers" + case .messageTags: + return "Premium/Perk/MessageTags" + case .lastSeen: + return "Premium/Perk/Wallpapers" + case .messagePrivacy: + return "Premium/Perk/MessageTags" } } } @@ -606,22 +654,25 @@ struct PremiumIntroConfiguration { static var defaultValue: PremiumIntroConfiguration { return PremiumIntroConfiguration(perks: [ .stories, - .doubleLimits, .moreUpload, + .doubleLimits, + .lastSeen, + .voiceToText, .fasterDownload, .translation, - .voiceToText, - .noAds, + .animatedEmoji, .emojiStatus, + .messageTags, .colors, .wallpapers, - .uniqueReactions, - .premiumStickers, - .animatedEmoji, - .advancedChatManagement, .profileBadge, + .messagePrivacy, + .advancedChatManagement, + .noAds, + .appIcons, + .uniqueReactions, .animatedUserpics, - .appIcons + .premiumStickers ]) } @@ -651,11 +702,11 @@ struct PremiumIntroConfiguration { perks = PremiumIntroConfiguration.defaultValue.perks } #if DEBUG - if !perks.contains(.wallpapers) { - perks.append(.wallpapers) + if !perks.contains(.lastSeen) { + perks.append(.lastSeen) } - if !perks.contains(.colors) { - perks.append(.colors) + if !perks.contains(.messagePrivacy) { + perks.append(.messagePrivacy) } #endif return PremiumIntroConfiguration(perks: perks) @@ -1617,8 +1668,11 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { self.newPerksDisposable = combineLatest(queue: Queue.mainQueue(), ApplicationSpecificNotice.dismissedPremiumAppIconsBadge(accountManager: context.sharedContext.accountManager), ApplicationSpecificNotice.dismissedPremiumWallpapersBadge(accountManager: context.sharedContext.accountManager), - ApplicationSpecificNotice.dismissedPremiumColorsBadge(accountManager: context.sharedContext.accountManager) - ).startStrict(next: { [weak self] dismissedPremiumAppIconsBadge, dismissedPremiumWallpapersBadge, dismissedPremiumColorsBadge in + ApplicationSpecificNotice.dismissedPremiumColorsBadge(accountManager: context.sharedContext.accountManager), + ApplicationSpecificNotice.dismissedMessageTagsBadge(accountManager: context.sharedContext.accountManager), + ApplicationSpecificNotice.dismissedLastSeenBadge(accountManager: context.sharedContext.accountManager), + ApplicationSpecificNotice.dismissedMessagePrivacyBadge(accountManager: context.sharedContext.accountManager) + ).startStrict(next: { [weak self] dismissedPremiumAppIconsBadge, dismissedPremiumWallpapersBadge, dismissedPremiumColorsBadge, dismissedMessageTagsBadge, dismissedLastSeenBadge, dismissedMessagePrivacyBadge in guard let self else { return } @@ -1629,6 +1683,15 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { if !dismissedPremiumColorsBadge { newPerks.append(PremiumPerk.colors.identifier) } + if !dismissedMessageTagsBadge { + newPerks.append(PremiumPerk.messageTags.identifier) + } + if !dismissedLastSeenBadge { + newPerks.append(PremiumPerk.lastSeen.identifier) + } + if !dismissedMessagePrivacyBadge { + newPerks.append(PremiumPerk.messagePrivacy.identifier) + } self.newPerks = newPerks self.updated() }) @@ -1803,14 +1866,17 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { UIColor(rgb: 0xef6922), UIColor(rgb: 0xe95a2c), UIColor(rgb: 0xe74e33), + UIColor(rgb: 0xe54937), UIColor(rgb: 0xe3433c), UIColor(rgb: 0xdb374b), UIColor(rgb: 0xcb3e6d), UIColor(rgb: 0xbc4395), UIColor(rgb: 0xab4ac4), + UIColor(rgb: 0xa34cd7), UIColor(rgb: 0x9b4fed), UIColor(rgb: 0x8958ff), UIColor(rgb: 0x676bff), + UIColor(rgb: 0x6172ff), UIColor(rgb: 0x5b79ff), UIColor(rgb: 0x4492ff), UIColor(rgb: 0x429bd5), @@ -2031,6 +2097,15 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { case .wallpapers: demoSubject = .wallpapers let _ = ApplicationSpecificNotice.setDismissedPremiumWallpapersBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() + case .messageTags: + demoSubject = .messageTags + let _ = ApplicationSpecificNotice.setDismissedMessageTagsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() + case .lastSeen: + demoSubject = .lastSeen + let _ = ApplicationSpecificNotice.setDismissedLastSeenBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() + case .messagePrivacy: + demoSubject = .messagePrivacy + let _ = ApplicationSpecificNotice.setDismissedMessagePrivacyBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() } let isPremium = state?.isPremium == true diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index d51b0948fe..2b5ae9062f 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -323,6 +323,7 @@ public class PremiumLimitDisplayComponent: Component { self.badgeIcon.image = component.badgeIconName.flatMap { UIImage(bundleImageName: $0)?.withRenderingMode(.alwaysTemplate) } self.badgeIcon.tintColor = component.activeTitleColor self.badgeView.isHidden = self.badgeIcon.image == nil + self.badgeLabel.color = component.activeTitleColor let lineHeight: CGFloat = 30.0 let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - lineHeight), size: CGSize(width: size.width, height: lineHeight)) @@ -914,11 +915,11 @@ private final class LimitSheetContent: CombinedComponent { defaultValue = component.count > limit ? "\(limit)" : "" premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)" if component.count >= premiumLimit { - badgeGraphPosition = max(0.15, CGFloat(limit) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(limit) / CGFloat(premiumLimit)) } else { - badgeGraphPosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(component.count) / CGFloat(premiumLimit)) } - badgePosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit)) + badgePosition = max(0.35, CGFloat(component.count) / CGFloat(premiumLimit)) if !state.isPremium && badgePosition > 0.5 { string = strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string @@ -952,11 +953,11 @@ private final class LimitSheetContent: CombinedComponent { defaultValue = component.count > limit ? "\(limit)" : "" premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)" if component.count >= premiumLimit { - badgeGraphPosition = max(0.15, CGFloat(limit) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(limit) / CGFloat(premiumLimit)) } else { - badgeGraphPosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(component.count) / CGFloat(premiumLimit)) } - badgePosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit)) + badgePosition = max(0.35, CGFloat(component.count) / CGFloat(premiumLimit)) if isPremiumDisabled { badgeText = "\(limit)" @@ -977,11 +978,11 @@ private final class LimitSheetContent: CombinedComponent { defaultValue = count > limit ? "\(limit)" : "" premiumValue = count >= premiumLimit ? "" : "\(premiumLimit)" if count >= premiumLimit { - badgeGraphPosition = max(0.15, CGFloat(limit) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(limit) / CGFloat(premiumLimit)) } else { - badgeGraphPosition = max(0.15, CGFloat(count) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(count) / CGFloat(premiumLimit)) } - badgePosition = max(0.15, CGFloat(count) / CGFloat(premiumLimit)) + badgePosition = max(0.35, CGFloat(count) / CGFloat(premiumLimit)) if isPremiumDisabled { badgeText = "\(limit)" @@ -998,11 +999,11 @@ private final class LimitSheetContent: CombinedComponent { defaultValue = component.count > limit ? "\(limit)" : "" premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)" if component.count >= premiumLimit { - badgeGraphPosition = max(0.15, CGFloat(limit) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(limit) / CGFloat(premiumLimit)) } else { - badgeGraphPosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(component.count) / CGFloat(premiumLimit)) } - badgePosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit)) + badgePosition = max(0.35, CGFloat(component.count) / CGFloat(premiumLimit)) if isPremiumDisabled { badgeText = "\(limit)" @@ -1033,7 +1034,7 @@ private final class LimitSheetContent: CombinedComponent { string = component.count >= premiumLimit ? strings.Premium_MaxSavedPinsFinalText("\(premiumLimit)").string : strings.Premium_MaxSavedPinsText("\(limit)", "\(premiumLimit)").string defaultValue = component.count > limit ? "\(limit)" : "" premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)" - badgePosition = max(0.15, min(0.85, CGFloat(component.count) / CGFloat(premiumLimit))) + badgePosition = max(0.35, min(0.65, CGFloat(component.count) / CGFloat(premiumLimit))) badgeGraphPosition = badgePosition buttonAnimationName = nil @@ -1126,6 +1127,11 @@ private final class LimitSheetContent: CombinedComponent { } buttonAnimationName = nil case let .storiesChannelBoost(peer, boostSubject, isCurrent, level, currentLevelBoosts, nextLevelBoosts, link, myBoostCount, canBoostAgain): + var isGroup = false + if case let .channel(channel) = peer, case .group = channel.info { + isGroup = true + } + if link == nil, !isCurrent, state.initialized { peerShortcutChild = peerShortcut.update( component: Button( @@ -1191,6 +1197,7 @@ private final class LimitSheetContent: CombinedComponent { remaining = nextLevelBoosts - component.count } + if let _ = link { if let remaining { let storiesString = strings.ChannelBoost_StoriesPerDay(level + 1) @@ -1200,15 +1207,15 @@ private final class LimitSheetContent: CombinedComponent { case .stories: if level == 0 { titleText = strings.ChannelBoost_EnableStories - string = strings.ChannelBoost_EnableStoriesText(valueString).string + string = isGroup ? strings.GroupBoost_EnableStoriesText(valueString).string : strings.ChannelBoost_EnableStoriesText(valueString).string } else { titleText = strings.ChannelBoost_IncreaseLimit - string = strings.ChannelBoost_IncreaseLimitText(valueString, storiesString).string + string = isGroup ? strings.GroupBoost_IncreaseLimitText(valueString, storiesString).string : strings.ChannelBoost_IncreaseLimitText(valueString, storiesString).string } case let .nameColors(colors): titleText = strings.ChannelBoost_EnableColors - let colorLevel = requiredBoostSubjectLevel(subject: .nameColors(colors: colors), context: component.context, configuration: premiumConfiguration) + let colorLevel = requiredBoostSubjectLevel(subject: .nameColors(colors: colors), group: false, context: component.context, configuration: premiumConfiguration) string = strings.ChannelBoost_EnableColorsLevelText("\(colorLevel)").string case let .channelReactions(reactionCount): diff --git a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift index 43617b719f..a26c18ac20 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift @@ -363,7 +363,7 @@ public class PremiumLimitsListScreen: ViewController { let backgroundSize = self.backgroundView.update( transition: .immediate, component: AnyComponent( - GradientBackgroundComponent(colors: [ + PremiumGradientBackgroundComponent(colors: [ UIColor(rgb: 0x0077ff), UIColor(rgb: 0x6b93ff), UIColor(rgb: 0x8878ff), @@ -742,6 +742,63 @@ public class PremiumLimitsListScreen: ViewController { ) ) ) + availableItems[.messageTags] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.messageTags, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .top, + model: .island, + videoFile: configuration.videos["saved_tags"], + decoration: .tag + )), + title: strings.Premium_MessageTags, + text: strings.Premium_MessageTagsInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.lastSeen] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.lastSeen, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .top, + model: .island, + videoFile: configuration.videos["last_seen"], + decoration: .tag + )), + title: strings.Premium_LastSeen, + text: strings.Premium_LastSeenInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.messagePrivacy] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.messagePrivacy, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .top, + model: .island, + videoFile: configuration.videos["message_privacy"], + decoration: .tag + )), + title: strings.Premium_MessagePrivacy, + text: strings.Premium_MessagePrivacyInfo, + textColor: textColor + ) + ) + ) + ) if let order = controller.order { var items: [DemoPagerComponent.Item] = order.compactMap { availableItems[$0] } diff --git a/submodules/PremiumUI/Sources/StoriesPageComponent.swift b/submodules/PremiumUI/Sources/StoriesPageComponent.swift index 3196b390fc..3821086a4c 100644 --- a/submodules/PremiumUI/Sources/StoriesPageComponent.swift +++ b/submodules/PremiumUI/Sources/StoriesPageComponent.swift @@ -312,13 +312,14 @@ private final class StoriesListComponent: CombinedComponent { let strings = context.component.context.sharedContext.currentPresentationData.with { $0 }.strings let colors = [ - UIColor(rgb: 0x0275f3), - UIColor(rgb: 0x8698ff), - UIColor(rgb: 0xc871ff), - UIColor(rgb: 0xc356ad), - UIColor(rgb: 0xe85c44), - UIColor(rgb: 0xff932b), - UIColor(rgb: 0xe9af18) + UIColor(rgb: 0x007aff), + UIColor(rgb: 0x798aff), + UIColor(rgb: 0xac64f3), + UIColor(rgb: 0xc456ae), + UIColor(rgb: 0xe95d44), + UIColor(rgb: 0xf2822a), + UIColor(rgb: 0xe79519), + UIColor(rgb: 0xe7ad19) ] let titleColor = theme.list.itemPrimaryTextColor @@ -367,6 +368,20 @@ private final class StoriesListComponent: CombinedComponent { ) ) + items.append( + AnyComponentWithIdentity( + id: "quality", + component: AnyComponent(ParagraphComponent( + title: strings.Premium_Stories_Quality_Title, + titleColor: titleColor, + text: strings.Premium_Stories_Quality_Text, + textColor: textColor, + iconName: "Premium/Stories/Quality", + iconColor: colors[2] + )) + ) + ) + items.append( AnyComponentWithIdentity( id: "views", @@ -376,7 +391,7 @@ private final class StoriesListComponent: CombinedComponent { text: strings.Premium_Stories_Views_Text, textColor: textColor, iconName: "Premium/Stories/Views", - iconColor: colors[2] + iconColor: colors[3] )) ) ) @@ -390,7 +405,7 @@ private final class StoriesListComponent: CombinedComponent { text: strings.Premium_Stories_Expiration_Text, textColor: textColor, iconName: "Premium/Stories/Expire", - iconColor: colors[3] + iconColor: colors[4] )) ) ) @@ -404,7 +419,7 @@ private final class StoriesListComponent: CombinedComponent { text: strings.Premium_Stories_Save_Text, textColor: textColor, iconName: "Premium/Stories/Save", - iconColor: colors[4] + iconColor: colors[5] )) ) ) @@ -418,7 +433,7 @@ private final class StoriesListComponent: CombinedComponent { text: strings.Premium_Stories_Captions_Text, textColor: textColor, iconName: "Premium/Stories/Caption", - iconColor: colors[5] + iconColor: colors[6] )) ) ) @@ -432,7 +447,7 @@ private final class StoriesListComponent: CombinedComponent { text: strings.Premium_Stories_Format_Text, textColor: textColor, iconName: "Premium/Stories/Format", - iconColor: colors[6] + iconColor: colors[7] )) ) ) diff --git a/submodules/StatisticsUI/BUILD b/submodules/StatisticsUI/BUILD index 4246307631..6bf260d9c3 100644 --- a/submodules/StatisticsUI/BUILD +++ b/submodules/StatisticsUI/BUILD @@ -38,6 +38,7 @@ swift_library( "//submodules/ShareController:ShareController", "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", + "//submodules/TelegramUI/Components/PlainButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/StatisticsUI/Sources/BackButton.swift b/submodules/StatisticsUI/Sources/BackButton.swift new file mode 100644 index 0000000000..271cae3d65 --- /dev/null +++ b/submodules/StatisticsUI/Sources/BackButton.swift @@ -0,0 +1,268 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import ContextUI +import TelegramPresentationData +import Display + +enum PeerInfoHeaderNavigationButtonKey { + case back + case edit + case done + case cancel + case select + case selectionDone + case search + case editPhoto + case editVideo + case more + case qrCode + case moreToSearch + case postStory +} + +final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { + let containerNode: ContextControllerSourceNode + let contextSourceNode: ContextReferenceContentNode + private let textNode: ImmediateTextNode + private let iconNode: ASImageNode + private let backIconLayer: SimpleShapeLayer + private let backgroundNode: NavigationBackgroundNode + + private var key: PeerInfoHeaderNavigationButtonKey? + + private var contentsColor: UIColor = .white + private var canBeExpanded: Bool = false + + var action: ((ASDisplayNode, ContextGesture?) -> Void)? + + init() { + self.contextSourceNode = ContextReferenceContentNode() + self.containerNode = ContextControllerSourceNode() + self.containerNode.animateScale = false + + self.textNode = ImmediateTextNode() + + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + self.iconNode.displayWithoutProcessing = true + + self.backIconLayer = SimpleShapeLayer() + self.backIconLayer.lineWidth = 3.0 + self.backIconLayer.lineCap = .round + self.backIconLayer.lineJoin = .round + self.backIconLayer.strokeColor = UIColor.white.cgColor + self.backIconLayer.fillColor = nil + self.backIconLayer.isHidden = true + self.backIconLayer.path = try? convertSvgPath("M10.5,2 L1.5,11 L10.5,20 ") + + self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: true) + + super.init(pointerStyle: .insetRectangle(-8.0, 2.0)) + + self.isAccessibilityElement = true + self.accessibilityTraits = .button + + self.containerNode.addSubnode(self.contextSourceNode) + self.contextSourceNode.addSubnode(self.backgroundNode) + self.contextSourceNode.addSubnode(self.textNode) + self.contextSourceNode.addSubnode(self.iconNode) + self.contextSourceNode.layer.addSublayer(self.backIconLayer) + + self.addSubnode(self.containerNode) + + self.containerNode.activated = { [weak self] gesture, _ in + guard let strongSelf = self else { + return + } + strongSelf.action?(strongSelf.contextSourceNode, gesture) + } + + self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) + } + + @objc private func pressed() { + self.action?(self.contextSourceNode, nil) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + var boundingRect = self.bounds + if self.textNode.alpha != 0.0 { + boundingRect = boundingRect.union(self.textNode.frame) + } + boundingRect = boundingRect.insetBy(dx: -8.0, dy: -4.0) + if boundingRect.contains(point) { + return super.hitTest(self.bounds.center, with: event) + } else { + return nil + } + } + + func updateContentsColor(backgroundColor: UIColor, contentsColor: UIColor, canBeExpanded: Bool, transition: ContainedViewLayoutTransition) { + self.contentsColor = contentsColor + self.canBeExpanded = canBeExpanded + + self.backgroundNode.updateColor(color: backgroundColor, transition: transition) + + transition.updateTintColor(layer: self.textNode.layer, color: self.contentsColor) + transition.updateTintColor(layer: self.iconNode.layer, color: self.contentsColor) + transition.updateStrokeColor(layer: self.backIconLayer, strokeColor: self.contentsColor) + + switch self.key { + case .back: + transition.updateAlpha(layer: self.textNode.layer, alpha: canBeExpanded ? 1.0 : 0.0) + transition.updateTransformScale(node: self.textNode, scale: canBeExpanded ? 1.0 : 0.001) + + var iconTransform = CATransform3DIdentity + iconTransform = CATransform3DScale(iconTransform, canBeExpanded ? 1.0 : 0.8, canBeExpanded ? 1.0 : 0.8, 1.0) + iconTransform = CATransform3DTranslate(iconTransform, canBeExpanded ? -7.0 : 0.0, 0.0, 0.0) + transition.updateTransform(node: self.iconNode, transform: CATransform3DGetAffineTransform(iconTransform)) + + transition.updateTransform(layer: self.backIconLayer, transform: CATransform3DGetAffineTransform(iconTransform)) + transition.updateLineWidth(layer: self.backIconLayer, lineWidth: canBeExpanded ? 3.0 : 2.075) + default: + break + } + } + + func update(key: PeerInfoHeaderNavigationButtonKey, presentationData: PresentationData, height: CGFloat) -> CGSize { + let transition: ContainedViewLayoutTransition = .immediate + + var iconOffset = CGPoint() + switch key { + case .back: + iconOffset = CGPoint(x: -1.0, y: 0.0) + default: + break + } + + let textSize: CGSize + if self.key != key { + self.key = key + + let text: String + var accessibilityText: String + var icon: UIImage? + var isBold = false + var isGestureEnabled = false + switch key { + case .back: + text = presentationData.strings.Common_Back + accessibilityText = presentationData.strings.Common_Back + icon = NavigationBar.backArrowImage(color: .white) + case .edit: + text = presentationData.strings.Common_Edit + accessibilityText = text + case .cancel: + text = presentationData.strings.Common_Cancel + accessibilityText = text + isBold = false + case .done, .selectionDone: + text = presentationData.strings.Common_Done + accessibilityText = text + isBold = true + case .select: + text = presentationData.strings.Common_Select + accessibilityText = text + case .search: + text = "" + accessibilityText = presentationData.strings.Common_Search + icon = nil// PresentationResourcesRootController.navigationCompactSearchIcon(presentationData.theme) + case .editPhoto: + text = presentationData.strings.Settings_EditPhoto + accessibilityText = text + case .editVideo: + text = presentationData.strings.Settings_EditVideo + accessibilityText = text + case .more: + text = "" + accessibilityText = presentationData.strings.Common_More + icon = nil// PresentationResourcesRootController.navigationMoreCircledIcon(presentationData.theme) + isGestureEnabled = true + case .qrCode: + text = "" + accessibilityText = presentationData.strings.PeerInfo_QRCode_Title + icon = PresentationResourcesRootController.navigationQrCodeIcon(presentationData.theme) + case .moreToSearch: + text = "" + accessibilityText = "" + case .postStory: + text = "" + accessibilityText = presentationData.strings.Story_Privacy_PostStory + icon = PresentationResourcesRootController.navigationPostStoryIcon(presentationData.theme) + } + self.accessibilityLabel = accessibilityText + self.containerNode.isGestureEnabled = isGestureEnabled + + let font: UIFont = isBold ? Font.semibold(17.0) : Font.regular(17.0) + + self.textNode.attributedText = NSAttributedString(string: text, font: font, textColor: .white) + transition.updateTintColor(layer: self.textNode.layer, color: self.contentsColor) + self.iconNode.image = icon + transition.updateTintColor(layer: self.iconNode.layer, color: self.contentsColor) + + self.iconNode.isHidden = false + + textSize = self.textNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) + } else { + textSize = self.textNode.bounds.size + } + + let inset: CGFloat = 0.0 + var textInset: CGFloat = 0.0 + switch key { + case .back: + textInset += 11.0 + default: + break + } + + let resultSize: CGSize + + let textFrame = CGRect(origin: CGPoint(x: inset + textInset, y: floor((height - textSize.height) / 2.0)), size: textSize) + self.textNode.position = textFrame.center + self.textNode.bounds = CGRect(origin: CGPoint(), size: textFrame.size) + + if let image = self.iconNode.image { + let iconFrame = CGRect(origin: CGPoint(x: inset, y: floor((height - image.size.height) / 2.0)), size: image.size).offsetBy(dx: iconOffset.x, dy: iconOffset.y) + self.iconNode.position = iconFrame.center + self.iconNode.bounds = CGRect(origin: CGPoint(), size: iconFrame.size) + + if case .back = key { + self.backIconLayer.position = iconFrame.center + self.backIconLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size) + + self.iconNode.isHidden = true + self.backIconLayer.isHidden = false + } else { + self.iconNode.isHidden = false + self.backIconLayer.isHidden = true + } + + let size = CGSize(width: image.size.width + inset * 2.0, height: height) + self.containerNode.frame = CGRect(origin: CGPoint(), size: size) + self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size) + resultSize = size + } else { + let size = CGSize(width: textSize.width + inset * 2.0, height: height) + self.containerNode.frame = CGRect(origin: CGPoint(), size: size) + self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size) + resultSize = size + } + + let diameter: CGFloat = 32.0 + let backgroundWidth: CGFloat + if self.iconNode.image != nil { + backgroundWidth = diameter + } else { + backgroundWidth = max(diameter, resultSize.width + 12.0 * 2.0) + } + let backgroundFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - backgroundWidth) * 0.5), y: floor((resultSize.height - diameter) * 0.5)), size: CGSize(width: backgroundWidth, height: diameter)) + transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) + self.backgroundNode.update(size: backgroundFrame.size, cornerRadius: diameter * 0.5, transition: transition) + + self.hitTestSlop = UIEdgeInsets(top: -2.0, left: -12.0, bottom: -2.0, right: -12.0) + + return resultSize + } +} diff --git a/submodules/StatisticsUI/Sources/BoostHeaderItem.swift b/submodules/StatisticsUI/Sources/BoostHeaderItem.swift new file mode 100644 index 0000000000..cf15172bd6 --- /dev/null +++ b/submodules/StatisticsUI/Sources/BoostHeaderItem.swift @@ -0,0 +1,514 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramCore +import TelegramPresentationData +import ItemListUI +import PresentationDataUtils +import Markdown +import ComponentFlow +import PremiumUI +import MultilineTextComponent +import BundleIconComponent +import PlainButtonComponent +import AccountContext + +final class BoostHeaderItem: ItemListControllerHeaderItem { + let context: AccountContext + let theme: PresentationTheme + let strings: PresentationStrings + let status: ChannelBoostStatus + let title: String + let text: String + let openBoost: () -> Void + let createGiveaway: () -> Void + let openFeatures: () -> Void + let back: () -> Void + + init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, status: ChannelBoostStatus, title: String, text: String, openBoost: @escaping () -> Void, createGiveaway: @escaping () -> Void, openFeatures: @escaping () -> Void, back: @escaping () -> Void) { + self.context = context + self.theme = theme + self.strings = strings + self.status = status + self.title = title + self.text = text + self.openBoost = openBoost + self.createGiveaway = createGiveaway + self.openFeatures = openFeatures + self.back = back + } + + func isEqual(to: ItemListControllerHeaderItem) -> Bool { + if let item = to as? BoostHeaderItem { + return self.theme === item.theme && self.title == item.title && self.text == item.text + } else { + return false + } + } + + func node(current: ItemListControllerHeaderItemNode?) -> ItemListControllerHeaderItemNode { + if let current = current as? BoostHeaderItemNode { + current.item = self + return current + } else { + return BoostHeaderItemNode(item: self) + } + } +} + +private let titleFont = Font.semibold(17.0) + +final class BoostHeaderItemNode: ItemListControllerHeaderItemNode { + private let backgroundNode: NavigationBackgroundNode + private let separatorNode: ASDisplayNode + private let whiteTitleNode: ImmediateTextNode + private let titleNode: ImmediateTextNode + private let backButton = PeerInfoHeaderNavigationButton() + + private var hostView: ComponentHostView? + + private var component: AnyComponent? + private var validLayout: ContainerViewLayout? + + fileprivate var item: BoostHeaderItem { + didSet { + self.updateItem() + if let layout = self.validLayout { + let _ = self.updateLayout(layout: layout, transition: .immediate) + } + } + } + + init(item: BoostHeaderItem) { + self.item = item + + self.backgroundNode = NavigationBackgroundNode(color: item.theme.rootController.navigationBar.blurredBackgroundColor) + self.backgroundNode.alpha = 0.0 + self.separatorNode = ASDisplayNode() + self.separatorNode.alpha = 0.0 + + self.whiteTitleNode = ImmediateTextNode() + self.whiteTitleNode.isUserInteractionEnabled = false + self.whiteTitleNode.contentMode = .left + self.whiteTitleNode.contentsScale = UIScreen.main.scale + + self.titleNode = ImmediateTextNode() + self.titleNode.alpha = 0.0 + self.titleNode.isUserInteractionEnabled = false + self.titleNode.contentMode = .left + self.titleNode.contentsScale = UIScreen.main.scale + + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.separatorNode) + self.addSubnode(self.titleNode) + self.addSubnode(self.whiteTitleNode) + self.addSubnode(self.backButton) + + self.updateItem() + + self.backButton.action = { [weak self] _, _ in + if let self { + self.item.back() + } + } + } + + override func didLoad() { + super.didLoad() + + let hostView = ComponentHostView() + self.hostView = hostView + self.view.insertSubview(hostView, at: 0) + + if let layout = self.validLayout, let component = self.component { + let navigationBarHeight: CGFloat = 44.0 + let statusBarHeight = layout.statusBarHeight ?? 0.0 + let containerSize = CGSize(width: layout.size.width, height: navigationBarHeight + statusBarHeight + 266.0) + + let size = hostView.update( + transition: .immediate, + component: component, + environment: {}, + containerSize: containerSize + ) + hostView.frame = CGRect(origin: CGPoint(x: 0.0, y: -self.contentOffset), size: size) + } + } + + func updateItem() { + self.backgroundNode.updateColor(color: self.item.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) + self.separatorNode.backgroundColor = self.item.theme.rootController.navigationBar.separatorColor + + self.titleNode.attributedText = NSAttributedString(string: self.item.title, font: titleFont, textColor: self.item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center) + self.whiteTitleNode.attributedText = NSAttributedString(string: self.item.title, font: titleFont, textColor: .white, paragraphAlignment: .center) + } + + private var contentOffset: CGFloat = 0.0 + override func updateContentOffset(_ contentOffset: CGFloat, transition: ContainedViewLayoutTransition) { + guard let layout = self.validLayout else { + return + } + self.contentOffset = contentOffset + + let topPanelAlpha = min(20.0, max(0.0, contentOffset - 44.0)) / 20.0 + transition.updateAlpha(node: self.backgroundNode, alpha: topPanelAlpha) + transition.updateAlpha(node: self.separatorNode, alpha: topPanelAlpha) + + transition.updateAlpha(node: self.titleNode, alpha: topPanelAlpha) + transition.updateAlpha(node: self.whiteTitleNode, alpha: 1.0 - topPanelAlpha) + + let scrolledUp = topPanelAlpha < 0.5 + self.backButton.updateContentsColor(backgroundColor: scrolledUp ? UIColor(white: 1.0, alpha: 0.2) : .clear, contentsColor: scrolledUp ? .white : self.item.theme.rootController.navigationBar.accentTextColor, canBeExpanded: !scrolledUp, transition: .animated(duration: 0.2, curve: .easeInOut)) + + if let hostView = self.hostView { + hostView.center = CGPoint(x: layout.size.width / 2.0, y: hostView.frame.height / 2.0 - contentOffset) + } + } + + override func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> CGFloat { + let isFirstTime = self.validLayout == nil + let leftInset: CGFloat = 24.0 + + let navigationBarHeight: CGFloat = 44.0 + let statusBarHeight = layout.statusBarHeight ?? 0.0 + + let constrainedSize = CGSize(width: layout.size.width - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude) + let titleSize = self.titleNode.updateLayout(constrainedSize) + let _ = self.whiteTitleNode.updateLayout(constrainedSize) + + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: statusBarHeight + navigationBarHeight))) + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: statusBarHeight + navigationBarHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) + + self.backgroundNode.update(size: CGSize(width: layout.size.width, height: statusBarHeight + navigationBarHeight), transition: transition) + + let component = AnyComponent(BoostHeaderComponent( + strings: self.item.strings, + text: self.item.text, + status: self.item.status, + openBoost: self.item.openBoost, + createGiveaway: self.item.createGiveaway, + openFeatures: self.item.openFeatures + )) + let containerSize = CGSize(width: layout.size.width, height: navigationBarHeight + statusBarHeight + 266.0) + + if let hostView = self.hostView { + let size = hostView.update( + transition: .immediate, + component: component, + environment: {}, + containerSize: containerSize + ) + hostView.frame = CGRect(origin: CGPoint(x: 0.0, y: -self.contentOffset), size: size) + } + + self.titleNode.bounds = CGRect(origin: .zero, size: titleSize) + self.titleNode.position = CGPoint(x: layout.size.width / 2.0, y: statusBarHeight + navigationBarHeight / 2.0) + + self.whiteTitleNode.bounds = self.titleNode.bounds + self.whiteTitleNode.position = self.titleNode.position + + let backSize = self.backButton.update(key: .back, presentationData: self.item.context.sharedContext.currentPresentationData.with { $0 }, height: 44.0) + self.backButton.frame = CGRect(origin: CGPoint(x: 16.0, y: 54.0), size: backSize) + + self.component = component + self.validLayout = layout + + if isFirstTime { + self.backButton.updateContentsColor(backgroundColor: UIColor(white: 1.0, alpha: 0.2), contentsColor: .white, canBeExpanded: false, transition: .immediate) + } + + return containerSize.height + } + + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + if let hostView = self.hostView, hostView.frame.contains(point) { + return true + } else { + return super.point(inside: point, with: event) + } + } +} + +private final class BoostHeaderComponent: CombinedComponent { + let strings: PresentationStrings + let text: String + let status: ChannelBoostStatus + let openBoost: () -> Void + let createGiveaway: () -> Void + let openFeatures: () -> Void + + public init( + strings: PresentationStrings, + text: String, + status: ChannelBoostStatus, + openBoost: @escaping () -> Void, + createGiveaway: @escaping () -> Void, + openFeatures: @escaping () -> Void + ) { + self.strings = strings + self.text = text + self.status = status + self.openBoost = openBoost + self.createGiveaway = createGiveaway + self.openFeatures = openFeatures + } + + public static func ==(lhs: BoostHeaderComponent, rhs: BoostHeaderComponent) -> Bool { + if lhs.strings !== rhs.strings { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.status != rhs.status { + return false + } + return true + } + + public static var body: Body { + let background = Child(PremiumGradientBackgroundComponent.self) + let stars = Child(BoostHeaderBackgroundComponent.self) + let progress = Child(PremiumLimitDisplayComponent.self) + let text = Child(MultilineTextComponent.self) + + let boostButton = Child(PlainButtonComponent.self) + let giveawayButton = Child(PlainButtonComponent.self) + let featuresButton = Child(PlainButtonComponent.self) + + return { context in + let size = context.availableSize + let sideInset: CGFloat = 16.0 + let component = context.component + + let background = background.update( + component: PremiumGradientBackgroundComponent( + colors: [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ] + ), + availableSize: size, + transition: context.transition + ) + context.add(background + .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0)) + ) + + let stars = stars.update( + component: BoostHeaderBackgroundComponent( + isVisible: true, + hasIdleAnimations: true + ), + availableSize: size, + transition: context.transition + ) + context.add(stars + .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0 + 20.0)) + ) + + let level = component.status.level + let position: CGFloat + if let nextLevelBoosts = component.status.nextLevelBoosts { + position = CGFloat(component.status.boosts - component.status.currentLevelBoosts) / CGFloat(nextLevelBoosts - component.status.currentLevelBoosts) + } else { + position = 1.0 + } + + let inactiveText = component.strings.ChannelBoost_Level("\(level)").string + let activeText = component.strings.ChannelBoost_Level("\(level + 1)").string + + let progress = progress.update( + component: PremiumLimitDisplayComponent( + inactiveColor: UIColor.white.withAlphaComponent(0.2), + activeColors: [.white, .white], + inactiveTitle: inactiveText, + inactiveValue: "", + inactiveTitleColor: .white, + activeTitle: "", + activeValue: activeText, + activeTitleColor: UIColor(rgb: 0x6f8fff), + badgeIconName: "Premium/Boost", + badgeText: "\(component.status.boosts)", + badgePosition: position, + badgeGraphPosition: position, + invertProgress: true, + isPremiumDisabled: false + ), + availableSize: CGSize(width: size.width - sideInset * 2.0, height: size.height), + transition: context.transition + ) + + context.add(progress + .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0 - 36.0)) + ) + + let font = Font.regular(15.0) + let boldFont = Font.semibold(15.0) + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: font, textColor: .white), bold: MarkdownAttributeSet(font: boldFont, textColor: .white), link: MarkdownAttributeSet(font: font, textColor: .white), linkAttribute: { _ in return nil}) + + let text = text.update( + component: MultilineTextComponent( + text: .markdown(text: component.text, attributes: markdownAttributes), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + availableSize: CGSize(width: size.width - sideInset * 3.0, height: size.height), + transition: context.transition + ) + context.add(text + .position(CGPoint(x: size.width / 2.0, y: size.height - 114.0)) + ) + + let minButtonWidth: CGFloat = 112.0 +// let buttonSpacing = (size.width - sideInset * 2.0 - minButtonWidth * 3.0) / 2.0 + let buttonHeight: CGFloat = 58.0 + + let boostButton = boostButton.update( + component: PlainButtonComponent( + content: AnyComponent( + BoostButtonComponent( + iconName: "Premium/Boosts/Boost", + title: "boost" + ) + ), + effectAlignment: .center, + action: { + component.openBoost() + } + ), + availableSize: CGSize(width: minButtonWidth, height: buttonHeight), + transition: context.transition + ) + context.add(boostButton + .position(CGPoint(x: sideInset + minButtonWidth / 2.0, y: size.height - 45.0)) + ) + + let giveawayButton = giveawayButton.update( + component: PlainButtonComponent( + content: AnyComponent( + BoostButtonComponent( + iconName: "Premium/Boosts/Giveaway", + title: "giveaway" + ) + ), + effectAlignment: .center, + action: { + component.createGiveaway() + } + ), + availableSize: CGSize(width: minButtonWidth, height: buttonHeight), + transition: context.transition + ) + context.add(giveawayButton + .position(CGPoint(x: context.availableSize.width / 2.0, y: size.height - 45.0)) + ) + + let featuresButton = featuresButton.update( + component: PlainButtonComponent( + content: AnyComponent( + BoostButtonComponent( + iconName: "Premium/Boosts/Features", + title: "features" + ) + ), + effectAlignment: .center, + action: { + component.openFeatures() + } + ), + availableSize: CGSize(width: minButtonWidth, height: buttonHeight), + transition: context.transition + ) + context.add(featuresButton + .position(CGPoint(x: context.availableSize.width - sideInset - minButtonWidth / 2.0, y: size.height - 45.0)) + ) + + return background.size + } + } +} + +private final class BoostButtonComponent: CombinedComponent { + let iconName: String + let title: String + + public init( + iconName: String, + title: String + ) { + self.iconName = iconName + self.title = title + } + + public static func ==(lhs: BoostButtonComponent, rhs: BoostButtonComponent) -> Bool { + if lhs.iconName != rhs.iconName { + return false + } + if lhs.title != rhs.title { + return false + } + return true + } + + public static var body: Body { + let background = Child(RoundedRectangle.self) + let icon = Child(BundleIconComponent.self) + let title = Child(MultilineTextComponent.self) + + return { context in + let size = context.availableSize + let component = context.component + + let background = background.update( + component: RoundedRectangle( + color: UIColor.white.withAlphaComponent(0.2), + cornerRadius: 10.0 + ), + availableSize: context.availableSize, + transition: context.transition + ) + context.add(background + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + let icon = icon.update( + component: BundleIconComponent( + name: component.iconName, + tintColor: .white + ), + availableSize: context.availableSize, + transition: context.transition + ) + context.add(icon + .position(CGPoint(x: size.width / 2.0, y: 21.0)) + ) + + let title = title.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.title, + font: Font.regular(11.0), + textColor: .white + )), + horizontalAlignment: .center, + maximumNumberOfLines: 1 + ), + availableSize: CGSize(width: size.width - 16.0, height: size.height), + transition: context.transition + ) + context.add(title + .position(CGPoint(x: size.width / 2.0, y: size.height - 16.0)) + ) + + return background.size + } + } +} diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 403492a7e3..ce8faf70c8 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -163,7 +163,7 @@ private enum StatsEntry: ItemListNodeEntry { case boostLevel(PresentationTheme, Int32, Int32, CGFloat) case boostOverviewTitle(PresentationTheme, String) - case boostOverview(PresentationTheme, ChannelBoostStatus) + case boostOverview(PresentationTheme, ChannelBoostStatus, Bool) case boostPrepaidTitle(PresentationTheme, String) case boostPrepaid(Int32, PresentationTheme, String, String, PrepaidGiveaway) @@ -505,8 +505,8 @@ private enum StatsEntry: ItemListNodeEntry { } else { return false } - case let .boostOverview(lhsTheme, lhsStats): - if case let .boostOverview(rhsTheme, rhsStats) = rhs, lhsTheme === rhsTheme, lhsStats == rhsStats { + case let .boostOverview(lhsTheme, lhsStats, lhsIsGroup): + if case let .boostOverview(rhsTheme, rhsStats, rhsIsGroup) = rhs, lhsTheme === rhsTheme, lhsStats == rhsStats, lhsIsGroup == rhsIsGroup { return true } else { return false @@ -631,7 +631,7 @@ private enum StatsEntry: ItemListNodeEntry { let .giftsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) case let .overview(_, stats): - return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks) + return StatsOverviewItem(presentationData: presentationData, isGroup: false, stats: stats, sectionId: self.section, style: .blocks) case let .growthGraph(_, _, _, graph, type), let .followersGraph(_, _, _, graph, type), let .notificationsGraph(_, _, _, graph, type), @@ -730,8 +730,8 @@ private enum StatsEntry: ItemListNodeEntry { let inactiveText = presentationData.strings.ChannelBoost_Level("\(level)").string let activeText = presentationData.strings.ChannelBoost_Level("\(level + 1)").string return BoostLevelHeaderItem(theme: presentationData.theme, count: count, position: position, activeText: activeText, inactiveText: inactiveText, sectionId: self.section) - case let .boostOverview(_, stats): - return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks) + case let .boostOverview(_, stats, isGroup): + return StatsOverviewItem(presentationData: presentationData, isGroup: isGroup, stats: stats, sectionId: self.section, style: .blocks) case let .boostLink(_, link): let invite: ExportedInvitation = .link(link: link, title: nil, isPermanent: false, requestApproval: false, isRevoked: false, adminId: PeerId(0), date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil) return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: 0, peers: [], displayButton: true, displayImporters: false, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: { @@ -823,7 +823,7 @@ private struct ChannelStatsControllerState: Equatable { } -private func channelStatsControllerEntries(state: ChannelStatsControllerState, peer: EnginePeer?, data: ChannelStats?, messages: [Message]?, stories: PeerStoryListContext.State?, interactions: [ChannelStatsPostInteractions.PostId: ChannelStatsPostInteractions]?, boostData: ChannelBoostStatus?, boostersState: ChannelBoostersContext.State?, giftsState: ChannelBoostersContext.State?, presentationData: PresentationData, giveawayAvailable: Bool) -> [StatsEntry] { +private func channelStatsControllerEntries(state: ChannelStatsControllerState, peer: EnginePeer?, data: ChannelStats?, messages: [Message]?, stories: PeerStoryListContext.State?, interactions: [ChannelStatsPostInteractions.PostId: ChannelStatsPostInteractions]?, boostData: ChannelBoostStatus?, boostersState: ChannelBoostersContext.State?, giftsState: ChannelBoostersContext.State?, presentationData: PresentationData, giveawayAvailable: Bool, isGroup: Bool, boostsOnly: Bool) -> [StatsEntry] { var entries: [StatsEntry] = [] switch state.section { @@ -895,7 +895,6 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p entries.append(.storyReactionsByEmotionGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.storyReactionsByEmotionGraph, .bars)) } - if let peer, let interactions { var posts: [StatsPostItem] = [] if let messages { @@ -935,16 +934,18 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p } case .boosts: if let boostData { - let progress: CGFloat - if let nextLevelBoosts = boostData.nextLevelBoosts { - progress = CGFloat(boostData.boosts - boostData.currentLevelBoosts) / CGFloat(nextLevelBoosts - boostData.currentLevelBoosts) - } else { - progress = 1.0 + if !boostsOnly { + let progress: CGFloat + if let nextLevelBoosts = boostData.nextLevelBoosts { + progress = CGFloat(boostData.boosts - boostData.currentLevelBoosts) / CGFloat(nextLevelBoosts - boostData.currentLevelBoosts) + } else { + progress = 1.0 + } + entries.append(.boostLevel(presentationData.theme, Int32(boostData.boosts), Int32(boostData.level), progress)) } - entries.append(.boostLevel(presentationData.theme, Int32(boostData.boosts), Int32(boostData.level), progress)) entries.append(.boostOverviewTitle(presentationData.theme, presentationData.strings.Stats_Boosts_OverviewHeader)) - entries.append(.boostOverview(presentationData.theme, boostData)) + entries.append(.boostOverview(presentationData.theme, boostData, isGroup)) if !boostData.prepaidGiveaways.isEmpty { entries.append(.boostPrepaidTitle(presentationData.theme, presentationData.strings.Stats_Boosts_PrepaidGiveawaysTitle)) @@ -962,7 +963,7 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p if let boostersState, boostersState.count > 0 { boostersTitle = presentationData.strings.Stats_Boosts_Boosts(boostersState.count) boostersPlaceholder = nil - boostersFooter = presentationData.strings.Stats_Boosts_BoostersInfo + boostersFooter = isGroup ? "Your group is currently boosted by these members." : presentationData.strings.Stats_Boosts_BoostersInfo } else { boostersTitle = presentationData.strings.Stats_Boosts_BoostsNone boostersPlaceholder = presentationData.strings.Stats_Boosts_NoBoostersYet @@ -1033,11 +1034,11 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p entries.append(.boostLinkTitle(presentationData.theme, presentationData.strings.Stats_Boosts_LinkHeader)) entries.append(.boostLink(presentationData.theme, boostData.url)) - entries.append(.boostLinkInfo(presentationData.theme, presentationData.strings.Stats_Boosts_LinkInfo)) + entries.append(.boostLinkInfo(presentationData.theme, isGroup ? "Share this link with your members to get more boosts." : presentationData.strings.Stats_Boosts_LinkInfo)) if giveawayAvailable { entries.append(.gifts(presentationData.theme, presentationData.strings.Stats_Boosts_GetBoosts)) - entries.append(.giftsInfo(presentationData.theme, presentationData.strings.Stats_Boosts_GetBoostsInfo)) + entries.append(.giftsInfo(presentationData.theme, isGroup ? "Get more boosts for your group by gifting Premium to your subscribers." : presentationData.strings.Stats_Boosts_GetBoostsInfo)) } } } @@ -1098,8 +1099,10 @@ public func channelStatsController(context: AccountContext, updatedPresentationD var dismissAllTooltipsImpl: (() -> Void)? var presentImpl: ((ViewController) -> Void)? var pushImpl: ((ViewController) -> Void)? + var dismissImpl: (() -> Void)? var navigateToChatImpl: ((EnginePeer) -> Void)? var navigateToMessageImpl: ((EngineMessage.Id) -> Void)? + var openBoostImpl: ((Bool) -> Void)? let arguments = ChannelStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal in return statsContext.loadDetailedGraph(graph, x: x) @@ -1256,6 +1259,11 @@ public func channelStatsController(context: AccountContext, updatedPresentationD ) |> deliverOnMainQueue |> map { presentationData, state, peer, data, messageView, stories, boostData, boostersState, giftsState, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in + var isGroup = false + if let peer, case let .channel(channel) = peer, case .group = channel.info { + isGroup = true + } + let previous = previousData.swap(data) var emptyStateItem: ItemListControllerEmptyStateItem? switch state.section { @@ -1294,8 +1302,30 @@ public func channelStatsController(context: AccountContext, updatedPresentationD return map } - let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .sectionControl([presentationData.strings.Stats_Statistics, presentationData.strings.Stats_Boosts], state.section == .boosts ? 1 : 0), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(state: state, peer: peer, data: data, messages: messages, stories: stories, interactions: interactions, boostData: boostData, boostersState: boostersState, giftsState: giftsState, presentationData: presentationData, giveawayAvailable: premiumConfiguration.giveawayGiftsPurchaseAvailable), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false) + //TODO:localize + var title: ItemListControllerTitle + var headerItem: BoostHeaderItem? + var leftNavigationButton: ItemListNavigationButton? + var boostsOnly = false + if isGroup, section == .boosts, let boostStatus { + title = .text("") + headerItem = BoostHeaderItem(context: context, theme: presentationData.theme, strings: presentationData.strings, status: boostStatus, title: "Boost Group", text: "Members of your group can **boost** it so that it **levels up** and gets **exclusive features**.", openBoost: { + openBoostImpl?(false) + }, createGiveaway: { + arguments.openGifts() + }, openFeatures: { + openBoostImpl?(true) + }, back: { + dismissImpl?() + }) + leftNavigationButton = ItemListNavigationButton(content: .none, style: .regular, enabled: false, action: {}) + boostsOnly = true + } else { + title = .sectionControl([presentationData.strings.Stats_Statistics, presentationData.strings.Stats_Boosts], state.section == .boosts ? 1 : 0) + } + + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(state: state, peer: peer, data: data, messages: messages, stories: stories, interactions: interactions, boostData: boostData, boostersState: boostersState, giftsState: giftsState, presentationData: presentationData, giveawayAvailable: premiumConfiguration.giveawayGiftsPurchaseAvailable, isGroup: isGroup, boostsOnly: boostsOnly), style: .blocks, emptyStateItem: emptyStateItem, headerItem: headerItem, crossfadeState: previous == nil, animateChanges: false) return (controllerState, (listState, arguments)) } @@ -1436,6 +1466,9 @@ public func channelStatsController(context: AccountContext, updatedPresentationD pushImpl = { [weak controller] c in controller?.push(c) } + dismissImpl = { [weak controller] in + controller?.dismiss() + } navigateToChatImpl = { [weak controller] peer in if let navigationController = controller?.navigationController as? NavigationController { context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), keepStack: .always, purposefulAction: {}, peekData: nil)) @@ -1454,6 +1487,36 @@ public func channelStatsController(context: AccountContext, updatedPresentationD } }) } + openBoostImpl = { [weak controller] features in + if features { + let boostController = PremiumBoostLevelsScreen( + context: context, + peerId: peerId, + mode: .features, + status: nil, + myBoostStatus: nil + ) + controller?.push(boostController) + } else { + let _ = combineLatest( + queue: Queue.mainQueue(), + context.engine.peers.getChannelBoostStatus(peerId: peerId), + context.engine.peers.getMyBoostStatus() + ).startStandalone(next: { [weak controller] boostStatus, myBoostStatus in + guard let boostStatus, let myBoostStatus else { + return + } + let boostController = PremiumBoostLevelsScreen( + context: context, + peerId: peerId, + mode: .user(mode: .current), + status: boostStatus, + myBoostStatus: myBoostStatus + ) + controller?.push(boostController) + }) + } + } return controller } diff --git a/submodules/StatisticsUI/Sources/GroupStatsController.swift b/submodules/StatisticsUI/Sources/GroupStatsController.swift index 452a4c8aa0..6c05f8096f 100644 --- a/submodules/StatisticsUI/Sources/GroupStatsController.swift +++ b/submodules/StatisticsUI/Sources/GroupStatsController.swift @@ -381,7 +381,7 @@ private enum StatsEntry: ItemListNodeEntry { let .topInvitersTitle(_, text, dates): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: dates, color: .generic), sectionId: self.section) case let .overview(_, stats): - return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks) + return StatsOverviewItem(presentationData: presentationData, isGroup: true, stats: stats, sectionId: self.section, style: .blocks) case let .growthGraph(_, _, _, graph, type), let .membersGraph(_, _, _, graph, type), let .newMembersBySourceGraph(_, _, _, graph, type), diff --git a/submodules/StatisticsUI/Sources/MessageStatsController.swift b/submodules/StatisticsUI/Sources/MessageStatsController.swift index 4327d8489a..1c818fe3ed 100644 --- a/submodules/StatisticsUI/Sources/MessageStatsController.swift +++ b/submodules/StatisticsUI/Sources/MessageStatsController.swift @@ -160,7 +160,7 @@ private enum StatsEntry: ItemListNodeEntry { let .publicForwardsTitle(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .overview(_, stats, storyViews, publicShares): - return StatsOverviewItem(presentationData: presentationData, stats: stats as! Stats, storyViews: storyViews, publicShares: publicShares, sectionId: self.section, style: .blocks) + return StatsOverviewItem(presentationData: presentationData, isGroup: false, stats: stats as! Stats, storyViews: storyViews, publicShares: publicShares, sectionId: self.section, style: .blocks) case let .interactionsGraph(_, _, _, graph, type, noInitialZoom), let .reactionsGraph(_, _, _, graph, type, noInitialZoom): return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, noInitialZoom: noInitialZoom, getDetailsData: { date, completion in let _ = arguments.loadDetailedGraph(graph, Int64(date.timeIntervalSince1970) * 1000).start(next: { graph in diff --git a/submodules/StatisticsUI/Sources/StatsOverviewItem.swift b/submodules/StatisticsUI/Sources/StatsOverviewItem.swift index a5f5e7d029..473e3a23cd 100644 --- a/submodules/StatisticsUI/Sources/StatsOverviewItem.swift +++ b/submodules/StatisticsUI/Sources/StatsOverviewItem.swift @@ -34,14 +34,16 @@ extension StoryStats: Stats { class StatsOverviewItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData + let isGroup: Bool let stats: Stats let storyViews: EngineStoryItem.Views? let publicShares: Int32? let sectionId: ItemListSectionId let style: ItemListStyle - init(presentationData: ItemListPresentationData, stats: Stats, storyViews: EngineStoryItem.Views? = nil, publicShares: Int32? = nil, sectionId: ItemListSectionId, style: ItemListStyle) { + init(presentationData: ItemListPresentationData, isGroup: Bool, stats: Stats, storyViews: EngineStoryItem.Views? = nil, publicShares: Int32? = nil, sectionId: ItemListSectionId, style: ItemListStyle) { self.presentationData = presentationData + self.isGroup = isGroup self.stats = stats self.storyViews = storyViews self.publicShares = publicShares @@ -403,7 +405,7 @@ class StatsOverviewItemNode: ListViewItemNode { params.width, item.presentationData, "≈\(Int(stats.premiumAudience?.value ?? 0))", - item.presentationData.strings.Stats_Boosts_PremiumSubscribers, + item.isGroup ? item.presentationData.strings.Stats_Boosts_PremiumMembers : item.presentationData.strings.Stats_Boosts_PremiumSubscribers, (String(format: "%.02f%%", premiumSubscribers * 100.0), .generic) ) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 54f3b0acb3..f4a89cceb7 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -105,6 +105,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1427671598] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeAbout($0) } dict[-1102180616] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeAvailableReactions($0) } dict[1051328177] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeEmojiStatus($0) } + dict[1188577451] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeEmojiStickerSet($0) } dict[1855199800] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeHistoryTTL($0) } dict[84703944] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeLinkedChat($0) } dict[241923758] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeLocation($0) } @@ -176,7 +177,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1605510357] = { return Api.ChatAdminRights.parse_chatAdminRights($0) } dict[-219353309] = { return Api.ChatAdminWithInvites.parse_chatAdminWithInvites($0) } dict[-1626209256] = { return Api.ChatBannedRights.parse_chatBannedRights($0) } - dict[254528367] = { return Api.ChatFull.parse_channelFull($0) } + dict[1153455271] = { return Api.ChatFull.parse_channelFull($0) } dict[-908914376] = { return Api.ChatFull.parse_chatFull($0) } dict[-840897472] = { return Api.ChatInvite.parse_chatInvite($0) } dict[1516793212] = { return Api.ChatInvite.parse_chatInviteAlready($0) } @@ -472,7 +473,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[340088945] = { return Api.MediaArea.parse_mediaAreaSuggestedReaction($0) } dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) } dict[64088654] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) } - dict[1992213009] = { return Api.Message.parse_message($0) } + dict[508332649] = { return Api.Message.parse_message($0) } dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) } dict[721967202] = { return Api.Message.parse_messageService($0) } dict[-988359047] = { return Api.MessageAction.parse_messageActionBotAllowed($0) } @@ -1104,7 +1105,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[415997816] = { return Api.help.InviteText.parse_inviteText($0) } dict[-1600596305] = { return Api.help.PassportConfig.parse_passportConfig($0) } dict[-1078332329] = { return Api.help.PassportConfig.parse_passportConfigNotModified($0) } - dict[-276549461] = { return Api.help.PeerColorOption.parse_peerColorOption($0) } + dict[-1377014082] = { return Api.help.PeerColorOption.parse_peerColorOption($0) } dict[1987928555] = { return Api.help.PeerColorSet.parse_peerColorProfileSet($0) } dict[639736408] = { return Api.help.PeerColorSet.parse_peerColorSet($0) } dict[16313608] = { return Api.help.PeerColors.parse_peerColors($0) } @@ -1274,7 +1275,7 @@ public extension Api { return parser(reader) } else { - telegramApiLog("Type constructor \(String(UInt32(bitPattern: signature), radix: 16, uppercase: false)) not found") + telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") return nil } } diff --git a/submodules/TelegramApi/Sources/Api12.swift b/submodules/TelegramApi/Sources/Api12.swift index c1740387cb..71d2947203 100644 --- a/submodules/TelegramApi/Sources/Api12.swift +++ b/submodules/TelegramApi/Sources/Api12.swift @@ -424,19 +424,20 @@ public extension Api { } public extension Api { indirect enum Message: TypeConstructorDescription { - case message(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?) + case message(flags: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?) case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?) case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, ttlPeriod: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .message(let flags, let id, let fromId, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod): + case .message(let flags, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod): if boxed { - buffer.appendInt32(1992213009) + buffer.appendInt32(508332649) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 8) != 0 {fromId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 29) != 0 {serializeInt32(fromBoostsApplied!, buffer: buffer, boxed: false)} peerId.serialize(buffer, true) if Int(flags) & Int(1 << 28) != 0 {savedPeerId!.serialize(buffer, true)} if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} @@ -491,8 +492,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .message(let flags, let id, let fromId, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod): - return ("message", [("flags", flags as Any), ("id", id as Any), ("fromId", fromId as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any)]) + case .message(let flags, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod): + return ("message", [("flags", flags as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any)]) case .messageEmpty(let flags, let id, let peerId): return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)]) case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action, let ttlPeriod): @@ -509,88 +510,91 @@ public extension Api { if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.Peer } } - var _4: Api.Peer? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } + var _4: Int32? + if Int(_1!) & Int(1 << 29) != 0 {_4 = reader.readInt32() } var _5: Api.Peer? - if Int(_1!) & Int(1 << 28) != 0 {if let signature = reader.readInt32() { + if let signature = reader.readInt32() { _5 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _6: Api.Peer? + if Int(_1!) & Int(1 << 28) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Peer } } - var _6: Api.MessageFwdHeader? + var _7: Api.MessageFwdHeader? if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader + _7 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader } } - var _7: Int64? - if Int(_1!) & Int(1 << 11) != 0 {_7 = reader.readInt64() } - var _8: Api.MessageReplyHeader? + var _8: Int64? + if Int(_1!) & Int(1 << 11) != 0 {_8 = reader.readInt64() } + var _9: Api.MessageReplyHeader? if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader + _9 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader } } - var _9: Int32? - _9 = reader.readInt32() - var _10: String? - _10 = parseString(reader) - var _11: Api.MessageMedia? + var _10: Int32? + _10 = reader.readInt32() + var _11: String? + _11 = parseString(reader) + var _12: Api.MessageMedia? if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.MessageMedia + _12 = Api.parse(reader, signature: signature) as? Api.MessageMedia } } - var _12: Api.ReplyMarkup? + var _13: Api.ReplyMarkup? if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup + _13 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } } - var _13: [Api.MessageEntity]? + var _14: [Api.MessageEntity]? if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _13 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } - var _14: Int32? - if Int(_1!) & Int(1 << 10) != 0 {_14 = reader.readInt32() } var _15: Int32? if Int(_1!) & Int(1 << 10) != 0 {_15 = reader.readInt32() } - var _16: Api.MessageReplies? + var _16: Int32? + if Int(_1!) & Int(1 << 10) != 0 {_16 = reader.readInt32() } + var _17: Api.MessageReplies? if Int(_1!) & Int(1 << 23) != 0 {if let signature = reader.readInt32() { - _16 = Api.parse(reader, signature: signature) as? Api.MessageReplies + _17 = Api.parse(reader, signature: signature) as? Api.MessageReplies } } - var _17: Int32? - if Int(_1!) & Int(1 << 15) != 0 {_17 = reader.readInt32() } - var _18: String? - if Int(_1!) & Int(1 << 16) != 0 {_18 = parseString(reader) } - var _19: Int64? - if Int(_1!) & Int(1 << 17) != 0 {_19 = reader.readInt64() } - var _20: Api.MessageReactions? + var _18: Int32? + if Int(_1!) & Int(1 << 15) != 0 {_18 = reader.readInt32() } + var _19: String? + if Int(_1!) & Int(1 << 16) != 0 {_19 = parseString(reader) } + var _20: Int64? + if Int(_1!) & Int(1 << 17) != 0 {_20 = reader.readInt64() } + var _21: Api.MessageReactions? if Int(_1!) & Int(1 << 20) != 0 {if let signature = reader.readInt32() { - _20 = Api.parse(reader, signature: signature) as? Api.MessageReactions + _21 = Api.parse(reader, signature: signature) as? Api.MessageReactions } } - var _21: [Api.RestrictionReason]? + var _22: [Api.RestrictionReason]? if Int(_1!) & Int(1 << 22) != 0 {if let _ = reader.readInt32() { - _21 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) + _22 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) } } - var _22: Int32? - if Int(_1!) & Int(1 << 25) != 0 {_22 = reader.readInt32() } + var _23: Int32? + if Int(_1!) & Int(1 << 25) != 0 {_23 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 28) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 11) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - let _c9 = _9 != nil + let _c4 = (Int(_1!) & Int(1 << 29) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 28) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 11) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil let _c10 = _10 != nil - let _c11 = (Int(_1!) & Int(1 << 9) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 6) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 7) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 10) == 0) || _14 != nil + let _c11 = _11 != nil + let _c12 = (Int(_1!) & Int(1 << 9) == 0) || _12 != nil + let _c13 = (Int(_1!) & Int(1 << 6) == 0) || _13 != nil + let _c14 = (Int(_1!) & Int(1 << 7) == 0) || _14 != nil let _c15 = (Int(_1!) & Int(1 << 10) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 23) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 15) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 16) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 17) == 0) || _19 != nil - let _c20 = (Int(_1!) & Int(1 << 20) == 0) || _20 != nil - let _c21 = (Int(_1!) & Int(1 << 22) == 0) || _21 != nil - let _c22 = (Int(_1!) & Int(1 << 25) == 0) || _22 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 { - return Api.Message.message(flags: _1!, id: _2!, fromId: _3, peerId: _4!, savedPeerId: _5, fwdFrom: _6, viaBotId: _7, replyTo: _8, date: _9!, message: _10!, media: _11, replyMarkup: _12, entities: _13, views: _14, forwards: _15, replies: _16, editDate: _17, postAuthor: _18, groupedId: _19, reactions: _20, restrictionReason: _21, ttlPeriod: _22) + let _c16 = (Int(_1!) & Int(1 << 10) == 0) || _16 != nil + let _c17 = (Int(_1!) & Int(1 << 23) == 0) || _17 != nil + let _c18 = (Int(_1!) & Int(1 << 15) == 0) || _18 != nil + let _c19 = (Int(_1!) & Int(1 << 16) == 0) || _19 != nil + let _c20 = (Int(_1!) & Int(1 << 17) == 0) || _20 != nil + let _c21 = (Int(_1!) & Int(1 << 20) == 0) || _21 != nil + let _c22 = (Int(_1!) & Int(1 << 22) == 0) || _22 != nil + let _c23 = (Int(_1!) & Int(1 << 25) == 0) || _23 != 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 { + return Api.Message.message(flags: _1!, id: _2!, fromId: _3, fromBoostsApplied: _4, peerId: _5!, savedPeerId: _6, fwdFrom: _7, viaBotId: _8, replyTo: _9, date: _10!, message: _11!, media: _12, replyMarkup: _13, entities: _14, views: _15, forwards: _16, replies: _17, editDate: _18, postAuthor: _19, groupedId: _20, reactions: _21, restrictionReason: _22, ttlPeriod: _23) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index e8bb5e4205..5f696bd03a 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -725,6 +725,7 @@ public extension Api { case channelAdminLogEventActionChangeAbout(prevValue: String, newValue: String) case channelAdminLogEventActionChangeAvailableReactions(prevValue: Api.ChatReactions, newValue: Api.ChatReactions) case channelAdminLogEventActionChangeEmojiStatus(prevValue: Api.EmojiStatus, newValue: Api.EmojiStatus) + case channelAdminLogEventActionChangeEmojiStickerSet(prevStickerset: Api.InputStickerSet, newStickerset: Api.InputStickerSet) case channelAdminLogEventActionChangeHistoryTTL(prevValue: Int32, newValue: Int32) case channelAdminLogEventActionChangeLinkedChat(prevValue: Int64, newValue: Int64) case channelAdminLogEventActionChangeLocation(prevValue: Api.ChannelLocation, newValue: Api.ChannelLocation) @@ -793,6 +794,13 @@ public extension Api { prevValue.serialize(buffer, true) newValue.serialize(buffer, true) break + case .channelAdminLogEventActionChangeEmojiStickerSet(let prevStickerset, let newStickerset): + if boxed { + buffer.appendInt32(1188577451) + } + prevStickerset.serialize(buffer, true) + newStickerset.serialize(buffer, true) + break case .channelAdminLogEventActionChangeHistoryTTL(let prevValue, let newValue): if boxed { buffer.appendInt32(1855199800) @@ -1098,6 +1106,8 @@ public extension Api { return ("channelAdminLogEventActionChangeAvailableReactions", [("prevValue", prevValue as Any), ("newValue", newValue as Any)]) case .channelAdminLogEventActionChangeEmojiStatus(let prevValue, let newValue): return ("channelAdminLogEventActionChangeEmojiStatus", [("prevValue", prevValue as Any), ("newValue", newValue as Any)]) + case .channelAdminLogEventActionChangeEmojiStickerSet(let prevStickerset, let newStickerset): + return ("channelAdminLogEventActionChangeEmojiStickerSet", [("prevStickerset", prevStickerset as Any), ("newStickerset", newStickerset as Any)]) case .channelAdminLogEventActionChangeHistoryTTL(let prevValue, let newValue): return ("channelAdminLogEventActionChangeHistoryTTL", [("prevValue", prevValue as Any), ("newValue", newValue as Any)]) case .channelAdminLogEventActionChangeLinkedChat(let prevValue, let newValue): @@ -1239,6 +1249,24 @@ public extension Api { return nil } } + public static func parse_channelAdminLogEventActionChangeEmojiStickerSet(_ reader: BufferReader) -> ChannelAdminLogEventAction? { + var _1: Api.InputStickerSet? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputStickerSet + } + var _2: Api.InputStickerSet? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputStickerSet + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeEmojiStickerSet(prevStickerset: _1!, newStickerset: _2!) + } + else { + return nil + } + } public static func parse_channelAdminLogEventActionChangeHistoryTTL(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Int32? _1 = reader.readInt32() diff --git a/submodules/TelegramApi/Sources/Api26.swift b/submodules/TelegramApi/Sources/Api26.swift index 60f1674a87..e688ea037b 100644 --- a/submodules/TelegramApi/Sources/Api26.swift +++ b/submodules/TelegramApi/Sources/Api26.swift @@ -1104,27 +1104,28 @@ public extension Api.help { } public extension Api.help { enum PeerColorOption: TypeConstructorDescription { - case peerColorOption(flags: Int32, colorId: Int32, colors: Api.help.PeerColorSet?, darkColors: Api.help.PeerColorSet?, channelMinLevel: Int32?) + case peerColorOption(flags: Int32, colorId: Int32, colors: Api.help.PeerColorSet?, darkColors: Api.help.PeerColorSet?, channelMinLevel: Int32?, groupMinLevel: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .peerColorOption(let flags, let colorId, let colors, let darkColors, let channelMinLevel): + case .peerColorOption(let flags, let colorId, let colors, let darkColors, let channelMinLevel, let groupMinLevel): if boxed { - buffer.appendInt32(-276549461) + buffer.appendInt32(-1377014082) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(colorId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 1) != 0 {colors!.serialize(buffer, true)} if Int(flags) & Int(1 << 2) != 0 {darkColors!.serialize(buffer, true)} if Int(flags) & Int(1 << 3) != 0 {serializeInt32(channelMinLevel!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(groupMinLevel!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .peerColorOption(let flags, let colorId, let colors, let darkColors, let channelMinLevel): - return ("peerColorOption", [("flags", flags as Any), ("colorId", colorId as Any), ("colors", colors as Any), ("darkColors", darkColors as Any), ("channelMinLevel", channelMinLevel as Any)]) + case .peerColorOption(let flags, let colorId, let colors, let darkColors, let channelMinLevel, let groupMinLevel): + return ("peerColorOption", [("flags", flags as Any), ("colorId", colorId as Any), ("colors", colors as Any), ("darkColors", darkColors as Any), ("channelMinLevel", channelMinLevel as Any), ("groupMinLevel", groupMinLevel as Any)]) } } @@ -1143,13 +1144,16 @@ public extension Api.help { } } var _5: Int32? if Int(_1!) & Int(1 << 3) != 0 {_5 = reader.readInt32() } + var _6: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_6 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.help.PeerColorOption.peerColorOption(flags: _1!, colorId: _2!, colors: _3, darkColors: _4, channelMinLevel: _5) + let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.help.PeerColorOption.peerColorOption(flags: _1!, colorId: _2!, colors: _3, darkColors: _4, channelMinLevel: _5, groupMinLevel: _6) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 3baff622ff..1548b8695b 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -920,14 +920,14 @@ public extension Api { } public extension Api { enum ChatFull: TypeConstructorDescription { - case channelFull(flags: Int32, flags2: Int32, id: Int64, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int64?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int64?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?, ttlPeriod: Int32?, pendingSuggestions: [String]?, groupcallDefaultJoinAs: Api.Peer?, themeEmoticon: String?, requestsPending: Int32?, recentRequesters: [Int64]?, defaultSendAs: Api.Peer?, availableReactions: Api.ChatReactions?, stories: Api.PeerStories?, wallpaper: Api.WallPaper?) + case channelFull(flags: Int32, flags2: Int32, id: Int64, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int64?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int64?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?, ttlPeriod: Int32?, pendingSuggestions: [String]?, groupcallDefaultJoinAs: Api.Peer?, themeEmoticon: String?, requestsPending: Int32?, recentRequesters: [Int64]?, defaultSendAs: Api.Peer?, availableReactions: Api.ChatReactions?, stories: Api.PeerStories?, wallpaper: Api.WallPaper?, boostsApplied: Int32?, boostsUnrestrict: Int32?, emojiset: Api.StickerSet?) case chatFull(flags: Int32, id: Int64, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?, ttlPeriod: Int32?, groupcallDefaultJoinAs: Api.Peer?, themeEmoticon: String?, requestsPending: Int32?, recentRequesters: [Int64]?, availableReactions: Api.ChatReactions?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .channelFull(let flags, let flags2, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending, let recentRequesters, let defaultSendAs, let availableReactions, let stories, let wallpaper): + case .channelFull(let flags, let flags2, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending, let recentRequesters, let defaultSendAs, let availableReactions, let stories, let wallpaper, let boostsApplied, let boostsUnrestrict, let emojiset): if boxed { - buffer.appendInt32(254528367) + buffer.appendInt32(1153455271) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags2, buffer: buffer, boxed: false) @@ -980,6 +980,9 @@ public extension Api { if Int(flags) & Int(1 << 30) != 0 {availableReactions!.serialize(buffer, true)} if Int(flags2) & Int(1 << 4) != 0 {stories!.serialize(buffer, true)} if Int(flags2) & Int(1 << 7) != 0 {wallpaper!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 8) != 0 {serializeInt32(boostsApplied!, buffer: buffer, boxed: false)} + if Int(flags2) & Int(1 << 9) != 0 {serializeInt32(boostsUnrestrict!, buffer: buffer, boxed: false)} + if Int(flags2) & Int(1 << 10) != 0 {emojiset!.serialize(buffer, true)} break case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttlPeriod, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending, let recentRequesters, let availableReactions): if boxed { @@ -1016,8 +1019,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .channelFull(let flags, let flags2, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending, let recentRequesters, let defaultSendAs, let availableReactions, let stories, let wallpaper): - return ("channelFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("participantsCount", participantsCount as Any), ("adminsCount", adminsCount as Any), ("kickedCount", kickedCount as Any), ("bannedCount", bannedCount as Any), ("onlineCount", onlineCount as Any), ("readInboxMaxId", readInboxMaxId as Any), ("readOutboxMaxId", readOutboxMaxId as Any), ("unreadCount", unreadCount as Any), ("chatPhoto", chatPhoto as Any), ("notifySettings", notifySettings as Any), ("exportedInvite", exportedInvite as Any), ("botInfo", botInfo as Any), ("migratedFromChatId", migratedFromChatId as Any), ("migratedFromMaxId", migratedFromMaxId as Any), ("pinnedMsgId", pinnedMsgId as Any), ("stickerset", stickerset as Any), ("availableMinId", availableMinId as Any), ("folderId", folderId as Any), ("linkedChatId", linkedChatId as Any), ("location", location as Any), ("slowmodeSeconds", slowmodeSeconds as Any), ("slowmodeNextSendDate", slowmodeNextSendDate as Any), ("statsDc", statsDc as Any), ("pts", pts as Any), ("call", call as Any), ("ttlPeriod", ttlPeriod as Any), ("pendingSuggestions", pendingSuggestions as Any), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs as Any), ("themeEmoticon", themeEmoticon as Any), ("requestsPending", requestsPending as Any), ("recentRequesters", recentRequesters as Any), ("defaultSendAs", defaultSendAs as Any), ("availableReactions", availableReactions as Any), ("stories", stories as Any), ("wallpaper", wallpaper as Any)]) + case .channelFull(let flags, let flags2, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending, let recentRequesters, let defaultSendAs, let availableReactions, let stories, let wallpaper, let boostsApplied, let boostsUnrestrict, let emojiset): + return ("channelFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("participantsCount", participantsCount as Any), ("adminsCount", adminsCount as Any), ("kickedCount", kickedCount as Any), ("bannedCount", bannedCount as Any), ("onlineCount", onlineCount as Any), ("readInboxMaxId", readInboxMaxId as Any), ("readOutboxMaxId", readOutboxMaxId as Any), ("unreadCount", unreadCount as Any), ("chatPhoto", chatPhoto as Any), ("notifySettings", notifySettings as Any), ("exportedInvite", exportedInvite as Any), ("botInfo", botInfo as Any), ("migratedFromChatId", migratedFromChatId as Any), ("migratedFromMaxId", migratedFromMaxId as Any), ("pinnedMsgId", pinnedMsgId as Any), ("stickerset", stickerset as Any), ("availableMinId", availableMinId as Any), ("folderId", folderId as Any), ("linkedChatId", linkedChatId as Any), ("location", location as Any), ("slowmodeSeconds", slowmodeSeconds as Any), ("slowmodeNextSendDate", slowmodeNextSendDate as Any), ("statsDc", statsDc as Any), ("pts", pts as Any), ("call", call as Any), ("ttlPeriod", ttlPeriod as Any), ("pendingSuggestions", pendingSuggestions as Any), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs as Any), ("themeEmoticon", themeEmoticon as Any), ("requestsPending", requestsPending as Any), ("recentRequesters", recentRequesters as Any), ("defaultSendAs", defaultSendAs as Any), ("availableReactions", availableReactions as Any), ("stories", stories as Any), ("wallpaper", wallpaper as Any), ("boostsApplied", boostsApplied as Any), ("boostsUnrestrict", boostsUnrestrict as Any), ("emojiset", emojiset as Any)]) case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttlPeriod, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending, let recentRequesters, let availableReactions): return ("chatFull", [("flags", flags as Any), ("id", id as Any), ("about", about as Any), ("participants", participants as Any), ("chatPhoto", chatPhoto as Any), ("notifySettings", notifySettings as Any), ("exportedInvite", exportedInvite as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("folderId", folderId as Any), ("call", call as Any), ("ttlPeriod", ttlPeriod as Any), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs as Any), ("themeEmoticon", themeEmoticon as Any), ("requestsPending", requestsPending as Any), ("recentRequesters", recentRequesters as Any), ("availableReactions", availableReactions as Any)]) } @@ -1130,6 +1133,14 @@ public extension Api { if Int(_2!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() { _39 = Api.parse(reader, signature: signature) as? Api.WallPaper } } + var _40: Int32? + if Int(_2!) & Int(1 << 8) != 0 {_40 = reader.readInt32() } + var _41: Int32? + if Int(_2!) & Int(1 << 9) != 0 {_41 = reader.readInt32() } + var _42: Api.StickerSet? + if Int(_2!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() { + _42 = Api.parse(reader, signature: signature) as? Api.StickerSet + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -1169,8 +1180,11 @@ public extension Api { let _c37 = (Int(_1!) & Int(1 << 30) == 0) || _37 != nil let _c38 = (Int(_2!) & Int(1 << 4) == 0) || _38 != nil let _c39 = (Int(_2!) & Int(1 << 7) == 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.ChatFull.channelFull(flags: _1!, flags2: _2!, id: _3!, about: _4!, participantsCount: _5, adminsCount: _6, kickedCount: _7, bannedCount: _8, onlineCount: _9, readInboxMaxId: _10!, readOutboxMaxId: _11!, unreadCount: _12!, chatPhoto: _13!, notifySettings: _14!, exportedInvite: _15, botInfo: _16!, migratedFromChatId: _17, migratedFromMaxId: _18, pinnedMsgId: _19, stickerset: _20, availableMinId: _21, folderId: _22, linkedChatId: _23, location: _24, slowmodeSeconds: _25, slowmodeNextSendDate: _26, statsDc: _27, pts: _28!, call: _29, ttlPeriod: _30, pendingSuggestions: _31, groupcallDefaultJoinAs: _32, themeEmoticon: _33, requestsPending: _34, recentRequesters: _35, defaultSendAs: _36, availableReactions: _37, stories: _38, wallpaper: _39) + let _c40 = (Int(_2!) & Int(1 << 8) == 0) || _40 != nil + let _c41 = (Int(_2!) & Int(1 << 9) == 0) || _41 != nil + let _c42 = (Int(_2!) & Int(1 << 10) == 0) || _42 != 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 && _c40 && _c41 && _c42 { + return Api.ChatFull.channelFull(flags: _1!, flags2: _2!, id: _3!, about: _4!, participantsCount: _5, adminsCount: _6, kickedCount: _7, bannedCount: _8, onlineCount: _9, readInboxMaxId: _10!, readOutboxMaxId: _11!, unreadCount: _12!, chatPhoto: _13!, notifySettings: _14!, exportedInvite: _15, botInfo: _16!, migratedFromChatId: _17, migratedFromMaxId: _18, pinnedMsgId: _19, stickerset: _20, availableMinId: _21, folderId: _22, linkedChatId: _23, location: _24, slowmodeSeconds: _25, slowmodeNextSendDate: _26, statsDc: _27, pts: _28!, call: _29, ttlPeriod: _30, pendingSuggestions: _31, groupcallDefaultJoinAs: _32, themeEmoticon: _33, requestsPending: _34, recentRequesters: _35, defaultSendAs: _36, availableReactions: _37, stories: _38, wallpaper: _39, boostsApplied: _40, boostsUnrestrict: _41, emojiset: _42) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api32.swift b/submodules/TelegramApi/Sources/Api32.swift index a09ade3911..d280ba8d81 100644 --- a/submodules/TelegramApi/Sources/Api32.swift +++ b/submodules/TelegramApi/Sources/Api32.swift @@ -1851,14 +1851,15 @@ public extension Api.functions.auth { } } public extension Api.functions.auth { - static func signUp(phoneNumber: String, phoneCodeHash: String, firstName: String, lastName: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func signUp(flags: Int32, phoneNumber: String, phoneCodeHash: String, firstName: String, lastName: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-2131827673) + buffer.appendInt32(-1429752041) + serializeInt32(flags, buffer: buffer, boxed: false) serializeString(phoneNumber, buffer: buffer, boxed: false) serializeString(phoneCodeHash, buffer: buffer, boxed: false) serializeString(firstName, buffer: buffer, boxed: false) serializeString(lastName, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.signUp", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + return (FunctionDescription(name: "auth.signUp", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in let reader = BufferReader(buffer) var result: Api.auth.Authorization? if let signature = reader.readInt32() { @@ -2886,6 +2887,22 @@ public extension Api.functions.channels { }) } } +public extension Api.functions.channels { + static func setBoostsToUnblockRestrictions(channel: Api.InputChannel, boosts: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1388733202) + channel.serialize(buffer, true) + serializeInt32(boosts, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.setBoostsToUnblockRestrictions", parameters: [("channel", String(describing: channel)), ("boosts", String(describing: boosts))]), 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 + }) + } +} public extension Api.functions.channels { static func setDiscussionGroup(broadcast: Api.InputChannel, group: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -2902,6 +2919,22 @@ public extension Api.functions.channels { }) } } +public extension Api.functions.channels { + static func setEmojiStickers(channel: Api.InputChannel, stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1020866743) + channel.serialize(buffer, true) + stickerset.serialize(buffer, true) + return (FunctionDescription(name: "channels.setEmojiStickers", parameters: [("channel", String(describing: channel)), ("stickerset", String(describing: stickerset))]), 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.channels { static func setStickers(channel: Api.InputChannel, stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -5901,11 +5934,13 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func getSavedReactionTags(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func getSavedReactionTags(flags: Int32, peer: Api.InputPeer?, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1981668047) + buffer.appendInt32(909631579) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {peer!.serialize(buffer, true)} serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getSavedReactionTags", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedReactionTags? in + return (FunctionDescription(name: "messages.getSavedReactionTags", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedReactionTags? in let reader = BufferReader(buffer) var result: Api.messages.SavedReactionTags? if let signature = reader.readInt32() { diff --git a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift index 7b1431d82f..cb4a3e086b 100644 --- a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift +++ b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift @@ -18,10 +18,9 @@ public enum ManagedAudioSessionType: Equatable { case ambient case play(mixWithOthers: Bool) case playWithPossiblePortOverride - case record(speaker: Bool, withOthers: Bool) + case record(speaker: Bool, video: Bool, withOthers: Bool) case voiceCall case videoCall - case recordWithOthers var isPlay: Bool { switch self { @@ -39,7 +38,7 @@ private func nativeCategoryForType(_ type: ManagedAudioSessionType, headphones: return .ambient case .play: return .playback - case .record, .recordWithOthers, .voiceCall, .videoCall: + case .record, .voiceCall, .videoCall: return .playAndRecord case .playWithPossiblePortOverride: if headphones { @@ -633,10 +632,8 @@ public final class ManagedAudioSession: NSObject { var lastIsRecordWithOthers = false if let lastHolder = self.holders.last { - if case let .record(_, withOthers) = lastHolder.audioSessionType { + if case let .record(_, _, withOthers) = lastHolder.audioSessionType { lastIsRecordWithOthers = withOthers - } else if case .recordWithOthers = lastHolder.audioSessionType { - lastIsRecordWithOthers = true } } if !deactivating { @@ -804,11 +801,14 @@ public final class ManagedAudioSession: NSObject { options.insert(.allowBluetooth) options.insert(.allowBluetoothA2DP) options.insert(.mixWithOthers) - case .record: + case let .record(_, video, mixWithOthers): options.insert(.allowBluetooth) - case .recordWithOthers: - options.insert(.allowBluetoothA2DP) - options.insert(.mixWithOthers) + if video { + options.insert(.allowBluetoothA2DP) + } + if mixWithOthers { + options.insert(.mixWithOthers) + } } managedAudioSessionLog("ManagedAudioSession setting category and options") let mode: AVAudioSession.Mode @@ -817,7 +817,7 @@ public final class ManagedAudioSession: NSObject { mode = .voiceChat case .videoCall: mode = .videoChat - case .recordWithOthers: + case .record(_, true, _): mode = .videoRecording default: mode = .default @@ -838,7 +838,7 @@ public final class ManagedAudioSession: NSObject { try AVAudioSession.sharedInstance().setMode(mode) if AVAudioSession.sharedInstance().categoryOptions != options { switch type { - case .voiceCall, .videoCall, .recordWithOthers: + case .voiceCall, .videoCall: managedAudioSessionLog("ManagedAudioSession resetting options") try AVAudioSession.sharedInstance().setCategory(nativeCategory, options: options) default: @@ -960,15 +960,26 @@ public final class ManagedAudioSession: NSObject { try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none) } + if case let .record(_, video, _) = type, video, let input = AVAudioSession.sharedInstance().availableInputs?.first { + if let dataSources = input.dataSources { + for source in dataSources { + if source.dataSourceName.contains("Front") { + try? input.setPreferredDataSource(source) + break + } + } + } + } + if resetToBuiltin { var updatedType = type - if case .record(false, let withOthers) = updatedType, self.isHeadsetPluggedInValue { - updatedType = .record(speaker: true, withOthers: withOthers) + if case .record(false, let video, let withOthers) = updatedType, self.isHeadsetPluggedInValue { + updatedType = .record(speaker: true, video: video, withOthers: withOthers) } switch updatedType { - case .record(false, _): + case .record(false, _, _): try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker) - case .voiceCall, .playWithPossiblePortOverride, .record(true, _): + case .voiceCall, .playWithPossiblePortOverride, .record(true, _, _): try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none) if let routes = AVAudioSession.sharedInstance().availableInputs { var alreadySet = false diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index 035ffaa88e..06e33fa927 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -101,6 +101,7 @@ private var declaredEncodables: Void = { declareEncodable(TelegramMediaWebpage.self, f: { TelegramMediaWebpage(decoder: $0) }) declareEncodable(ViewCountMessageAttribute.self, f: { ViewCountMessageAttribute(decoder: $0) }) declareEncodable(ForwardCountMessageAttribute.self, f: { ForwardCountMessageAttribute(decoder: $0) }) + declareEncodable(BoostCountMessageAttribute.self, f: { BoostCountMessageAttribute(decoder: $0) }) declareEncodable(NotificationInfoMessageAttribute.self, f: { NotificationInfoMessageAttribute(decoder: $0) }) declareEncodable(TelegramMediaAction.self, f: { TelegramMediaAction(decoder: $0) }) declareEncodable(TelegramPeerNotificationSettings.self, f: { TelegramPeerNotificationSettings(decoder: $0) }) diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index c77129451d..c85af6eb72 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -126,7 +126,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { switch messsage { - case let .message(_, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let chatPeerId = messagePeerId return chatPeerId.peerId case let .messageEmpty(_, _, peerId): @@ -142,7 +142,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { switch message { - case let .message(_, _, fromId, chatPeerId, savedPeerId, fwdHeader, viaBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _): + case let .message(_, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _): let peerId: PeerId = chatPeerId.peerId var result = [peerId] @@ -263,7 +263,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: ReferencedReplyMessageIds, generalIds: [MessageId])? { switch message { - case let .message(_, id, _, chatPeerId, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, id, _, _, chatPeerId, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if let replyTo = replyTo { let peerId: PeerId = chatPeerId.peerId @@ -597,7 +597,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, id, fromId, chatPeerId, savedPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod): + case let .message(flags, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod): let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId let peerId: PeerId @@ -800,6 +800,12 @@ extension StoreMessage { attributes.append(ForwardCountMessageAttribute(count: Int(forwards))) } } + + if namespace == Namespaces.Message.Cloud { + if let boosts = boosts { + attributes.append(BoostCountMessageAttribute(count: Int(boosts))) + } + } if let editDate = editDate { attributes.append(EditedMessageAttribute(date: editDate, isHidden: (flags & (1 << 21)) != 0)) diff --git a/submodules/TelegramCore/Sources/Authorization.swift b/submodules/TelegramCore/Sources/Authorization.swift index 0796004f0d..97cd8f9b07 100644 --- a/submodules/TelegramCore/Sources/Authorization.swift +++ b/submodules/TelegramCore/Sources/Authorization.swift @@ -1208,10 +1208,14 @@ public enum SignUpError { case invalidLastName } -public func signUpWithName(accountManager: AccountManager, account: UnauthorizedAccount, firstName: String, lastName: String, avatarData: Data?, avatarVideo: Signal?, videoStartTimestamp: Double?, forcedPasswordSetupNotice: @escaping (Int32) -> (NoticeEntryKey, CodableEntry)?) -> Signal { +public func signUpWithName(accountManager: AccountManager, account: UnauthorizedAccount, firstName: String, lastName: String, avatarData: Data?, avatarVideo: Signal?, videoStartTimestamp: Double?, disableJoinNotifications: Bool = false, forcedPasswordSetupNotice: @escaping (Int32) -> (NoticeEntryKey, CodableEntry)?) -> Signal { return account.postbox.transaction { transaction -> Signal in if let state = transaction.getState() as? UnauthorizedAccountState, case let .signUp(number, codeHash, _, _, _, syncContacts) = state.contents { - return account.network.request(Api.functions.auth.signUp(phoneNumber: number, phoneCodeHash: codeHash, firstName: firstName, lastName: lastName)) + var flags: Int32 = 0 + if disableJoinNotifications { + flags |= (1 << 0) + } + return account.network.request(Api.functions.auth.signUp(flags: flags, phoneNumber: number, phoneCodeHash: codeHash, firstName: firstName, lastName: lastName)) |> mapError { error -> SignUpError in if error.errorDescription.hasPrefix("FLOOD_WAIT") { return .limitExceeded diff --git a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift index cd37ccc932..6bb8c76657 100644 --- a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift @@ -96,7 +96,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes var updatedTimestamp: Int32? if let apiMessage = apiMessage { switch apiMessage { - case let .message(_, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _): updatedTimestamp = date case .messageEmpty: break diff --git a/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift b/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift index 0f75973ee7..1dd9ea2807 100644 --- a/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift +++ b/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift @@ -557,7 +557,7 @@ private func synchronizeSavedMessageTags(postbox: Postbox, network: Network, pee return .complete() } - return network.request(Api.functions.messages.getSavedReactionTags(hash: 0)) + return network.request(Api.functions.messages.getSavedReactionTags(flags: 0, peer: nil, hash: 0)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/Sources/State/ManagedPeerColorUpdates.swift b/submodules/TelegramCore/Sources/State/ManagedPeerColorUpdates.swift index 76c5dc6fcc..a75ad876ba 100644 --- a/submodules/TelegramCore/Sources/State/ManagedPeerColorUpdates.swift +++ b/submodules/TelegramCore/Sources/State/ManagedPeerColorUpdates.swift @@ -95,18 +95,21 @@ public final class EngineAvailableColorOptions: Codable, Equatable { case dark = "d" case isHidden = "h" case requiredChannelMinBoostLevel = "rcmb" + case requiredGroupMinBoostLevel = "rgmb" } public let light: ColorOption public let dark: ColorOption? public let isHidden: Bool public let requiredChannelMinBoostLevel: Int32? + public let requiredGroupMinBoostLevel: Int32? - public init(light: ColorOption, dark: ColorOption?, isHidden: Bool, requiredChannelMinBoostLevel: Int32?) { + public init(light: ColorOption, dark: ColorOption?, isHidden: Bool, requiredChannelMinBoostLevel: Int32?, requiredGroupMinBoostLevel: Int32?) { self.light = light self.dark = dark self.isHidden = isHidden self.requiredChannelMinBoostLevel = requiredChannelMinBoostLevel + self.requiredGroupMinBoostLevel = requiredGroupMinBoostLevel } public init(from decoder: Decoder) throws { @@ -116,6 +119,7 @@ public final class EngineAvailableColorOptions: Codable, Equatable { self.dark = try container.decodeIfPresent(ColorOption.self, forKey: .dark) self.isHidden = try container.decode(Bool.self, forKey: .isHidden) self.requiredChannelMinBoostLevel = try container.decodeIfPresent(Int32.self, forKey: .requiredChannelMinBoostLevel) + self.requiredGroupMinBoostLevel = try container.decodeIfPresent(Int32.self, forKey: .requiredGroupMinBoostLevel) } public func encode(to encoder: Encoder) throws { @@ -125,6 +129,7 @@ public final class EngineAvailableColorOptions: Codable, Equatable { try container.encodeIfPresent(self.dark, forKey: .dark) try container.encodeIfPresent(self.isHidden, forKey: .isHidden) try container.encodeIfPresent(self.requiredChannelMinBoostLevel, forKey: .requiredChannelMinBoostLevel) + try container.encodeIfPresent(self.requiredGroupMinBoostLevel, forKey: .requiredGroupMinBoostLevel) } public static func ==(lhs: ColorOptionPack, rhs: ColorOptionPack) -> Bool { @@ -143,6 +148,9 @@ public final class EngineAvailableColorOptions: Codable, Equatable { if lhs.requiredChannelMinBoostLevel != rhs.requiredChannelMinBoostLevel { return false } + if lhs.requiredGroupMinBoostLevel != rhs.requiredGroupMinBoostLevel { + return false + } return true } } @@ -270,14 +278,14 @@ private extension EngineAvailableColorOptions { var mappedOptions: [Option] = [] for apiColor in apiColors { switch apiColor { - case let .peerColorOption(flags, colorId, colors, darkColors, requiredChannelMinBoostLevel): + case let .peerColorOption(flags, colorId, colors, darkColors, requiredChannelMinBoostLevel, requiredGroupMinBoostLevel): let isHidden = (flags & (1 << 0)) != 0 let mappedColors = colors.flatMap(EngineAvailableColorOptions.ColorOption.init(apiColors:)) let mappedDarkColors = darkColors.flatMap(EngineAvailableColorOptions.ColorOption.init(apiColors:)) if let mappedColors = mappedColors { - mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: mappedColors, dark: mappedDarkColors, isHidden: isHidden, requiredChannelMinBoostLevel: requiredChannelMinBoostLevel))) + mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: mappedColors, dark: mappedDarkColors, isHidden: isHidden, requiredChannelMinBoostLevel: requiredChannelMinBoostLevel, requiredGroupMinBoostLevel: requiredGroupMinBoostLevel))) } else if colorId >= 0 && colorId <= 6 { let staticMap: [UInt32] = [ 0xcc5049, @@ -290,7 +298,7 @@ private extension EngineAvailableColorOptions { ] let colorPack = MultiColorPack(colors: [staticMap[Int(colorId)]]) let defaultColors = EngineAvailableColorOptions.ColorOption(palette: colorPack, background: colorPack, stories: nil) - mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: defaultColors, dark: nil, isHidden: isHidden, requiredChannelMinBoostLevel: requiredChannelMinBoostLevel))) + mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: defaultColors, dark: nil, isHidden: isHidden, requiredChannelMinBoostLevel: requiredChannelMinBoostLevel, requiredGroupMinBoostLevel: requiredGroupMinBoostLevel))) } } } diff --git a/submodules/TelegramCore/Sources/State/SavedMessageTags.swift b/submodules/TelegramCore/Sources/State/SavedMessageTags.swift index 9387979e63..3d0cb4e8f4 100644 --- a/submodules/TelegramCore/Sources/State/SavedMessageTags.swift +++ b/submodules/TelegramCore/Sources/State/SavedMessageTags.swift @@ -155,7 +155,7 @@ func managedSynchronizeSavedMessageTags(postbox: Postbox, network: Network, acco let signal: Signal = _internal_savedMessageTags(postbox: postbox) |> mapToSignal { current in - return (network.request(Api.functions.messages.getSavedReactionTags(hash: current?.hash ?? 0)) + return (network.request(Api.functions.messages.getSavedReactionTags(flags: 0, peer: nil, hash: current?.hash ?? 0)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index b54867d1ff..ebac41c4cf 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 172 + return 174 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift index 6d559c42e3..d3091dad12 100644 --- a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift @@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.putNext(groups) } case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod): - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: .peerUser(userId: fromId), peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod) + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { @@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService { let generatedPeerId = Api.Peer.peerUser(userId: userId) - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod) + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index b1eedff4e5..87f132c4ca 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -104,7 +104,7 @@ extension Api.MessageMedia { extension Api.Message { var rawId: Int32 { switch self { - case let .message(_, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return id case let .messageEmpty(_, id, _): return id @@ -115,7 +115,7 @@ extension Api.Message { func id(namespace: MessageId.Namespace = Namespaces.Message.Cloud) -> MessageId? { switch self { - case let .message(_, id, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = messagePeerId.peerId return MessageId(peerId: peerId, namespace: namespace, id: id) case let .messageEmpty(_, id, peerId): @@ -132,7 +132,7 @@ extension Api.Message { var peerId: PeerId? { switch self { - case let .message(_, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = messagePeerId.peerId return peerId case let .messageEmpty(_, _, peerId): @@ -145,7 +145,7 @@ extension Api.Message { var timestamp: Int32? { switch self { - case let .message(_, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _): return date case let .messageService(_, _, _, _, _, date, _, _): return date @@ -156,7 +156,7 @@ extension Api.Message { var preCachedResources: [(MediaResource, Data)]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedResources default: return nil @@ -165,7 +165,7 @@ extension Api.Message { var preCachedStories: [StoryId: Api.StoryItem]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedStories default: return nil diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_BoostCountMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_BoostCountMessageAttribute.swift new file mode 100644 index 0000000000..4305845d64 --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_BoostCountMessageAttribute.swift @@ -0,0 +1,20 @@ +import Foundation +import Postbox + +public class BoostCountMessageAttribute: MessageAttribute { + public let count: Int + + public var associatedMessageIds: [MessageId] = [] + + public init(count: Int) { + self.count = count + } + + required public init(decoder: PostboxDecoder) { + self.count = Int(decoder.decodeInt32ForKey("c", orElse: 0)) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(Int32(self.count), forKey: "c") + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift index c4d1462963..3f5daf9186 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift @@ -260,6 +260,8 @@ public final class CachedChannelData: CachedPeerData { public let membersHidden: EnginePeerCachedInfoItem public let viewForumAsMessages: EnginePeerCachedInfoItem public let wallpaper: TelegramWallpaper? + public let boostsUnrestrict: Int32? + public let emojiPack: StickerPackCollectionInfo? public let peerIds: Set public let messageIds: Set @@ -301,6 +303,8 @@ public final class CachedChannelData: CachedPeerData { self.membersHidden = .unknown self.viewForumAsMessages = .unknown self.wallpaper = nil + self.boostsUnrestrict = nil + self.emojiPack = nil } public init( @@ -334,7 +338,9 @@ public final class CachedChannelData: CachedPeerData { allowedReactions: EnginePeerCachedInfoItem, membersHidden: EnginePeerCachedInfoItem, viewForumAsMessages: EnginePeerCachedInfoItem, - wallpaper: TelegramWallpaper? + wallpaper: TelegramWallpaper?, + boostsUnrestrict: Int32?, + emojiPack: StickerPackCollectionInfo? ) { self.isNotAccessible = isNotAccessible self.flags = flags @@ -367,6 +373,8 @@ public final class CachedChannelData: CachedPeerData { self.membersHidden = membersHidden self.viewForumAsMessages = viewForumAsMessages self.wallpaper = wallpaper + self.boostsUnrestrict = boostsUnrestrict + self.emojiPack = emojiPack var peerIds = Set() for botInfo in botInfos { @@ -394,127 +402,135 @@ public final class CachedChannelData: CachedPeerData { } public func withUpdatedIsNotAccessible(_ isNotAccessible: Bool) -> CachedChannelData { - return CachedChannelData(isNotAccessible: isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedFlags(_ flags: CachedChannelFlags) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedAbout(_ about: String?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedParticipantsSummary(_ participantsSummary: CachedChannelParticipantsSummary) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedExportedInvitation(_ exportedInvitation: ExportedInvitation?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedBotInfos(_ botInfos: [CachedPeerBotInfo]) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedStickerPack(_ stickerPack: StickerPackCollectionInfo?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedMinAvailableMessageId(_ minAvailableMessageId: MessageId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedMigrationReference(_ migrationReference: ChannelMigrationReference?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedLinkedDiscussionPeerId(_ linkedDiscussionPeerId: LinkedDiscussionPeerId) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedPeerGeoLocation(_ peerGeoLocation: PeerGeoLocation?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedSlowModeTimeout(_ slowModeTimeout: Int32?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedSlowModeValidUntilTimestamp(_ slowModeValidUntilTimestamp: Int32?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedStatsDatacenterId(_ statsDatacenterId: Int32) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedInvitedBy(_ invitedBy: PeerId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedInvitedOn(_ invitedOn: Int32?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedPhoto(_ photo: TelegramMediaImage?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedActiveCall(_ activeCall: ActiveCall?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedCallJoinPeerId(_ callJoinPeerId: PeerId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedPendingSuggestions(_ pendingSuggestions: [String]) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedThemeEmoticon(_ themeEmoticon: String?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedInviteRequestsPending(_ inviteRequestsPending: Int32?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedSendAsPeerId(_ sendAsPeerId: PeerId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedAllowedReactions(_ allowedReactions: EnginePeerCachedInfoItem) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedMembersHidden(_ membersHidden: EnginePeerCachedInfoItem) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedViewForumAsMessages(_ viewForumAsMessages: EnginePeerCachedInfoItem) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: viewForumAsMessages, wallpaper: self.wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) } public func withUpdatedWallpaper(_ wallpaper: TelegramWallpaper?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: wallpaper) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: self.emojiPack) + } + + public func withUpdatedBoostsUnrestrict(_ boostsUnrestrict: Int32?) -> CachedChannelData { + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: boostsUnrestrict, emojiPack: self.emojiPack) + } + + public func withUpdatedEmojiPack(_ emojiPack: StickerPackCollectionInfo?) -> CachedChannelData { + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsUnrestrict: self.boostsUnrestrict, emojiPack: emojiPack) } public init(decoder: PostboxDecoder) { @@ -632,6 +648,14 @@ public final class CachedChannelData: CachedPeerData { } } + self.boostsUnrestrict = decoder.decodeOptionalInt32ForKey("bu") + + if let emojiPack = decoder.decodeObjectForKey("ep", decoder: { StickerPackCollectionInfo(decoder: $0) }) as? StickerPackCollectionInfo { + self.emojiPack = emojiPack + } else { + self.emojiPack = nil + } + self.peerIds = peerIds var messageIds = Set() @@ -796,6 +820,18 @@ public final class CachedChannelData: CachedPeerData { } else { encoder.encodeNil(forKey: "wp") } + + if let boostsUnrestrict = self.boostsUnrestrict { + encoder.encodeInt32(boostsUnrestrict, forKey: "bu") + } else { + encoder.encodeNil(forKey: "bu") + } + + if let emojiPack = self.emojiPack { + encoder.encodeObject(emojiPack, forKey: "ep") + } else { + encoder.encodeNil(forKey: "ep") + } } public func isEqual(to: CachedPeerData) -> Bool { @@ -925,6 +961,12 @@ public final class CachedChannelData: CachedPeerData { if other.wallpaper != self.wallpaper { return false } + if other.boostsUnrestrict != self.boostsUnrestrict { + return false + } + if other.emojiPack != self.emojiPack { + return false + } return true } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index d24b4d1840..778709bdc9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -539,6 +539,33 @@ public extension TelegramEngine.EngineData.Item { } } + public struct EmojiPack: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { + public typealias Result = StickerPackCollectionInfo? + + fileprivate var id: EnginePeer.Id + public var mapKey: EnginePeer.Id { + return self.id + } + + public init(id: EnginePeer.Id) { + self.id = id + } + + var key: PostboxViewKey { + return .cachedPeerData(peerId: self.id) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? CachedPeerDataView else { + preconditionFailure() + } + guard let cachedData = view.cachedPeerData as? CachedChannelData else { + return nil + } + return cachedData.emojiPack + } + } + public struct AllowedReactions: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { public typealias Result = EnginePeerCachedInfoItem diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift index 718d9720e4..3ece75f732 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift @@ -91,6 +91,7 @@ public enum AdminLogEventAction { case changeProfileColor(prevColor: PeerNameColor?, prevIcon: Int64?, newColor: PeerNameColor?, newIcon: Int64?) case changeWallpaper(prev: TelegramWallpaper?, new: TelegramWallpaper?) case changeStatus(prev: PeerEmojiStatus?, new: PeerEmojiStatus?) + case changeEmojiPack(prev: StickerPackReference?, new: StickerPackReference?) } public enum ChannelAdminLogEventError { @@ -414,6 +415,8 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net action = .changeWallpaper(prev: prev, new: new) case let .channelAdminLogEventActionChangeEmojiStatus(prevValue, newValue): action = .changeStatus(prev: PeerEmojiStatus(apiStatus: prevValue), new: PeerEmojiStatus(apiStatus: newValue)) + case let .channelAdminLogEventActionChangeEmojiStickerSet(prevStickerset, newStickerset): + action = .changeEmojiPack(prev: StickerPackReference(apiInputSet: prevStickerset), new: StickerPackReference(apiInputSet: newStickerset)) } let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) if let action = action { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerSpecificStickerPack.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerSpecificStickerPack.swift index 3110230154..082d0ca9d7 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerSpecificStickerPack.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerSpecificStickerPack.swift @@ -46,3 +46,35 @@ func _internal_peerSpecificStickerPack(postbox: Postbox, network: Network, peerI return .single(PeerSpecificStickerPackData(packInfo: nil, canSetup: false)) } } + + +func _internal_peerSpecificEmojiPack(postbox: Postbox, network: Network, peerId: PeerId) -> Signal { + if peerId.namespace == Namespaces.Peer.CloudChannel { + let signal: Signal<(WrappedStickerPackCollectionInfo, Bool), NoError> = postbox.combinedView(keys: [.cachedPeerData(peerId: peerId)]) + |> map { view -> (WrappedStickerPackCollectionInfo, Bool) in + let dataView = view.views[.cachedPeerData(peerId: peerId)] as? CachedPeerDataView + return (WrappedStickerPackCollectionInfo(info: (dataView?.cachedPeerData as? CachedChannelData)?.emojiPack), (dataView?.cachedPeerData as? CachedChannelData)?.flags.contains(.canSetStickerSet) ?? false) + } + |> distinctUntilChanged(isEqual: { lhs, rhs -> Bool in + return lhs.0 == rhs.0 && lhs.1 == rhs.1 + }) + + return signal + |> mapToSignal { info, canInstall -> Signal in + if let info = info.info { + return _internal_cachedStickerPack(postbox: postbox, network: network, reference: .id(id: info.id.id, accessHash: info.accessHash), forceRemote: false) + |> map { result -> PeerSpecificStickerPackData in + if case let .result(info, items, _) = result { + return PeerSpecificStickerPackData(packInfo: (info, items), canSetup: canInstall) + } else { + return PeerSpecificStickerPackData(packInfo: nil, canSetup: canInstall) + } + } + } else { + return .single(PeerSpecificStickerPackData(packInfo: nil, canSetup: canInstall)) + } + } + } else { + return .single(PeerSpecificStickerPackData(packInfo: nil, canSetup: false)) + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index b5df4c4643..69e52ed13d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -419,6 +419,10 @@ public extension TelegramEngine { public func updateGroupSpecificStickerset(peerId: PeerId, info: StickerPackCollectionInfo?) -> Signal { return _internal_updateGroupSpecificStickerset(postbox: self.account.postbox, network: self.account.network, peerId: peerId, info: info) } + + public func updateGroupSpecificEmojiset(peerId: PeerId, info: StickerPackCollectionInfo?) -> Signal { + return _internal_updateGroupSpecificEmojiset(postbox: self.account.postbox, network: self.account.network, peerId: peerId, info: info) + } public func joinChannel(peerId: PeerId, hash: String?) -> Signal { return _internal_joinChannel(account: self.account, peerId: peerId, hash: hash) @@ -462,6 +466,10 @@ public extension TelegramEngine { public func peerSpecificStickerPack(peerId: PeerId) -> Signal { return _internal_peerSpecificStickerPack(postbox: self.account.postbox, network: self.account.network, peerId: peerId) } + + public func peerSpecificEmojiPack(peerId: PeerId) -> Signal { + return _internal_peerSpecificEmojiPack(postbox: self.account.postbox, network: self.account.network, peerId: peerId) + } public func addRecentlySearchedPeer(peerId: PeerId) -> Signal { return _internal_addRecentlySearchedPeer(postbox: self.account.postbox, peerId: peerId) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 35d7cb1735..cd392adb0a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -452,14 +452,14 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee switch result { case let .chatFull(fullChat, chats, users): switch fullChat { - case let .channelFull(_, _, _, _, _, _, _, _, _, _, _, _, _, notifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .channelFull(_, _, _, _, _, _, _, _, _, _, _, _, _, notifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: notifySettings)]) case .chatFull: break } switch fullChat { - case let .channelFull(flags, flags2, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, _, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, _, inputCall, ttl, pendingSuggestions, groupcallDefaultJoinAs, themeEmoticon, requestsPending, _, defaultSendAs, allowedReactions, _, wallpaper): + case let .channelFull(flags, flags2, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, _, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, _, inputCall, ttl, pendingSuggestions, groupcallDefaultJoinAs, themeEmoticon, requestsPending, _, defaultSendAs, allowedReactions, _, wallpaper, _, boostsUnrestrict, emojiSet): var channelFlags = CachedChannelFlags() if (flags & (1 << 3)) != 0 { channelFlags.insert(.canDisplayParticipants) @@ -588,6 +588,22 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee } let photo = telegramMediaImageFromApiPhoto(chatPhoto) + + let emojiPack: StickerPackCollectionInfo? = emojiSet.flatMap { apiSet -> StickerPackCollectionInfo in + let namespace: ItemCollectionId.Namespace + switch apiSet { + case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _): + if (flags & (1 << 3)) != 0 { + namespace = Namespaces.ItemCollection.CloudMaskPacks + } else if (flags & (1 << 7)) != 0 { + namespace = Namespaces.ItemCollection.CloudEmojiPacks + } else { + namespace = Namespaces.ItemCollection.CloudStickerPacks + } + } + + return StickerPackCollectionInfo(apiSet: apiSet, namespace: namespace) + } var minAvailableMessageIdUpdated = false transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current in @@ -658,6 +674,8 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee .withUpdatedMembersHidden(.known(PeerMembersHidden(value: membersHidden))) .withUpdatedViewForumAsMessages(.known(forumViewAsMessages)) .withUpdatedWallpaper(wallpaper) + .withUpdatedBoostsUnrestrict(boostsUnrestrict) + .withUpdatedEmojiPack(emojiPack) }) if let minAvailableMessageId = minAvailableMessageId, minAvailableMessageIdUpdated { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateGroupSpecificStickerset.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateGroupSpecificStickerset.swift index b2cb591709..047910c571 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateGroupSpecificStickerset.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateGroupSpecificStickerset.swift @@ -40,3 +40,41 @@ func _internal_updateGroupSpecificStickerset(postbox: Postbox, network: Network, return .complete() } } + + +public enum UpdateGroupSpecificEmojisetError { + case generic +} + +func _internal_updateGroupSpecificEmojiset(postbox: Postbox, network: Network, peerId: PeerId, info: StickerPackCollectionInfo?) -> Signal { + return postbox.loadedPeerWithId(peerId) + |> castError(UpdateGroupSpecificEmojisetError.self) + |> mapToSignal { peer -> Signal in + let inputStickerset: Api.InputStickerSet + if let info = info { + inputStickerset = Api.InputStickerSet.inputStickerSetShortName(shortName: info.shortName) + } else { + inputStickerset = Api.InputStickerSet.inputStickerSetEmpty + } + if let inputChannel = apiInputChannel(peer) { + return network.request(Api.functions.channels.setEmojiStickers(channel: inputChannel, stickerset: inputStickerset)) + |> mapError { _ -> UpdateGroupSpecificEmojisetError in + return .generic + } + |> mapToSignal { value -> Signal in + switch value { + case .boolTrue: + return postbox.transaction { transaction -> Void in + return transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current -> CachedPeerData? in + return (current as? CachedChannelData)?.withUpdatedEmojiPack(info) + }) + } + |> castError(UpdateGroupSpecificEmojisetError.self) + default: + return .complete() + } + } + } + return .complete() + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift index 9470ac2a76..70159a16c5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift @@ -639,6 +639,39 @@ func _internal_searchStickerSets(postbox: Postbox, query: String) -> Signal switchToLatest } +func _internal_searchEmojiSets(postbox: Postbox, query: String) -> Signal { + return postbox.transaction { transaction -> Signal in + let infos = transaction.getItemCollectionsInfos(namespace: Namespaces.ItemCollection.CloudEmojiPacks) + + var collections: [(ItemCollectionId, ItemCollectionInfo)] = [] + var topItems: [ItemCollectionId: ItemCollectionItem] = [:] + var entries: [ItemCollectionViewEntry] = [] + for info in infos { + if let info = info.1 as? StickerPackCollectionInfo { + let split = info.title.split(separator: " ") + if !split.filter({$0.lowercased().hasPrefix(query.lowercased())}).isEmpty || info.shortName.lowercased().hasPrefix(query.lowercased()) { + collections.append((info.id, info)) + } + } + } + var index: Int32 = 0 + + for info in collections { + let items = transaction.getItemCollectionItems(collectionId: info.0) + let values = items.map({ ItemCollectionViewEntry(index: ItemCollectionViewEntryIndex(collectionIndex: index, collectionId: info.0, itemIndex: $0.index), item: $0) }) + entries.append(contentsOf: values) + if let first = items.first { + topItems[info.0] = first + } + index += 1 + } + + let result = FoundStickerSets(infos: collections.map { ($0.0, $0.1, topItems[$0.0], true) }, entries: entries) + + return .single(result) + } |> switchToLatest +} + func _internal_searchGifs(account: Account, query: String, nextOffset: String = "") -> Signal { return account.postbox.transaction { transaction -> String in let configuration = currentSearchBotsConfiguration(transaction: transaction) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift index 7fabb0b1b4..d4903c457c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift @@ -45,6 +45,10 @@ public extension TelegramEngine { public func searchStickerSets(query: String) -> Signal { return _internal_searchStickerSets(postbox: self.account.postbox, query: query) } + + public func searchEmojiSets(query: String) -> Signal { + return _internal_searchEmojiSets(postbox: self.account.postbox, query: query) + } public func searchGifs(query: String, nextOffset: String = "") -> Signal { return _internal_searchGifs(account: self.account, query: query, nextOffset: nextOffset) diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index 95dfbdca6b..eff12498c8 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -195,6 +195,9 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case videoMessagesPlayOnceSuggestion = 61 case incomingVideoMessagePlayOnceTip = 62 case outgoingVideoMessagePlayOnceTip = 63 + case dismissedMessageTagsBadge = 64 + case dismissedLastSeenBadge = 65 + case dismissedMessagePrivacyBadge = 66 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -504,6 +507,18 @@ private struct ApplicationSpecificNoticeKeys { static func outgoingVideoMessagePlayOnceTip() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.outgoingVideoMessagePlayOnceTip.key) } + + static func dismissedMessageTagsBadge() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedMessageTagsBadge.key) + } + + static func dismissedLastSeenBadge() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedLastSeenBadge.key) + } + + static func dismissedMessagePrivacyBadge() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedMessagePrivacyBadge.key) + } } public struct ApplicationSpecificNotice { @@ -2080,4 +2095,67 @@ public struct ApplicationSpecificNotice { return Int(previousValue) } } + + public static func setDismissedMessageTagsBadge(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Void in + if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedMessageTagsBadge(), entry) + } + } + |> ignoreValues + } + + public static func dismissedMessageTagsBadge(accountManager: AccountManager) -> Signal { + return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedMessageTagsBadge()) + |> map { view -> Bool in + if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) { + return true + } else { + return false + } + } + |> take(1) + } + + public static func setDismissedLastSeenBadge(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Void in + if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedLastSeenBadge(), entry) + } + } + |> ignoreValues + } + + public static func dismissedLastSeenBadge(accountManager: AccountManager) -> Signal { + return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedLastSeenBadge()) + |> map { view -> Bool in + if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) { + return true + } else { + return false + } + } + |> take(1) + } + + public static func setDismissedMessagePrivacyBadge(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Void in + if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedMessagePrivacyBadge(), entry) + } + } + |> ignoreValues + } + + public static func dismissedMessagePrivacyBadge(accountManager: AccountManager) -> Signal { + return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedMessagePrivacyBadge()) + |> map { view -> Bool in + if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) { + return true + } else { + return false + } + } + |> take(1) + } } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index ed1e6f4ede..c32d06c154 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -937,7 +937,12 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = NSAttributedString(string: strings.Notification_GiftLink, font: titleFont, textColor: primaryTextColor) } case .giveawayLaunched: - let resultTitleString = strings.Notification_GiveawayStarted(compactAuthorName) + var isGroup = false + let messagePeer = message.peers[message.id.peerId] + if let channel = messagePeer as? TelegramChannel, case .group = channel.info { + isGroup = true + } + let resultTitleString = isGroup ? strings.Notification_GiveawayStartedGroup(compactAuthorName) : strings.Notification_GiveawayStarted(compactAuthorName) attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) case .joinedChannel: attributedString = NSAttributedString(string: strings.Notification_ChannelJoinedByYou, font: titleBoldFont, textColor: primaryTextColor) diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index 59fb84f8a1..881fdd33fe 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -2766,7 +2766,7 @@ public class CameraScreen: ViewController { } private func requestAudioSession() { - self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .recordWithOthers, activate: { _ in + self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .record(speaker: false, video: true, withOthers: true), activate: { _ in if #available(iOS 13.0, *) { try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(true) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift index ae5c65eeda..4298a2546d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift @@ -83,13 +83,13 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton }) } - public typealias AsyncLayout = (_ width: CGFloat, _ iconImage: UIImage?, _ cornerIcon: Bool, _ title: String, _ titleColor: UIColor, _ inProgress: Bool, _ drawBackground: Bool) -> (CGFloat, (CGFloat, CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageAttachedContentButtonNode)) + public typealias AsyncLayout = (_ width: CGFloat, _ sideInset: CGFloat?, _ iconImage: UIImage?, _ cornerIcon: Bool, _ title: String, _ titleColor: UIColor, _ inProgress: Bool, _ drawBackground: Bool) -> (CGFloat, (CGFloat, CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageAttachedContentButtonNode)) public static func asyncLayout(_ current: ChatMessageAttachedContentButtonNode?) -> AsyncLayout { let previousRegularIconImage = current?.regularIconImage let maybeMakeTextLayout = (current?.textNode).flatMap(TextNode.asyncLayout) - return { width, iconImage, cornerIcon, title, titleColor, inProgress, drawBackground in + return { width, sideInset, iconImage, cornerIcon, title, titleColor, inProgress, drawBackground in let targetNode: ChatMessageAttachedContentButtonNode if let current = current { targetNode = current @@ -114,7 +114,7 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton iconWidth = iconImage.size.width + 5.0 } - let labelInset: CGFloat = 8.0 + let labelInset: CGFloat = sideInset ?? 8.0 let (textSize, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: buttonFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(1.0, width - labelInset * 2.0 - iconWidth), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets())) @@ -146,7 +146,7 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: refinedWidth, height: size.height)) - var textFrame = CGRect(origin: CGPoint(x: floor((refinedWidth - textSize.size.width) / 2.0), y: floor((backgroundFrame.height - textSize.size.height) / 2.0)), size: textSize.size) + var textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((refinedWidth - textSize.size.width) / 2.0), y: floorToScreenPixels((backgroundFrame.height - textSize.size.height) / 2.0)), size: textSize.size) if drawBackground { textFrame.origin.y += 1.0 } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index 21e7c4e1e8..7a9723668c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -555,6 +555,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { let (buttonWidth, continueLayout) = makeActionButtonLayout( maxContentsWidth, + nil, buttonIconImage, cornerIcon, actionTitle, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index d8af7c2e1e..b2a87e43ad 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -557,6 +557,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI private var boostBadgeNode: TextNode? private var boostIconNode: UIImageView? + private var boostCount: Int = 0 + private var boostButtonNode: HighlightTrackingButtonNode? private var boostHighlightNode: ASImageNode? @@ -1515,7 +1517,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if !needsShareButton, let author = item.message.author as? TelegramUser, let _ = author.botInfo { if !item.message.media.isEmpty && !(item.message.media.first is TelegramMediaAction) { needsShareButton = true - } else if author.id == PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(92386307)) { + } else if author.id == PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(92386307)) || author.id == PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(6435149744)) { needsShareButton = true } } @@ -2197,12 +2199,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI adminBadgeString = NSAttributedString(string: " \(item.presentationData.strings.Channel_Status)", font: inlineBotPrefixFont, textColor: messageTheme.secondaryTextColor) } - let boostCount: Int32 = 2 - - if boostCount > 1, let authorNameColor = authorNameColor { - boostBadgeString = NSAttributedString(string: "\(boostCount)", font: boostBadgeFont, textColor: authorNameColor) - } - var viaSuffix: NSAttributedString? if let authorNameString = authorNameString, let authorNameColor = authorNameColor, let inlineBotNameString = inlineBotNameString { let mutableString = NSMutableAttributedString(string: "\(authorNameString) ", attributes: [NSAttributedString.Key.font: nameFont, NSAttributedString.Key.foregroundColor: authorNameColor]) @@ -2235,18 +2231,34 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI credibilityIconWidth += 20.0 } } + let adminBadgeSizeAndApply = adminBadgeLayout(TextNodeLayoutArguments(attributedString: adminBadgeString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0, maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right), height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - adminNodeSizeApply = (adminBadgeSizeAndApply.0.size, { - return adminBadgeSizeAndApply.1() - }) + if adminBadgeSizeAndApply.0.size.width > 0.0 { + adminNodeSizeApply = (adminBadgeSizeAndApply.0.size, { + return adminBadgeSizeAndApply.1() + }) + } + + var boostCount: Int = 0 + for attribute in item.message.attributes { + if let attribute = attribute as? BoostCountMessageAttribute { + boostCount = attribute.count + } + } + + if boostCount > 1, let authorNameColor = authorNameColor { + boostBadgeString = NSAttributedString(string: "\(boostCount)", font: boostBadgeFont, textColor: authorNameColor) + } var boostBadgeWidth: CGFloat = 0.0 let boostBadgeSizeAndApply = boostBadgeLayout(TextNodeLayoutArguments(attributedString: boostBadgeString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0, maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right), height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - boostNodeSizeApply = (boostBadgeSizeAndApply.0.size, { - return boostBadgeSizeAndApply.1() - }) if boostBadgeSizeAndApply.0.size.width > 0.0 { + boostNodeSizeApply = (boostBadgeSizeAndApply.0.size, { + return boostBadgeSizeAndApply.1() + }) boostBadgeWidth += boostBadgeSizeAndApply.0.size.width + 19.0 + } else if boostCount == 1 { + boostBadgeWidth = 14.0 } let closeButtonWidth: CGFloat = item.message.adAttribute != nil ? 18.0 : 0.0 @@ -3166,6 +3178,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.credibilityHighlightNode = nil } + var boostCount: Int = 0 + for attribute in item.message.attributes { + if let attribute = attribute as? BoostCountMessageAttribute { + boostCount = attribute.count + } + } + var rightContentOffset: CGFloat = 0.0 if let boostBadgeNode = boostNodeSizeApply.1() { boostBadgeNode.alpha = 0.75 @@ -3184,13 +3203,25 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } else { animation.animator.updateFrame(layer: boostBadgeNode.layer, frame: boostBadgeFrame, completion: nil) } - rightContentOffset += boostNodeSizeApply.0.width + 19.0 - - var boostIconFrame = boostBadgeFrame - boostIconFrame.origin.x -= 16.0 - boostIconFrame.origin.y -= 3.0 - boostIconFrame.size.width += 19.0 - boostIconFrame.size.height = 22.0 + } else { + strongSelf.boostBadgeNode?.removeFromSupernode() + strongSelf.boostBadgeNode = nil + } + + if boostCount > 0 { + var boostTotalWidth: CGFloat = 22.0 + if boostNodeSizeApply.0.width > 0.0 { + boostTotalWidth += boostNodeSizeApply.0.width + rightContentOffset += boostTotalWidth + } else { + boostTotalWidth -= 6.0 + rightContentOffset += boostTotalWidth - 2.0 + } + + + let boostIconFrame = CGRect(origin: CGPoint(x: contentUpperRightCorner.x - layoutConstants.text.bubbleInsets.left - boostTotalWidth + 4.0, y: layoutConstants.bubble.contentInsets.top + nameNodeOriginY + 1.0 - UIScreenPixel - 3.0), size: CGSize(width: boostTotalWidth, height: 22.0)) + + let previousBoostCount = strongSelf.boostCount let boostIconNode: UIImageView let boostButtonNode: HighlightTrackingButtonNode @@ -3202,7 +3233,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } else { boostIconNode = UIImageView() boostIconNode.alpha = 0.75 - boostIconNode.image = UIImage(bundleImageName: "Chat/Message/Boosts")?.withRenderingMode(.alwaysTemplate) strongSelf.clippingNode.view.addSubview(boostIconNode) strongSelf.boostIconNode = boostIconNode @@ -3232,10 +3262,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.boostButtonNode = boostButtonNode } + if boostCount != previousBoostCount { + boostIconNode.image = UIImage(bundleImageName: boostCount == 1 ? "Chat/Message/Boost" : "Chat/Message/Boosts")?.withRenderingMode(.alwaysTemplate) + } + boostIconNode.tintColor = nameColor - let iconSize = CGSize(width: 14.0, height: 14.0) - boostIconNode.frame = CGRect(origin: CGPoint(x: boostIconFrame.minX + 1.0, y: boostIconFrame.midY - iconSize.height / 2.0), size: iconSize) + if let iconSize = boostIconNode.image?.size { + boostIconNode.frame = CGRect(origin: CGPoint(x: boostTotalWidth > 22.0 ? boostIconFrame.minX + 3.0 : boostIconFrame.midX - iconSize.width / 2.0, y: boostIconFrame.midY - iconSize.height / 2.0), size: iconSize) + } boostHighlightNode.frame = boostIconFrame boostButtonNode.frame = boostIconFrame.insetBy(dx: -2.0, dy: -3.0) @@ -3244,13 +3279,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI boostHighlightNode.image = generateFilledRoundedRectImage(size: CGSize(width: 8.0, height: 8.0), cornerRadius: 4.0, color: nameColor.withAlphaComponent(0.1))?.stretchableImage(withLeftCapWidth: 4, topCapHeight: 4) } } else { - strongSelf.boostBadgeNode?.removeFromSupernode() - strongSelf.boostBadgeNode = nil strongSelf.boostButtonNode?.removeFromSupernode() strongSelf.boostButtonNode = nil strongSelf.boostHighlightNode?.removeFromSupernode() strongSelf.boostHighlightNode = nil + strongSelf.boostIconNode?.removeFromSuperview() + strongSelf.boostIconNode = nil } + strongSelf.boostCount = boostCount if let adminBadgeNode = adminNodeSizeApply.1() { strongSelf.adminBadgeNode = adminBadgeNode @@ -5129,7 +5165,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI guard let item = self.item, let peer = item.message.author else { return } - item.controllerInteraction.openGroupBoostInfo(peer.id) + + var boostCount: Int = 0 + for attribute in item.message.attributes { + if let attribute = attribute as? BoostCountMessageAttribute { + boostCount = attribute.count + } + } + + item.controllerInteraction.openGroupBoostInfo(peer.id, boostCount) } private var playedSwipeToReplyHaptic = false diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift index 9158abb2c9..e61efc2c4d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift @@ -307,7 +307,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { avatarPlaceholderColor = item.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor } - let (messageButtonWidth, messageContinueLayout) = makeMessageButtonLayout(constrainedSize.width, nil, false, item.presentationData.strings.Conversation_ContactMessage.uppercased(), mainColor, false, false) + let (messageButtonWidth, messageContinueLayout) = makeMessageButtonLayout(constrainedSize.width, 10.0, nil, false, item.presentationData.strings.Conversation_ContactMessage.uppercased(), mainColor, false, false) let addTitle: String if !canMessage && !canAdd { @@ -319,7 +319,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { addTitle = item.presentationData.strings.Conversation_ContactAddContactLong } } - let (addButtonWidth, addContinueLayout) = makeAddButtonLayout(constrainedSize.width, nil, false, addTitle.uppercased(), mainColor, false, false) + let (addButtonWidth, addContinueLayout) = makeAddButtonLayout(constrainedSize.width, 10.0, nil, false, addTitle.uppercased(), mainColor, false, false) let maxButtonWidth = max(messageButtonWidth, addButtonWidth) var maxContentWidth: CGFloat = avatarSize.width + 7.0 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift index 53f4f6b2a0..3e79ec1270 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift @@ -362,6 +362,8 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, let participantsText: String let countriesText: String + let isGroup = "".isEmpty + if let giveaway { if giveaway.flags.contains(.onlyNewSubscribers) { if giveaway.channelPeerIds.count > 1 { @@ -373,7 +375,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, if giveaway.channelPeerIds.count > 1 { participantsText = item.presentationData.strings.Chat_Giveaway_Message_ParticipantsMany } else { - participantsText = item.presentationData.strings.Chat_Giveaway_Message_Participants + participantsText = isGroup ? item.presentationData.strings.Chat_Giveaway_Message_Group_Participants : item.presentationData.strings.Chat_Giveaway_Message_Participants } } if !giveaway.countries.isEmpty { @@ -537,7 +539,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, titleColor = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor } - let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, nil, false, item.presentationData.strings.Chat_Giveaway_Message_LearnMore.uppercased(), titleColor, false, true) + let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, nil, nil, false, item.presentationData.strings.Chat_Giveaway_Message_LearnMore.uppercased(), titleColor, false, true) let animationName: String let months = giveaway?.months ?? 0 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift index b154236175..eb8e110712 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift @@ -48,7 +48,7 @@ public final class ChatMessageUnsupportedBubbleContentNode: ChatMessageBubbleCon } else { titleColor = presentationData.theme.theme.chat.message.outgoing.accentTextColor } - let (buttonWidth, continueActionButtonLayout) = makeButtonLayout(constrainedSize.width, nil, false, presentationData.strings.Conversation_UpdateTelegram, titleColor, false, true) + let (buttonWidth, continueActionButtonLayout) = makeButtonLayout(constrainedSize.width, nil, nil, false, presentationData.strings.Conversation_UpdateTelegram, titleColor, false, true) let initialWidth = buttonWidth + insets.left + insets.right diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index 0ef76df20d..1d675f758c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -458,6 +458,8 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent actionTitle = item.presentationData.strings.Chat_OpenStory case "telegram_channel_boost": actionTitle = item.presentationData.strings.Conversation_BoostChannel + case "telegram_group_boost": + actionTitle = item.presentationData.strings.Conversation_BoostChannel default: break } diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index 695c6112bc..2235adcd3e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -571,7 +571,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, displayGiveawayParticipationStatus: { _ in }, openPremiumStatusInfo: { _, _, _, _ in }, openRecommendedChannelContextMenu: { _, _, _ in - }, openGroupBoostInfo: { _ in + }, openGroupBoostInfo: { _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift index d37a02c53d..300c165ebc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift @@ -2116,6 +2116,35 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) } + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + case let .changeEmojiPack(_, new): + var peers = SimpleDictionary() + var author: Peer? + if let peer = self.entry.peers[self.entry.event.peerId] { + author = peer + peers[peer.id] = peer + } + var text: String = "" + var entities: [MessageTextEntity] = [] + + if new != nil { + appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageChangedGroupStickerPack(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""), generateEntities: { index in + if index == 0, let author = author { + return [.TextMention(peerId: author.id)] + } + return [] + }, to: &text, entities: &entities) + } else { + appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageRemovedGroupStickerPack(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""), generateEntities: { index in + if index == 0, let author = author { + return [.TextMention(peerId: author.id)] + } + return [] + }, to: &text, entities: &entities) + } + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 9bbe491a8f..2687dc2e3d 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -231,7 +231,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let displayGiveawayParticipationStatus: (EngineMessage.Id) -> Void public let openPremiumStatusInfo: (EnginePeer.Id, UIView, Int64?, PeerNameColor) -> Void public let openRecommendedChannelContextMenu: (EnginePeer, UIView, ContextGesture?) -> Void - public let openGroupBoostInfo: (EnginePeer.Id) -> Void + public let openGroupBoostInfo: (EnginePeer.Id, Int) -> Void public let requestMessageUpdate: (MessageId, Bool) -> Void public let cancelInteractiveKeyboardGestures: () -> Void @@ -354,7 +354,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol displayGiveawayParticipationStatus: @escaping (EngineMessage.Id) -> Void, openPremiumStatusInfo: @escaping (EnginePeer.Id, UIView, Int64?, PeerNameColor) -> Void, openRecommendedChannelContextMenu: @escaping (EnginePeer, UIView, ContextGesture?) -> Void, - openGroupBoostInfo: @escaping (EnginePeer.Id) -> Void, + openGroupBoostInfo: @escaping (EnginePeer.Id, Int) -> Void, requestMessageUpdate: @escaping (MessageId, Bool) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, dismissTextInput: @escaping () -> Void, diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index b25f6f1339..8cc36dd5aa 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -885,9 +885,16 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { let remotePacksSignal: Signal<(sets: FoundStickerSets, isFinalResult: Bool), NoError> if hasPremium { remoteSignal = context.engine.stickers.searchEmoji(emojiString: Array(allEmoticons.keys)) - remotePacksSignal = .single((FoundStickerSets(), false)) |> then(context.engine.stickers.searchEmojiSetsRemotely(query: query) |> map { - ($0, true) - }) + remotePacksSignal = context.engine.stickers.searchEmojiSets(query: query) + |> mapToSignal { localResult in + return .single((localResult, false)) + |> then( + context.engine.stickers.searchEmojiSetsRemotely(query: query) + |> map { remoteResult in + return (localResult.merge(with: remoteResult), true) + } + ) + } } else { remoteSignal = .single(([], true)) remotePacksSignal = .single((FoundStickerSets(), true)) diff --git a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift index a44750a2d8..158931012b 100644 --- a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift @@ -400,6 +400,8 @@ public final class EmojiStatusComponent: Component { emojiThemeColor = themeColor emojiLoopMode = loopMode emojiSize = size + } else if case let .premium(color) = component.content { + iconTintColor = color } } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift index 5139ba06d4..cd246afb39 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift @@ -67,6 +67,48 @@ public extension EmojiPagerContentComponent { let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings + struct PeerSpecificPackData: Equatable { + var info: StickerPackCollectionInfo + var items: [StickerPackItem] + var peer: EnginePeer + + static func ==(lhs: PeerSpecificPackData, rhs: PeerSpecificPackData) -> Bool { + if lhs.info.id != rhs.info.id { + return false + } + if lhs.items != rhs.items { + return false + } + if lhs.peer != rhs.peer { + return false + } + + return true + } + } + + let peerSpecificPack: Signal + if let chatPeerId = chatPeerId { + peerSpecificPack = combineLatest( + context.engine.peers.peerSpecificEmojiPack(peerId: chatPeerId), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: chatPeerId)) + ) + |> map { packData, peer -> PeerSpecificPackData? in + guard let peer = peer else { + return nil + } + + guard let (info, items) = packData.packInfo else { + return nil + } + + return PeerSpecificPackData(info: info, items: items.compactMap { $0 as? StickerPackItem }, peer: peer) + } + |> distinctUntilChanged + } else { + peerSpecificPack = .single(nil) + } + var orderedItemListCollectionIds: [Int32] = [] switch subject { @@ -157,9 +199,10 @@ public extension EmojiPagerContentComponent { availableReactions, searchCategories, iconStatusEmoji, + peerSpecificPack, ApplicationSpecificNotice.dismissedTrendingEmojiPacks(accountManager: context.sharedContext.accountManager) ) - |> map { view, hasPremium, featuredEmojiPacks, availableReactions, searchCategories, iconStatusEmoji, dismissedTrendingEmojiPacks -> EmojiPagerContentComponent in + |> map { view, hasPremium, featuredEmojiPacks, availableReactions, searchCategories, iconStatusEmoji, peerSpecificPack, dismissedTrendingEmojiPacks -> EmojiPagerContentComponent in struct ItemGroup { var supergroupId: AnyHashable var id: AnyHashable @@ -1180,8 +1223,51 @@ public extension EmojiPagerContentComponent { itemCollectionMapping[id] = info } } - + var skippedCollectionIds = Set() + + var avatarPeer: EnginePeer? + if let peerSpecificPack = peerSpecificPack { + avatarPeer = peerSpecificPack.peer + + var processedIds = Set() + for item in peerSpecificPack.items { + if isPremiumDisabled && item.file.isPremiumSticker { + continue + } + if processedIds.contains(item.file.fileId) { + continue + } + processedIds.insert(item.file.fileId) + + var tintMode: Item.TintMode = .none + if item.file.isCustomTemplateEmoji { + tintMode = .primary + } + + let animationData = EntityKeyboardAnimationData(file: item.file) + let resultItem = EmojiPagerContentComponent.Item( + animationData: animationData, + content: .animation(animationData), + itemFile: item.file, + subgroupId: nil, + icon: .none, + tintMode: tintMode + ) + + let groupId = "peerSpecific" + if let groupIndex = itemGroupIndexById[groupId] { + itemGroups[groupIndex].items.append(resultItem) + } else { + itemGroupIndexById[groupId] = itemGroups.count + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: peerSpecificPack.peer.compactDisplayTitle, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem])) + } + } + + let supergroupId: AnyHashable = peerSpecificPack.info.id + skippedCollectionIds.insert(supergroupId) + } + if areCustomEmojiEnabled { for entry in view.entries { guard let item = entry.item as? StickerPackItem else { @@ -1442,7 +1528,7 @@ public extension EmojiPagerContentComponent { return EmojiPagerContentComponent( id: "emoji", context: context, - avatarPeer: nil, + avatarPeer: avatarPeer, animationCache: animationCache, animationRenderer: animationRenderer, inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(), diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD b/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD new file mode 100644 index 0000000000..00b92ec4b5 --- /dev/null +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD @@ -0,0 +1,27 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GroupStickerPackSetupController", + module_name = "GroupStickerPackSetupController", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/ItemListUI:ItemListUI", + "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/AccountContext:AccountContext", + "//submodules/StickerPackPreviewUI:StickerPackPreviewUI", + "//submodules/ItemListStickerPackItem:ItemListStickerPackItem", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Info.plist b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Info.plist new file mode 100644 index 0000000000..e1fe4cfb7b --- /dev/null +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/submodules/PeerInfoUI/Sources/GroupStickerPackCurrentItem.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift similarity index 91% rename from submodules/PeerInfoUI/Sources/GroupStickerPackCurrentItem.swift rename to submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift index 2f32d43117..c87e720881 100644 --- a/submodules/PeerInfoUI/Sources/GroupStickerPackCurrentItem.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift @@ -89,6 +89,7 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { private let topStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode + private let maskNode: ASImageNode fileprivate let imageNode: TransformImageNode private let notFoundNode: ASImageNode @@ -121,6 +122,9 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { self.bottomStripeNode = ASDisplayNode() self.bottomStripeNode.isLayerBacked = true + self.maskNode = ASImageNode() + self.maskNode.isUserInteractionEnabled = false + self.imageNode = TransformImageNode() self.imageNode.isLayerBacked = !smartInvertColorsEnabled() @@ -282,11 +286,35 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { if strongSelf.bottomStripeNode.supernode == nil { strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) } + if strongSelf.maskNode.supernode == nil { + strongSelf.addSubnode(strongSelf.maskNode) + } +// switch neighbors.top { +// case .sameSection(false): +// strongSelf.topStripeNode.isHidden = true +// default: +// strongSelf.topStripeNode.isHidden = false +// } +// let bottomStripeInset: CGFloat +// let bottomStripeOffset: CGFloat +// switch neighbors.bottom { +// case .sameSection(false): +// bottomStripeInset = leftInset + editingOffset +// bottomStripeOffset = -separatorHeight +// default: +// bottomStripeInset = 0.0 +// bottomStripeOffset = 0.0 +// } + + let hasCorners = itemListHasRoundedBlockLayout(params) + var hasTopCorners = false + var hasBottomCorners = false switch neighbors.top { case .sameSection(false): strongSelf.topStripeNode.isHidden = true default: - strongSelf.topStripeNode.isHidden = false + hasTopCorners = true + strongSelf.topStripeNode.isHidden = hasCorners } let bottomStripeInset: CGFloat let bottomStripeOffset: CGFloat @@ -294,11 +322,18 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { case .sameSection(false): bottomStripeInset = leftInset + editingOffset bottomStripeOffset = -separatorHeight + strongSelf.bottomStripeNode.isHidden = false default: bottomStripeInset = 0.0 bottomStripeOffset = 0.0 + hasBottomCorners = true + strongSelf.bottomStripeNode.isHidden = hasCorners } + + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) + strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) diff --git a/submodules/PeerInfoUI/Sources/GroupStickerPackSetupController.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift similarity index 84% rename from submodules/PeerInfoUI/Sources/GroupStickerPackSetupController.swift rename to submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift index 1c3e5ae2c2..0c13db0b1c 100644 --- a/submodules/PeerInfoUI/Sources/GroupStickerPackSetupController.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift @@ -202,11 +202,12 @@ private enum GroupStickerPackEntry: ItemListNodeEntry { let arguments = arguments as! GroupStickerPackSetupControllerArguments switch self { case let .search(theme, _, prefix, placeholder, value): + let isEmoji = prefix.contains("addemoji") return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: prefix, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), spacing: 0.0, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { value in arguments.updateSearchText(value) }, processPaste: { text in if let url = (URL(string: text) ?? URL(string: "http://" + text)), url.host == "t.me" || url.host == "telegram.me" { - let prefix = "/addstickers/" + let prefix = isEmoji ? "/addemoji/" : "/addstickers/" if url.path.hasPrefix(prefix) { return String(url.path[url.path.index(url.path.startIndex, offsetBy: prefix.count)...]) } @@ -260,13 +261,13 @@ private struct GroupStickerPackSetupControllerState: Equatable { var isSaving: Bool } -private func groupStickerPackSetupControllerEntries(context: AccountContext, presentationData: PresentationData, searchText: String, view: CombinedView, initialData: InitialStickerPackData?, searchState: GroupStickerPackSearchState, stickerSettings: StickerSettings) -> [GroupStickerPackEntry] { +private func groupStickerPackSetupControllerEntries(context: AccountContext, presentationData: PresentationData, searchText: String, view: CombinedView, initialData: InitialStickerPackData?, searchState: GroupStickerPackSearchState, stickerSettings: StickerSettings, emoji: Bool) -> [GroupStickerPackEntry] { if initialData == nil { return [] } var entries: [GroupStickerPackEntry] = [] - entries.append(.search(presentationData.theme, presentationData.strings, "t.me/addstickers/", presentationData.strings.Channel_Stickers_Placeholder, searchText)) + entries.append(.search(presentationData.theme, presentationData.strings, emoji ? "t.me/addemoji/" : "t.me/addstickers/", emoji ? "emojiset" : presentationData.strings.Channel_Stickers_Placeholder, searchText)) switch searchState { case .none: break @@ -277,10 +278,10 @@ private func groupStickerPackSetupControllerEntries(context: AccountContext, pre case let .found(data): entries.append(.currentPack(0, presentationData.theme, presentationData.strings, .found(packInfo: data.info, topItem: data.item, subtitle: presentationData.strings.StickerPack_StickerCount(data.info.count)))) } - entries.append(.searchInfo(presentationData.theme, presentationData.strings.Channel_Stickers_CreateYourOwn)) - entries.append(.packsTitle(presentationData.theme, presentationData.strings.Channel_Stickers_YourStickers)) + entries.append(.searchInfo(presentationData.theme, emoji ? "All members will be able to use these emoji in the group, even if they don't have Telegram Premium." : presentationData.strings.Channel_Stickers_CreateYourOwn)) + entries.append(.packsTitle(presentationData.theme, emoji ? "CHOOSE FROM YOUR EMOJI" : presentationData.strings.Channel_Stickers_YourStickers)) - let namespace = Namespaces.ItemCollection.CloudStickerPacks + let namespace = emoji ? Namespaces.ItemCollection.CloudEmojiPacks : Namespaces.ItemCollection.CloudStickerPacks if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [namespace])] as? ItemCollectionInfosView { if let packsEntries = stickerPacksView.entriesByNamespace[namespace] { var index: Int32 = 0 @@ -300,7 +301,7 @@ private func groupStickerPackSetupControllerEntries(context: AccountContext, pre return entries } -public func groupStickerPackSetupController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, currentPackInfo: StickerPackCollectionInfo?) -> ViewController { +public func groupStickerPackSetupController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, emoji: Bool = false, currentPackInfo: StickerPackCollectionInfo?, completion: ((StickerPackCollectionInfo?) -> Void)? = nil) -> ViewController { let initialState = GroupStickerPackSetupControllerState(isSaving: false) let statePromise = ValuePromise(initialState, ignoreRepeated: true) @@ -329,7 +330,7 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres } let stickerPacks = Promise() - stickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) + stickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [emoji ? Namespaces.ItemCollection.CloudEmojiPacks : Namespaces.ItemCollection.CloudStickerPacks])])) let searchState = Promise<(String, GroupStickerPackSearchState)>() searchState.set(combineLatest(searchText.get(), initialData.get(), stickerPacks.get()) @@ -340,7 +341,7 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres } else if case let .data(data) = initialData, searchText.lowercased() == data.info.shortName { return .single((searchText, .found(StickerPackData(info: data.info, item: data.item)))) } else { - let namespace = Namespaces.ItemCollection.CloudStickerPacks + let namespace = emoji ? Namespaces.ItemCollection.CloudEmojiPacks : Namespaces.ItemCollection.CloudStickerPacks if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [namespace])] as? ItemCollectionInfosView { if let packsEntries = stickerPacksView.entriesByNamespace[namespace] { for entry in packsEntries { @@ -387,10 +388,16 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres let arguments = GroupStickerPackSetupControllerArguments(context: context, selectStickerPack: { info in searchText.set(info.shortName) + if let completion { + completion(info) + } }, openStickerPack: { info in presentStickerPackController?(info) }, updateSearchText: { text in searchText.set(text) + if text == "", let completion { + completion(nil) + } }, openStickersBot: { resolveDisposable.set((context.engine.peers.resolvePeerByName(name: "stickers") |> mapToSignal { result -> Signal in @@ -417,18 +424,27 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres stickerSettings = value } - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { - dismissImpl?() - }) + let leftNavigationButton: ItemListNavigationButton? + if emoji { + leftNavigationButton = nil + } else { + leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + dismissImpl?() + }) + } + var rightNavigationButton: ItemListNavigationButton? - if initialData != nil { - if state.isSaving { - rightNavigationButton = ItemListNavigationButton(content: .text(""), style: .activity, enabled: true, action: {}) - } else { - let enabled: Bool - var info: StickerPackCollectionInfo? - switch searchState.1 { + if let _ = completion { + rightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: {}) + } else { + if initialData != nil { + if state.isSaving { + rightNavigationButton = ItemListNavigationButton(content: .text(""), style: .activity, enabled: true, action: {}) + } else { + let enabled: Bool + var info: StickerPackCollectionInfo? + switch searchState.1 { case .searching, .notFound: enabled = false case .none: @@ -436,32 +452,38 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres case let .found(data): enabled = true info = data.info - } - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: enabled, action: { - if info?.id == currentPackInfo?.id { - dismissImpl?() - } else { - updateState { state in - var state = state - state.isSaving = true - return state - } - saveDisposable.set((context.engine.peers.updateGroupSpecificStickerset(peerId: peerId, info: info) - |> deliverOnMainQueue).start(error: { _ in - updateState { state in - var state = state - state.isSaving = false - return state - } - }, completed: { - dismissImpl?() - })) } - }) + rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: enabled, action: { + if let completion { + completion(info) + dismissImpl?() + } else { + if info?.id == currentPackInfo?.id { + dismissImpl?() + } else { + updateState { state in + var state = state + state.isSaving = true + return state + } + saveDisposable.set((context.engine.peers.updateGroupSpecificStickerset(peerId: peerId, info: info) + |> deliverOnMainQueue).start(error: { _ in + updateState { state in + var state = state + state.isSaving = false + return state + } + }, completed: { + dismissImpl?() + })) + } + } + }) + } } } - let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Channel_Info_Stickers), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(emoji ? "Group Emoji Pack" : presentationData.strings.Channel_Info_Stickers), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let hasData = initialData != nil let hadData = previousHadData.swap(hasData) @@ -471,7 +493,7 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) } - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStickerPackSetupControllerEntries(context: context, presentationData: presentationData, searchText: searchState.0, view: view, initialData: initialData, searchState: searchState.1, stickerSettings: stickerSettings), style: .blocks, emptyStateItem: emptyStateItem, animateChanges: hasData && hadData) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStickerPackSetupControllerEntries(context: context, presentationData: presentationData, searchText: searchState.0, view: view, initialData: initialData, searchState: searchState.1, stickerSettings: stickerSettings, emoji: emoji), style: .blocks, emptyStateItem: emptyStateItem, animateChanges: hasData && hadData) return (controllerState, (listState, arguments)) } |> afterDisposed { actionsDisposable.dispose() diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 1d06352aa7..52e1f0ef04 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -4420,7 +4420,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate checkPostingAvailability = true } if needsAudioSession { - self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .recordWithOthers, activate: { _ in + self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .record(speaker: false, video: true, withOthers: true), activate: { _ in if #available(iOS 13.0, *) { try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(true) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index f8cfcee9c5..40fcce5064 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -102,6 +102,7 @@ import PeerInfoPaneNode import MediaPickerUI import AttachmentUI import BoostLevelIconComponent +import GroupStickerPackSetupController public enum PeerInfoAvatarEditingMode { case generic @@ -540,7 +541,7 @@ private final class PeerInfoInteraction { let editingToggleMessageSignatures: (Bool) -> Void let openParticipantsSection: (PeerInfoParticipantsSection) -> Void let openRecentActions: () -> Void - let openStats: () -> Void + let openStats: (Bool) -> Void let editingOpenPreHistorySetup: () -> Void let editingOpenAutoremoveMesages: () -> Void let openPermissions: () -> Void @@ -595,7 +596,7 @@ private final class PeerInfoInteraction { editingToggleMessageSignatures: @escaping (Bool) -> Void, openParticipantsSection: @escaping (PeerInfoParticipantsSection) -> Void, openRecentActions: @escaping () -> Void, - openStats: @escaping () -> Void, + openStats: @escaping (Bool) -> Void, editingOpenPreHistorySetup: @escaping () -> Void, editingOpenAutoremoveMesages: @escaping () -> Void, openPermissions: @escaping () -> Void, @@ -1747,7 +1748,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL if let cachedData = data.cachedData as? CachedChannelData, cachedData.flags.contains(.canViewStats) { items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStats, label: .none, text: presentationData.strings.Channel_Info_Stats, icon: UIImage(bundleImageName: "Chat/Info/StatsIcon"), action: { - interaction.openStats() + interaction.openStats(false) })) } @@ -2029,7 +2030,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL interaction.openParticipantsSection(.memberRequests) })) } - + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRemovedUsers, label: .text(cachedData.participantsSummary.kickedCount.flatMap { $0 > 0 ? "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" : "" } ?? ""), text: presentationData.strings.GroupInfo_Permissions_Removed, icon: UIImage(bundleImageName: "Chat/Info/GroupRemovedIcon"), action: { interaction.openParticipantsSection(.banned) })) @@ -2433,8 +2434,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro openRecentActions: { [weak self] in self?.openRecentActions() }, - openStats: { [weak self] in - self?.openStats() + openStats: { [weak self] boosts in + self?.openStats(boosts: boosts) }, editingOpenPreHistorySetup: { [weak self] in self?.editingOpenPreHistorySetup() @@ -3058,7 +3059,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, displayGiveawayParticipationStatus: { _ in }, openPremiumStatusInfo: { _, _, _, _ in }, openRecommendedChannelContextMenu: { _, _, _ in - }, openGroupBoostInfo: { _ in + }, openGroupBoostInfo: { _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { @@ -3933,7 +3934,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro var previousTimestamp: Double? self.headerNode.displayPremiumIntro = { [weak self] sourceView, peerStatus, emojiStatusFileAndPack, white in - guard let strongSelf = self else { + guard let strongSelf = self, let peer = strongSelf.data?.peer else { return } let currentTimestamp = CACurrentMediaTime() @@ -3953,7 +3954,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro |> take(1) |> mapToSignal { emojiStatusFileAndPack -> Signal in if let (file, pack) = emojiStatusFileAndPack { - return .single(.emojiStatus(strongSelf.peerId, peerStatus.fileId, file, pack)) + return .single(.emojiStatus(peer.id, peerStatus.fileId, file, pack)) } else { return .complete() } @@ -6943,17 +6944,32 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } private func openBoost() { - guard let controller = self.controller else { + guard let peer = self.data?.peer, let channel = peer as? TelegramChannel, let controller = self.controller else { return } - let boostController = PremiumBoostLevelsScreen( - context: self.context, - peerId: controller.peerId, - mode: .user(mode: .current), - status: ChannelBoostStatus(level: 0, boosts: 0, giftBoosts: 0, currentLevelBoosts: 0, nextLevelBoosts: 1, premiumAudience: nil, url: "", prepaidGiveaways: [], boostedByMe: false), - myBoostStatus: nil - ) - controller.push(boostController) + + if channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) { + let boostsController = channelStatsController(context: self.context, updatedPresentationData: controller.updatedPresentationData, peerId: self.peerId, section: .boosts, boostStatus: self.boostStatus) + controller.push(boostsController) + } else { + let _ = combineLatest( + queue: Queue.mainQueue(), + context.engine.peers.getChannelBoostStatus(peerId: self.peerId), + context.engine.peers.getMyBoostStatus() + ).startStandalone(next: { [weak self] boostStatus, myBoostStatus in + guard let self, let controller = self.controller, let boostStatus, let myBoostStatus else { + return + } + let boostController = PremiumBoostLevelsScreen( + context: self.context, + peerId: controller.peerId, + mode: .user(mode: .current), + status: boostStatus, + myBoostStatus: myBoostStatus + ) + controller.push(boostController) + }) + } } private func openVoiceChatOptions(defaultJoinAsPeerId: PeerId?, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil) { diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD index 91503f00f9..17f4fe21d2 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD @@ -48,6 +48,7 @@ swift_library( "//submodules/TelegramUI/Components/Settings/WallpaperGridScreen", "//submodules/TelegramUI/Components/Settings/BoostLevelIconComponent", "//submodules/Markdown", + "//submodules/TelegramUI/Components/GroupStickerPackSetupController", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift index 0bb23e8034..11ed2f1869 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift @@ -36,6 +36,7 @@ import WallpaperGridScreen import BoostLevelIconComponent import BundleIconComponent import Markdown +import GroupStickerPackSetupController private final class EmojiActionIconComponent: Component { let context: AccountContext @@ -162,12 +163,14 @@ final class ChannelAppearanceScreenComponent: Component { private final class ContentsData { let peer: EnginePeer? let peerWallpaper: TelegramWallpaper? + let peerEmojiPack: StickerPackCollectionInfo? let subscriberCount: Int? let availableThemes: [TelegramTheme] - init(peer: EnginePeer?, peerWallpaper: TelegramWallpaper?, subscriberCount: Int?, availableThemes: [TelegramTheme]) { + init(peer: EnginePeer?, peerWallpaper: TelegramWallpaper?, peerEmojiPack: StickerPackCollectionInfo?, subscriberCount: Int?, availableThemes: [TelegramTheme]) { self.peer = peer self.peerWallpaper = peerWallpaper + self.peerEmojiPack = peerEmojiPack self.subscriberCount = subscriberCount self.availableThemes = availableThemes } @@ -177,15 +180,17 @@ final class ChannelAppearanceScreenComponent: Component { context.engine.data.subscribe( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), TelegramEngine.EngineData.Item.Peer.ParticipantCount(id: peerId), + TelegramEngine.EngineData.Item.Peer.EmojiPack(id: peerId), TelegramEngine.EngineData.Item.Peer.Wallpaper(id: peerId) ), telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager) ) |> map { peerData, cloudThemes -> ContentsData in - let (peer, subscriberCount, wallpaper) = peerData + let (peer, subscriberCount, emojiPack, wallpaper) = peerData return ContentsData( peer: peer, peerWallpaper: wallpaper, + peerEmojiPack: emojiPack, subscriberCount: subscriberCount, availableThemes: cloudThemes ) @@ -213,6 +218,7 @@ final class ChannelAppearanceScreenComponent: Component { static let backgroundFileId = Changes(rawValue: 1 << 3) static let emojiStatus = Changes(rawValue: 1 << 4) static let wallpaper = Changes(rawValue: 1 << 5) + static let emojiPack = Changes(rawValue: 1 << 6) } var nameColor: PeerNameColor @@ -221,16 +227,27 @@ final class ChannelAppearanceScreenComponent: Component { var backgroundFileId: Int64? var emojiStatus: PeerEmojiStatus? var wallpaper: TelegramWallpaper? + var emojiPack: StickerPackCollectionInfo? var changes: Changes - init(nameColor: PeerNameColor, profileColor: PeerNameColor?, replyFileId: Int64?, backgroundFileId: Int64?, emojiStatus: PeerEmojiStatus?, wallpaper: TelegramWallpaper?, changes: Changes) { + init( + nameColor: PeerNameColor, + profileColor: PeerNameColor?, + replyFileId: Int64?, + backgroundFileId: Int64?, + emojiStatus: PeerEmojiStatus?, + wallpaper: TelegramWallpaper?, + emojiPack: StickerPackCollectionInfo?, + changes: Changes + ) { self.nameColor = nameColor self.profileColor = profileColor self.replyFileId = replyFileId self.backgroundFileId = backgroundFileId self.emojiStatus = emojiStatus self.wallpaper = wallpaper + self.emojiPack = emojiPack self.changes = changes } } @@ -274,6 +291,7 @@ final class ChannelAppearanceScreenComponent: Component { private var updatedPeerProfileEmoji: Int64?? private var updatedPeerStatus: PeerEmojiStatus?? private var updatedPeerWallpaper: WallpaperSelectionResult? + private var updatedPeerEmojiPack: StickerPackCollectionInfo?? private var temporaryPeerWallpaper: TelegramWallpaper? private var requiredBoostSubject: BoostSubject? @@ -350,8 +368,8 @@ final class ChannelAppearanceScreenComponent: Component { } if !resolvedState.changes.isEmpty { - if let premiumConfiguration = self.premiumConfiguration, let requiredBoostSubject = self.requiredBoostSubject{ - let requiredLevel = requiredBoostSubject.requiredLevel(context: component.context, configuration: premiumConfiguration) + if let premiumConfiguration = self.premiumConfiguration, let requiredBoostSubject = self.requiredBoostSubject { + let requiredLevel = requiredBoostSubject.requiredLevel(group: self.isGroup, context: component.context, configuration: premiumConfiguration) if let boostLevel = self.boostLevel, requiredLevel > boostLevel { return true } @@ -478,6 +496,16 @@ final class ChannelAppearanceScreenComponent: Component { changes.insert(.emojiStatus) } + let emojiPack: StickerPackCollectionInfo? + if case let .some(value) = self.updatedPeerEmojiPack { + emojiPack = value + } else { + emojiPack = contentsData.peerEmojiPack + } + if emojiPack != contentsData.peerEmojiPack { + changes.insert(.emojiPack) + } + let wallpaper: TelegramWallpaper? if let updatedPeerWallpaper = self.updatedPeerWallpaper { switch updatedPeerWallpaper { @@ -500,6 +528,7 @@ final class ChannelAppearanceScreenComponent: Component { backgroundFileId: backgroundFileId, emojiStatus: emojiStatus, wallpaper: wallpaper, + emojiPack: emojiPack, changes: changes ) } @@ -512,7 +541,7 @@ final class ChannelAppearanceScreenComponent: Component { return } - let requiredLevel = requiredBoostSubject.requiredLevel(context: component.context, configuration: premiumConfiguration) + let requiredLevel = requiredBoostSubject.requiredLevel(group: self.isGroup, context: component.context, configuration: premiumConfiguration) if let boostLevel = self.boostLevel, requiredLevel > boostLevel { self.displayBoostLevels(subject: requiredBoostSubject) return @@ -556,6 +585,13 @@ final class ChannelAppearanceScreenComponent: Component { return .generic }) } + if resolvedState.changes.contains(.emojiPack) { + signals.append(component.context.engine.peers.updateGroupSpecificEmojiset(peerId: component.peerId, info: resolvedState.emojiPack) + |> ignoreValues + |> mapError { _ -> ApplyError in + return .generic + }) + } if resolvedState.changes.contains(.wallpaper) { if let updatedPeerWallpaper { switch updatedPeerWallpaper { @@ -605,7 +641,7 @@ final class ChannelAppearanceScreenComponent: Component { }) } - private func displayBoostLevels(subject: BoostSubject) { + private func displayBoostLevels(subject: BoostSubject?) { guard let component = self.component, let status = self.boostStatus else { return } @@ -647,8 +683,8 @@ final class ChannelAppearanceScreenComponent: Component { return } let level = boostStatus.level - let requiredWallpaperLevel = Int(BoostSubject.wallpaper.requiredLevel(context: component.context, configuration: premiumConfiguration)) - let requiredCustomWallpaperLevel = Int(BoostSubject.customWallpaper.requiredLevel(context: component.context, configuration: premiumConfiguration)) + let requiredWallpaperLevel = Int(BoostSubject.wallpaper.requiredLevel(group: self.isGroup, context: component.context, configuration: premiumConfiguration)) + let requiredCustomWallpaperLevel = Int(BoostSubject.customWallpaper.requiredLevel(group: self.isGroup, context: component.context, configuration: premiumConfiguration)) let selectedWallpaper = resolvedState.wallpaper @@ -684,12 +720,15 @@ final class ChannelAppearanceScreenComponent: Component { } private func openEmojiPackSetup() { - guard let component = self.component, let environment = self.environment else { + guard let component = self.component, let environment = self.environment, let resolvedState = self.resolveState() else { return } - let controller = component.context.sharedContext.makeInstalledStickerPacksController(context: component.context, mode: .groupEmoji(selectedPack: nil, completion: { _ in - - }), forceTheme: nil) + let controller = groupStickerPackSetupController(context: component.context, peerId: component.peerId, emoji: true, currentPackInfo: resolvedState.emojiPack, completion: { [weak self] emojiPack in + if let self { + self.updatedPeerEmojiPack = emojiPack + self.state?.updated(transition: .spring(duration: 0.4)) + } + }) environment.controller()?.push(controller) } @@ -795,6 +834,16 @@ final class ChannelAppearanceScreenComponent: Component { environment.controller()?.present(controller, in: .window(.root)) } + private var isGroup: Bool { + guard let contentsData = self.contentsData, let peer = contentsData.peer else { + return false + } + if case let .channel(channel) = peer, case .group = channel.info { + return true + } + return false + } + func update(component: ChannelAppearanceScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { self.isUpdating = true defer { @@ -859,13 +908,6 @@ final class ChannelAppearanceScreenComponent: Component { self.boostLevel = boostStatus?.level self.boostStatus = boostStatus - #if DEBUG - if boostStatus == nil { - self.boostLevel = 0 - self.boostStatus = ChannelBoostStatus(level: 0, boosts: 0, giftBoosts: nil, currentLevelBoosts: 0, nextLevelBoosts: 10, premiumAudience: nil, url: "", prepaidGiveaways: [], boostedByMe: false) - } - #endif - if !self.isUpdating { self.state?.updated(transition: .immediate) } @@ -877,12 +919,7 @@ final class ChannelAppearanceScreenComponent: Component { } var requiredBoostSubjects: [BoostSubject] = [.nameColors(colors: resolvedState.nameColor)] - - let replyIconLevel = Int(BoostSubject.nameIcon.requiredLevel(context: component.context, configuration: premiumConfiguration)) - let profileIconLevel = Int(BoostSubject.profileIcon.requiredLevel(context: component.context, configuration: premiumConfiguration)) - let emojiStatusLevel = Int(BoostSubject.emojiStatus.requiredLevel(context: component.context, configuration: premiumConfiguration)) - let themeLevel = Int(BoostSubject.wallpaper.requiredLevel(context: component.context, configuration: premiumConfiguration)) - + let replyFileId = resolvedState.replyFileId if replyFileId != nil { requiredBoostSubjects.append(.nameIcon) @@ -902,6 +939,12 @@ final class ChannelAppearanceScreenComponent: Component { if emojiStatus != nil { requiredBoostSubjects.append(.emojiStatus) } + + let emojiPack = resolvedState.emojiPack + if emojiPack != nil { + requiredBoostSubjects.append(.emojiPack) + } + let statusFileId = emojiStatus?.fileId let cloudThemes: [PresentationThemeReference] = contentsData.availableThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil, creatorAccountId: $0.isCreator ? component.context.account.id : nil)) } @@ -981,8 +1024,14 @@ final class ChannelAppearanceScreenComponent: Component { } } + let replyIconLevel = Int(BoostSubject.nameIcon.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration)) + let profileIconLevel = Int(BoostSubject.profileIcon.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration)) + let emojiStatusLevel = Int(BoostSubject.emojiStatus.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration)) + let emojiPackLevel = Int(BoostSubject.emojiPack.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration)) + let themeLevel = Int(BoostSubject.wallpaper.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration)) + let requiredBoostSubject: BoostSubject - if let maxBoostSubject = requiredBoostSubjects.max(by: { $0.requiredLevel(context: component.context, configuration: premiumConfiguration) < $1.requiredLevel(context: component.context, configuration: premiumConfiguration) }) { + if let maxBoostSubject = requiredBoostSubjects.max(by: { $0.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration) < $1.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration) }) { requiredBoostSubject = maxBoostSubject } else { requiredBoostSubject = .nameColors(colors: resolvedState.nameColor) @@ -1121,7 +1170,7 @@ final class ChannelAppearanceScreenComponent: Component { title: AnyComponent(HStack(boostContents, spacing: 12.0)), icon: nil, action: { [weak self] _ in - self?.displayBoostLevels(subject: .profileColors) + self?.displayBoostLevels(subject: nil) } ))) ] @@ -1148,7 +1197,7 @@ final class ChannelAppearanceScreenComponent: Component { )), maximumNumberOfLines: 0 )))) - if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelProfileIconLevel { + if let boostLevel = self.boostLevel, boostLevel < (isGroup ? premiumConfiguration.minGroupProfileIconLevel : premiumConfiguration.minChannelProfileIconLevel) { profileLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( strings: environment.strings, level: profileIconLevel @@ -1285,12 +1334,19 @@ final class ChannelAppearanceScreenComponent: Component { )), maximumNumberOfLines: 0 )))) - if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelEmojiStatusLevel { + if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minGroupEmojiPackLevel { emojiPackContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( strings: environment.strings, - level: emojiStatusLevel + level: emojiPackLevel )))) } + + + var emojiPackFile: TelegramMediaFile? + if let thumbnail = emojiPack?.thumbnail { + emojiPackFile = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: thumbnail.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: thumbnail.immediateThumbnailData, mimeType: "", size: nil, attributes: []) + } + let emojiPackSectionSize = self.emojiPackSection.update( transition: transition, component: AnyComponent(ListSectionComponent( @@ -1311,8 +1367,8 @@ final class ChannelAppearanceScreenComponent: Component { icon: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent( context: component.context, color: environment.theme.list.itemAccentColor, - fileId: statusFileId, - file: statusFileId.flatMap { self.cachedIconFiles[$0] } + fileId: emojiPack?.thumbnailFileId, + file: emojiPackFile ))), action: { [weak self] view in guard let self, let resolvedState = self.resolveState() else { @@ -1347,7 +1403,7 @@ final class ChannelAppearanceScreenComponent: Component { )), maximumNumberOfLines: 0 )))) - if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelEmojiStatusLevel { + if let boostLevel = self.boostLevel, boostLevel < (isGroup ? premiumConfiguration.minGroupEmojiStatusLevel : premiumConfiguration.minChannelEmojiStatusLevel) { emojiStatusContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( strings: environment.strings, level: emojiStatusLevel @@ -1532,7 +1588,7 @@ final class ChannelAppearanceScreenComponent: Component { )), maximumNumberOfLines: 0 )))) - if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelCustomWallpaperLevel { + if let boostLevel = self.boostLevel, boostLevel < (isGroup ? premiumConfiguration.minGroupCustomWallpaperLevel : premiumConfiguration.minChannelCustomWallpaperLevel) { wallpaperLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( strings: environment.strings, level: themeLevel @@ -1549,16 +1605,28 @@ final class ChannelAppearanceScreenComponent: Component { var wallpaperItems: [AnyComponentWithIdentity] = [] if isGroup { - let messageItem = PeerNameColorChatPreviewItem.MessageItem( + let incomingMessageItem = PeerNameColorChatPreviewItem.MessageItem( outgoing: false, - peerId: EnginePeer.Id(namespace: peer.id.namespace, id: PeerId.Id._internalFromInt64Value(0)), + peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0)), author: peer.compactDisplayTitle, photo: peer.profileImageRepresentations, - nameColor: resolvedState.nameColor, - backgroundEmojiId: replyFileId, - reply: (peer.compactDisplayTitle, environment.strings.Channel_Appearance_ExampleReplyText), - linkPreview: (environment.strings.Channel_Appearance_ExampleLinkWebsite, environment.strings.Channel_Appearance_ExampleLinkTitle, environment.strings.Channel_Appearance_ExampleLinkText), - text: environment.strings.Channel_Appearance_ExampleText + nameColor: .blue, + backgroundEmojiId: nil, + reply: (environment.strings.Appearance_PreviewReplyAuthor, environment.strings.Appearance_PreviewReplyText), + linkPreview: nil, + text: environment.strings.Appearance_PreviewIncomingText + ) + + let outgoingMessageItem = PeerNameColorChatPreviewItem.MessageItem( + outgoing: true, + peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), + author: peer.compactDisplayTitle, + photo: peer.profileImageRepresentations, + nameColor: .blue, + backgroundEmojiId: nil, + reply: nil, + linkPreview: nil, + text: environment.strings.Appearance_PreviewOutgoingText ) wallpaperItems.append( @@ -1574,7 +1642,7 @@ final class ChannelAppearanceScreenComponent: Component { wallpaper: chatPreviewWallpaper, dateTimeFormat: environment.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, - messageItems: [messageItem] + messageItems: [incomingMessageItem, outgoingMessageItem] ), params: listItemParams ))) @@ -1665,7 +1733,7 @@ final class ChannelAppearanceScreenComponent: Component { Text(text: environment.strings.Channel_Appearance_ApplyButton, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor) ))) - let requiredLevel = requiredBoostSubject.requiredLevel(context: component.context, configuration: premiumConfiguration) + let requiredLevel = requiredBoostSubject.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration) if let boostLevel = self.boostLevel, requiredLevel > boostLevel { buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(PremiumLockButtonSubtitleComponent( count: Int(requiredLevel), @@ -1780,9 +1848,9 @@ public class ChannelAppearanceScreen: ViewControllerComponentContainer { boostStatus: boostStatus ), navigationBarAppearance: .default, theme: .default, updatedPresentationData: updatedPresentationData) -// let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } self.title = "" //presentationData.strings.Channel_Appearance_Title -// self.navigationItem.backBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) + self.navigationItem.backBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) self.ready.set(.never()) diff --git a/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift b/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift index 74b0f895a3..3e9a18b768 100644 --- a/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift +++ b/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift @@ -80,6 +80,7 @@ public final class SettingsThemeWallpaperNode: ASDisplayNode { self.statusNode.isUserInteractionEnabled = false self.emojiContainerNode = ASDisplayNode() + self.emojiContainerNode.isUserInteractionEnabled = false self.emojiImageNode = TransformImageNode() super.init() diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 7eb14f88d4..f0bfb15166 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -1030,10 +1030,22 @@ final class ShareWithPeersScreenComponent: Component { if case .user = peer { subtitle = environment.strings.VoiceChat_PersonalAccount } else { - if let count = component.stateContext.stateValue?.participants[peer.id] { - subtitle = environment.strings.Conversation_StatusSubscribers(Int32(count)) + if case let .channel(channel) = peer { + if case .broadcast = channel.info { + if let count = component.stateContext.stateValue?.participants[peer.id] { + subtitle = environment.strings.Conversation_StatusSubscribers(Int32(count)) + } else { + subtitle = environment.strings.Channel_Status + } + } else { + if let count = component.stateContext.stateValue?.participants[peer.id] { + subtitle = environment.strings.Conversation_StatusMembers(Int32(count)) + } else { + subtitle = environment.strings.Group_Status + } + } } else { - subtitle = environment.strings.Channel_Status + subtitle = nil } } diff --git a/submodules/TelegramUI/Components/SliderContextItem/Sources/SliderContextItem.swift b/submodules/TelegramUI/Components/SliderContextItem/Sources/SliderContextItem.swift index ae6018d0a8..5272830366 100644 --- a/submodules/TelegramUI/Components/SliderContextItem/Sources/SliderContextItem.swift +++ b/submodules/TelegramUI/Components/SliderContextItem/Sources/SliderContextItem.swift @@ -122,6 +122,8 @@ private final class SliderContextItemNode: ASDisplayNode, ContextMenuCustomNode, override func didLoad() { super.didLoad() + self.view.disablesInteractiveTransitionGestureRecognizer = true + if let vibrancyEffectView = self.vibrancyEffectView { Queue.mainQueue().after(0.05) { if let effectNode = findEffectNode(node: self.supernode) { diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index 29284f0c7c..5f58798e50 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -655,6 +655,7 @@ public class VideoMessageCameraScreen: ViewController { self.backgroundColor = .clear + self.view.addSubview(self.backgroundView) self.view.addSubview(self.containerView) self.containerView.addSubview(self.previewContainerView) @@ -694,7 +695,7 @@ public class VideoMessageCameraScreen: ViewController { func withReadyCamera(isFirstTime: Bool = false, _ f: @escaping () -> Void) { let previewReady: Signal if #available(iOS 13.0, *) { - previewReady = self.cameraState.isDualCameraEnabled ? self.additionalPreviewView.isPreviewing : self.mainPreviewView.isPreviewing |> delay(0.2, queue: Queue.mainQueue()) + previewReady = self.cameraState.isDualCameraEnabled ? self.additionalPreviewView.isPreviewing : self.mainPreviewView.isPreviewing |> delay(0.3, queue: Queue.mainQueue()) } else { previewReady = .single(true) |> delay(0.35, queue: Queue.mainQueue()) } @@ -739,7 +740,7 @@ public class VideoMessageCameraScreen: ViewController { position: self.cameraState.position, isDualEnabled: self.cameraState.isDualCameraEnabled, audio: true, - photo: true, + photo: false, metadata: false, isRoundVideo: true ), @@ -791,9 +792,9 @@ public class VideoMessageCameraScreen: ViewController { func animateIn() { self.animatingIn = true - if let chatNode = self.controller?.chatNode { - chatNode.supernode?.view.insertSubview(self.backgroundView, aboveSubview: chatNode.view) - } +// if let chatNode = self.controller?.chatNode { +// chatNode.supernode?.view.insertSubview(self.backgroundView, aboveSubview: chatNode.view) +// } self.backgroundView.alpha = 0.0 UIView.animate(withDuration: 0.4, animations: { @@ -1681,9 +1682,9 @@ public class VideoMessageCameraScreen: ViewController { private func requestAudioSession() { let audioSessionType: ManagedAudioSessionType if self.context.sharedContext.currentMediaInputSettings.with({ $0 }).pauseMusicOnRecording { - audioSessionType = .record(speaker: false, withOthers: false) + audioSessionType = .record(speaker: false, video: true, withOthers: false) } else { - audioSessionType = .recordWithOthers + audioSessionType = .record(speaker: false, video: true, withOthers: true) } self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: audioSessionType, activate: { [weak self] _ in diff --git a/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/AudioTranscription.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/AudioTranscription.imageset/Contents.json new file mode 100644 index 0000000000..62f1f00fa3 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/AudioTranscription.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "voicetotext_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/AudioTranscription.imageset/voicetotext_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/AudioTranscription.imageset/voicetotext_30.pdf new file mode 100644 index 0000000000..2c905d2cdb --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/AudioTranscription.imageset/voicetotext_30.pdf @@ -0,0 +1,140 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 5.834961 2.334961 cm +0.000000 0.000000 0.000000 scn +9.164956 24.330078 m +6.312406 24.330078 3.999956 22.017628 3.999956 19.165077 c +3.999956 13.165078 l +3.999956 10.312528 6.312406 8.000078 9.164956 8.000078 c +12.017498 8.000078 14.329942 10.312513 14.329956 13.165051 c +14.329956 13.165078 l +14.330078 16.165051 l +14.330093 16.532320 14.032374 16.830063 13.665105 16.830078 c +13.297836 16.830093 13.000093 16.532375 13.000078 16.165104 c +12.999956 13.165105 l +12.999956 13.165078 l +12.999956 11.047067 11.282969 9.330078 9.164956 9.330078 c +7.046944 9.330078 5.329956 11.047067 5.329956 13.165078 c +5.329956 19.165077 l +5.329956 21.283091 7.046944 23.000078 9.164956 23.000078 c +9.458838 23.000078 9.744217 22.967144 10.017904 22.905033 c +10.376066 22.823750 10.732306 23.048206 10.813588 23.406368 c +10.894870 23.764530 10.670414 24.120770 10.312252 24.202051 c +9.942642 24.285933 9.558549 24.330078 9.164956 24.330078 c +h +1.330000 13.665080 m +1.330000 14.032350 1.032269 14.330080 0.665000 14.330080 c +0.297731 14.330080 0.000000 14.032350 0.000000 13.665080 c +0.000000 13.165079 l +0.000000 8.327017 3.748748 4.364523 8.500000 4.023836 c +8.500000 0.665079 l +8.500000 0.297810 8.797730 0.000080 9.165000 0.000080 c +9.532269 0.000080 9.830000 0.297810 9.830000 0.665079 c +9.830000 4.023836 l +14.581252 4.364523 18.330002 8.327017 18.330002 13.165079 c +18.330002 13.665080 l +18.330002 14.032350 18.032270 14.330080 17.665001 14.330080 c +17.297731 14.330080 17.000000 14.032350 17.000000 13.665080 c +17.000000 13.165079 l +17.000000 8.837929 13.492150 5.330080 9.165000 5.330080 c +4.837850 5.330080 1.330000 8.837929 1.330000 13.165079 c +1.330000 13.665080 l +h +20.773884 23.920847 m +20.670631 24.168657 20.428501 24.330078 20.160038 24.330078 c +19.891579 24.330078 19.649448 24.168657 19.546192 23.920847 c +17.046192 17.920847 l +16.904936 17.581829 17.065252 17.192490 17.404270 17.051231 c +17.743288 16.909975 18.132627 17.070292 18.273884 17.409309 c +18.936707 19.000078 l +21.383373 19.000078 l +22.046192 17.409309 l +22.187450 17.070292 22.576790 16.909975 22.915808 17.051231 c +23.254826 17.192490 23.415142 17.581829 23.273886 17.920847 c +20.773884 23.920847 l +h +20.160038 21.936077 m +20.829205 20.330078 l +19.490871 20.330078 l +20.160038 21.936077 l +h +13.189813 23.135303 m +13.449512 23.395002 13.870566 23.395002 14.130264 23.135303 c +16.130264 21.135303 l +16.194023 21.071547 16.242128 20.998062 16.274580 20.919630 c +16.306854 20.841801 16.324774 20.756514 16.325035 20.667072 c +16.325039 20.665077 l +16.325035 20.663084 l +16.324532 20.493561 16.259607 20.324194 16.130264 20.194851 c +14.130264 18.194853 l +13.870566 17.935154 13.449512 17.935154 13.189813 18.194853 c +12.930115 18.454552 12.930115 18.875607 13.189813 19.135303 c +14.054586 20.000078 l +10.660039 20.000078 l +10.292769 20.000078 9.995039 20.297810 9.995039 20.665077 c +9.995039 21.032347 10.292769 21.330078 10.660039 21.330078 c +14.054586 21.330078 l +13.189813 22.194853 l +12.930115 22.454552 12.930115 22.875607 13.189813 23.135303 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 3084 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000003174 00000 n +0000003197 00000 n +0000003370 00000 n +0000003444 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3503 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/EmojiPack.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/EmojiPack.imageset/Contents.json new file mode 100644 index 0000000000..602367a4a9 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/EmojiPack.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "customemoji_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/EmojiPack.imageset/customemoji_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/EmojiPack.imageset/customemoji_30.pdf new file mode 100644 index 0000000000..b06238e39f --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/EmojiPack.imageset/customemoji_30.pdf @@ -0,0 +1,207 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.000000 15.000000 cm +0.000000 0.000000 0.000000 scn +2.764045 6.214447 m +2.730338 6.067720 2.629215 6.000000 2.505619 6.000000 c +2.370788 6.000000 2.269665 6.079007 2.247193 6.214447 c +2.084556 7.059471 2.008693 7.479908 1.761133 7.731189 c +1.512707 7.983349 1.091379 8.065167 0.235956 8.234763 c +0.089889 8.268623 0.000000 8.370203 0.000000 8.494356 c +0.000000 8.629797 0.089889 8.731377 0.235956 8.765237 c +1.884714 9.092119 1.889418 9.113951 2.211266 10.607844 c +2.222804 10.661399 2.234749 10.716846 2.247193 10.774266 c +2.269665 10.909706 2.370788 11.000000 2.505619 11.000000 c +2.640450 11.000000 2.730338 10.920993 2.764045 10.774266 c +2.773893 10.727359 2.783427 10.681768 2.792694 10.637450 c +3.111211 9.114224 3.115385 9.094264 4.764046 8.765237 c +4.910113 8.731377 5.000000 8.641084 5.000000 8.494356 c +5.000000 8.358916 4.910113 8.268623 4.764046 8.234763 c +3.069506 7.919125 3.067419 7.907477 2.765165 6.220693 c +2.764045 6.214447 l +h +21.255651 0.239583 m +21.234783 0.093750 21.151304 0.000000 20.994783 0.000000 c +20.848696 0.000000 20.775652 0.093750 20.733913 0.239583 c +20.725233 0.285484 20.716770 0.330369 20.708492 0.374265 c +20.348595 2.282850 20.341238 2.321868 18.240000 2.729167 c +18.093912 2.770833 18.000000 2.843750 18.000000 3.000000 c +18.000000 3.156250 18.093912 3.229167 18.240000 3.260417 c +20.297527 3.669214 20.309143 3.724892 20.681557 5.510261 c +20.698215 5.590123 20.715595 5.673446 20.733913 5.760417 c +20.775652 5.906250 20.848696 6.000000 20.994783 6.000000 c +21.151304 6.000000 21.234783 5.906250 21.255651 5.760417 c +21.268061 5.701499 21.280066 5.644257 21.291733 5.588629 c +21.682287 3.726425 21.693047 3.675112 23.760000 3.260417 c +23.916521 3.229167 24.000000 3.156250 24.000000 3.000000 c +24.000000 2.843750 23.916521 2.770833 23.760000 2.729167 c +21.639460 2.326447 21.631285 2.282002 21.265360 0.292361 c +21.255651 0.239583 l +h +f* +n +Q +q +1.000000 0.000000 -0.000000 1.000000 2.834961 2.834961 cm +0.000000 0.000000 0.000000 scn +4.163339 19.470703 m +3.886738 19.167919 3.627093 18.849380 3.385908 18.516592 c +3.315481 18.882309 3.260239 19.161963 3.195358 19.380081 c +3.126438 19.611776 3.046642 19.774040 2.926216 19.896275 c +2.920539 19.902039 2.914770 19.907711 2.908909 19.913300 c +2.898950 19.922628 l +2.879345 19.940674 2.858713 19.957811 2.836943 19.974150 c +3.301094 20.527996 3.813655 21.039896 4.368117 21.503338 c +4.383039 21.484247 4.398642 21.466011 4.415012 21.448555 c +4.422750 21.440306 4.430657 21.432230 4.438746 21.424318 c +4.560323 21.305412 4.722594 21.223927 4.955741 21.151937 c +5.169733 21.085863 5.443433 21.027790 5.800178 20.956127 c +5.823530 20.951441 l +5.484449 20.706285 5.160123 20.441982 4.852137 20.160120 c +4.668910 20.098377 4.532844 20.025940 4.426920 19.925903 c +4.419153 19.918465 l +4.306369 19.808969 4.228240 19.666809 4.163339 19.470703 c +h +7.082016 21.736238 m +8.597874 22.542948 10.328053 23.000078 12.165000 23.000078 c +15.688613 23.000078 18.819374 21.318090 20.798193 18.713207 c +20.861319 18.821304 20.943417 18.934189 21.050665 19.041252 c +21.292435 19.282600 21.587986 19.421938 21.891327 19.472813 c +19.671530 22.422594 16.141024 24.330078 12.165000 24.330078 c +9.722339 24.330078 7.447829 23.610147 5.542076 22.370897 c +5.692157 22.335089 5.895671 22.293228 6.189427 22.234604 c +6.209517 22.230595 l +6.229474 22.225967 l +6.487258 22.166210 6.813300 22.030020 7.082016 21.736238 c +h +1.970473 18.805342 m +0.724305 16.896053 0.000000 14.615150 0.000000 12.165078 c +0.000000 5.446533 5.446456 0.000076 12.165000 0.000076 c +18.883545 0.000076 24.330002 5.446533 24.330002 12.165078 c +24.330002 12.582549 24.308968 12.995111 24.267912 13.401757 c +24.193945 13.383650 24.129078 13.366135 24.071005 13.348480 c +24.030434 13.336147 23.998871 13.325320 23.974834 13.316376 c +23.967258 13.294499 23.958279 13.266483 23.948175 13.231253 c +23.885868 13.013967 23.829411 12.710866 23.731909 12.181010 c +23.688400 11.915857 23.568218 11.566923 23.266214 11.274580 c +23.164131 11.175761 23.054655 11.096324 22.941631 11.033643 c +22.375893 5.581362 17.766920 1.330078 12.165000 1.330078 c +6.180995 1.330078 1.330000 6.181072 1.330000 12.165078 c +1.330000 14.001682 1.786960 15.731558 2.593387 17.247211 c +2.320833 17.495625 2.162627 17.816124 2.104306 18.138243 c +2.045608 18.443207 2.005440 18.651093 1.970473 18.805342 c +h +22.839943 14.030951 m +22.854223 14.056534 22.869083 14.081108 22.884594 14.104742 c +22.980665 14.251131 23.101734 14.361477 23.265259 14.452059 c +23.438992 14.548294 23.660646 14.622222 23.951162 14.693368 c +23.986843 14.702106 24.023561 14.710802 24.061359 14.719493 c +23.992277 15.042780 23.910357 15.361301 23.816145 15.674510 c +23.594534 15.737709 23.419144 15.804732 23.276915 15.887229 c +23.103924 15.987570 22.979992 16.110804 22.881632 16.277901 c +22.781334 16.448290 22.707623 16.664288 22.635593 16.948122 c +22.578993 17.171165 22.523428 17.436102 22.456816 17.753716 c +22.454109 17.766624 l +22.444901 17.810524 l +22.420734 17.925503 l +22.399866 18.071337 22.316387 18.165087 22.159866 18.165087 c +22.013779 18.165087 21.940735 18.071337 21.898996 17.925503 c +21.882824 17.848526 l +21.874866 17.810532 l +21.846638 17.675344 l +21.812077 17.509653 21.780622 17.358860 21.750351 17.221300 c +22.270237 16.237764 22.643177 15.164577 22.839943 14.030951 c +h +12.164252 9.958713 m +14.317219 9.958713 16.264034 10.424266 17.662300 10.758640 c +19.028076 11.085245 19.870502 11.286700 19.870502 10.806913 c +19.870502 6.550869 16.420296 3.100664 12.164252 3.100664 c +7.908207 3.100664 4.458001 6.550869 4.458001 10.806913 c +4.458001 11.286698 5.300421 11.085247 6.666188 10.758643 c +6.666201 10.758640 l +6.666217 10.758637 l +8.064482 10.424262 10.011292 9.958713 12.164252 9.958713 c +h +16.926613 9.254669 m +15.850132 8.972999 14.200197 8.541277 12.155004 8.541277 c +10.109828 8.541277 8.459906 8.972991 7.383423 9.254661 c +7.383405 9.254666 l +7.383395 9.254669 l +6.488583 9.488805 5.990004 9.619263 5.990004 9.311902 c +5.990004 6.361948 10.471958 6.229403 12.155004 6.229403 c +14.044576 6.229403 18.320004 6.362463 18.320004 9.311902 c +18.320004 9.619263 17.821424 9.488805 16.926613 9.254669 c +h +8.665002 13.415092 m +9.493429 13.415092 10.165002 14.198593 10.165002 15.165092 c +10.165002 16.131590 9.493429 16.915092 8.665002 16.915092 c +7.836575 16.915092 7.165002 16.131590 7.165002 15.165092 c +7.165002 14.198593 7.836575 13.415092 8.665002 13.415092 c +h +17.165001 15.165092 m +17.165001 14.198593 16.493429 13.415092 15.665002 13.415092 c +14.836575 13.415092 14.165002 14.198593 14.165002 15.165092 c +14.165002 16.131590 14.836575 16.915092 15.665002 16.915092 c +16.493429 16.915092 17.165001 16.131590 17.165001 15.165092 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 6708 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000006798 00000 n +0000006821 00000 n +0000006994 00000 n +0000007068 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +7127 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Boost.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Boost.imageset/Contents.json new file mode 100644 index 0000000000..7f8630ed82 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Boost.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "boost_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Boost.imageset/boost_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Boost.imageset/boost_30.pdf new file mode 100644 index 0000000000..fd9103e865 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Boost.imageset/boost_30.pdf @@ -0,0 +1,116 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 15.834961 4.834961 cm +0.000000 0.000000 0.000000 scn +4.830000 7.665078 m +4.830000 8.032348 4.532269 8.330078 4.165000 8.330078 c +3.797731 8.330078 3.500000 8.032348 3.500000 7.665078 c +3.500000 4.830078 l +0.665000 4.830078 l +0.297731 4.830078 0.000000 4.532348 0.000000 4.165078 c +0.000000 3.797809 0.297731 3.500078 0.665000 3.500078 c +3.500000 3.500078 l +3.500000 0.665078 l +3.500000 0.297809 3.797731 0.000078 4.165000 0.000078 c +4.532269 0.000078 4.830000 0.297809 4.830000 0.665078 c +4.830000 3.500078 l +7.665000 3.500078 l +8.032269 3.500078 8.330000 3.797809 8.330000 4.165078 c +8.330000 4.532348 8.032269 4.830078 7.665000 4.830078 c +4.830000 4.830078 l +4.830000 7.665078 l +h +f* +n +Q +q +1.000000 0.000000 -0.000000 1.000000 8.511719 3.673828 cm +0.000000 0.000000 0.000000 scn +8.062260 14.252642 m +7.985591 13.767072 8.360885 13.327871 8.852470 13.327871 c +12.167960 13.327871 l +12.811290 13.327871 13.191483 12.607067 12.828204 12.076122 c +11.935228 10.771004 l +11.791506 10.803908 11.641865 10.821289 11.488164 10.821289 c +10.386356 10.821289 9.493164 9.928097 9.493164 8.826289 c +9.493164 7.321289 l +7.988164 7.321289 l +6.886356 7.321289 5.993164 6.428097 5.993164 5.326290 c +5.993164 4.576958 6.406287 3.924118 7.017194 3.583107 c +5.288596 1.056694 l +4.806934 0.352728 3.705108 0.790676 3.838140 1.633213 c +4.907070 8.403103 l +4.983739 8.888673 4.608444 9.327872 4.116858 9.327872 c +0.801369 9.327872 l +0.158039 9.327872 -0.222153 10.048677 0.141125 10.579621 c +7.680734 21.599051 l +8.162395 22.303017 9.264221 21.865067 9.131189 21.022530 c +8.062260 14.252642 l +h +8.664898 5.991289 m +7.778071 4.695158 l +7.513759 4.783100 7.323164 5.032436 7.323164 5.326290 c +7.323164 5.693558 7.620895 5.991289 7.988164 5.991289 c +8.664898 5.991289 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1821 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001911 00000 n +0000001934 00000 n +0000002107 00000 n +0000002181 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2240 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Contents.json new file mode 100644 index 0000000000..6e965652df --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Features.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Features.imageset/Contents.json new file mode 100644 index 0000000000..0ed80ac1b2 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Features.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "about_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Features.imageset/about_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Features.imageset/about_30.pdf new file mode 100644 index 0000000000..ee0c4d4620 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Features.imageset/about_30.pdf @@ -0,0 +1,85 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +-1.000000 0.000000 -0.000000 -1.000000 24.666992 25.000000 cm +0.000000 0.000000 0.000000 scn +10.000000 0.000000 m +15.522847 0.000000 20.000000 4.477153 20.000000 10.000000 c +20.000000 15.522848 15.522847 20.000000 10.000000 20.000000 c +4.477152 20.000000 0.000000 15.522848 0.000000 10.000000 c +0.000000 4.477153 4.477152 0.000000 10.000000 0.000000 c +h +10.000000 16.499992 m +10.552284 16.499992 11.000000 16.052279 11.000000 15.499993 c +11.000000 8.499993 l +11.000000 7.947709 10.552284 7.499993 10.000000 7.499993 c +9.447715 7.499993 9.000000 7.947709 9.000000 8.499993 c +9.000000 15.499993 l +9.000000 16.052279 9.447715 16.499992 10.000000 16.499992 c +h +10.000000 3.749992 m +10.690356 3.749992 11.250000 4.309637 11.250000 4.999993 c +11.250000 5.690350 10.690356 6.249993 10.000000 6.249993 c +9.309644 6.249993 8.750000 5.690350 8.750000 4.999993 c +8.750000 4.309637 9.309644 3.749992 10.000000 3.749992 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 948 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001038 00000 n +0000001060 00000 n +0000001233 00000 n +0000001307 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1366 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Giveaway.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Giveaway.imageset/Contents.json new file mode 100644 index 0000000000..cff8af9df5 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Giveaway.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "giveaway_30 (2).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Giveaway.imageset/giveaway_30 (2).pdf b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Giveaway.imageset/giveaway_30 (2).pdf new file mode 100644 index 0000000000..d32aef4b3f --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Giveaway.imageset/giveaway_30 (2).pdf @@ -0,0 +1,186 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 7.428711 20.093262 cm +0.000000 0.000000 0.000000 scn +6.271209 0.000062 m +4.615478 0.000062 l +3.771815 0.000062 3.116881 0.213802 2.650675 0.641283 c +2.184469 1.068763 1.951365 1.570652 1.951365 2.146948 c +1.951365 2.710829 2.139227 3.152450 2.514951 3.471810 c +2.890676 3.791170 3.377159 3.950850 3.974400 3.950850 c +4.607714 3.950850 5.148844 3.728631 5.597790 3.284194 c +6.046736 2.839757 6.271209 2.243457 6.271209 1.495295 c +6.271209 0.000062 l +8.333720 0.000062 l +8.333720 1.495295 l +8.333720 2.243457 8.416081 2.839757 8.865445 3.284194 c +9.314810 3.728631 9.859194 3.950850 10.498600 3.950850 c +11.089033 3.950850 11.571902 3.791170 11.947208 3.471810 c +12.322515 3.152450 12.510168 2.710829 12.510168 2.146948 c +12.510168 1.570652 12.277244 1.068763 11.811396 0.641283 c +11.345549 0.213802 10.690793 0.000062 9.847131 0.000062 c +8.333720 0.000062 l +13.456915 0.000062 l +13.768794 0.289877 14.014379 0.629154 14.193670 1.017893 c +14.372962 1.406632 14.462607 1.832761 14.462607 2.296282 c +14.462607 2.976503 14.291677 3.581712 13.949817 4.111909 c +13.607956 4.642107 13.149544 5.056827 12.574580 5.356069 c +11.999616 5.655311 11.356059 5.804932 10.643909 5.804932 c +9.859492 5.804932 9.163199 5.604929 8.555029 5.204925 c +7.946858 4.804920 7.507379 4.239113 7.236590 3.507505 c +6.965801 4.239113 6.524559 4.804920 5.912865 5.204925 c +5.301171 5.604929 4.603175 5.804932 3.818878 5.804932 c +3.113536 5.804932 2.471682 5.655311 1.893314 5.356069 c +1.314945 5.056827 0.854801 4.642107 0.512880 4.111909 c +0.170960 3.581712 0.000000 2.976503 0.000000 2.296282 c +0.000000 1.832761 0.089646 1.406632 0.268938 1.017893 c +0.448229 0.629154 0.693875 0.289877 1.005873 0.000062 c +6.271209 0.000062 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 4.399414 3.928711 cm +0.000000 0.000000 0.000000 scn +9.297392 16.184326 m +9.297392 13.233196 l +9.297392 13.205330 9.304155 13.177880 9.317101 13.153204 c +9.361280 13.069000 9.465354 13.036554 9.549558 13.080732 c +10.010468 13.322554 l +10.210828 13.427675 10.450042 13.427675 10.650402 13.322554 c +11.111313 13.080732 l +11.135988 13.067786 11.163439 13.061022 11.191304 13.061022 c +11.286394 13.061022 11.363479 13.138107 11.363479 13.233196 c +11.363479 16.184326 l +19.283478 16.184326 l +20.044191 16.184326 20.660870 15.567647 20.660870 14.806935 c +20.660870 12.052153 l +20.660870 11.291441 20.044191 10.674761 19.283478 10.674761 c +18.594784 10.674761 l +18.594933 10.657510 l +18.539255 10.668822 18.481627 10.674761 18.422609 10.674761 c +15.055919 10.675272 l +15.696681 10.485103 16.147873 9.883333 16.129395 9.195256 c +16.125700 9.122501 l +16.121429 9.065297 16.113916 9.008687 16.103285 8.952910 c +18.422609 8.953022 l +18.481627 8.953022 18.539255 8.958961 18.594933 8.970274 c +18.594784 2.754762 l +18.594784 1.233337 17.361425 -0.000021 15.840000 -0.000021 c +11.363479 -0.000021 l +11.363479 2.804149 l +11.363479 3.021412 11.261068 3.224681 11.089363 3.354087 c +11.040842 3.387503 l +10.513464 3.718435 l +10.401549 3.788662 10.259320 3.788662 10.147406 3.718435 c +9.620029 3.387503 l +9.419268 3.261524 9.297392 3.041163 9.297392 2.804149 c +9.297392 -0.000021 l +4.820869 -0.000021 l +3.299445 -0.000021 2.066087 1.233337 2.066087 2.754762 c +2.066087 8.953022 l +4.572276 8.953012 l +4.497755 9.343477 4.577706 9.761106 4.823255 10.106071 c +4.874576 10.174066 l +5.066670 10.414354 5.326026 10.587791 5.615777 10.674929 c +1.893913 10.674761 l +1.377391 10.674761 l +0.616679 10.674761 0.000000 11.291441 0.000000 12.052153 c +0.000000 14.806935 l +-0.000000 15.567647 0.616679 16.184326 1.377391 16.184326 c +9.297392 16.184326 l +h +9.899277 12.092336 m +8.916199 9.828533 l +8.846663 9.668410 8.694118 9.560011 8.520034 9.547014 c +6.013629 9.359897 l +5.883370 9.350172 5.763032 9.286686 5.681469 9.184660 c +5.519364 8.981886 5.552333 8.686092 5.755107 8.523988 c +6.579829 7.864677 l +6.972448 7.550803 7.477788 7.414086 7.975103 7.487194 c +10.210711 7.815840 l +10.304756 7.829664 10.396884 7.780811 10.438204 7.695207 c +10.490416 7.587036 10.445051 7.457020 10.336881 7.404807 c +8.412390 6.475896 l +7.984262 6.269247 7.667592 5.886102 7.545246 5.426723 c +7.218948 4.201552 l +7.186707 4.080493 7.204124 3.951572 7.267334 3.843410 c +7.398319 3.619270 7.686204 3.543755 7.910343 3.674740 c +10.093266 4.950423 l +10.239786 5.036049 10.421084 5.036049 10.567604 4.950423 c +12.768568 3.664197 l +12.874888 3.602064 13.001345 3.584135 13.120747 3.614265 c +13.372462 3.677783 13.525026 3.933331 13.461508 4.185046 c +12.875005 6.509303 l +12.830278 6.686551 12.892370 6.873654 13.034194 6.988995 c +14.923353 8.525375 l +15.023630 8.606926 15.085902 8.726170 15.095525 8.855062 c +15.114852 9.113949 14.920650 9.339484 14.661765 9.358811 c +12.140836 9.547014 l +11.966751 9.560011 11.814207 9.668410 11.744673 9.828533 c +10.761594 12.092336 l +10.714136 12.201618 10.626952 12.288803 10.517670 12.336260 c +10.279548 12.439667 10.002684 12.330458 9.899277 12.092336 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 4957 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000005047 00000 n +0000005070 00000 n +0000005243 00000 n +0000005317 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +5376 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessageTags.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessageTags.imageset/Contents.json new file mode 100644 index 0000000000..1e34c1570c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessageTags.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tag_30 (2).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessageTags.imageset/tag_30 (2).pdf b/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessageTags.imageset/tag_30 (2).pdf new file mode 100644 index 0000000000..a815495248 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessageTags.imageset/tag_30 (2).pdf @@ -0,0 +1,164 @@ +%PDF-1.7 + +1 0 obj + << /Type /XObject + /Length 2 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 30.000000 30.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 5.406738 8.000000 cm +0.000000 0.000000 0.000000 scn +16.973253 11.854660 m +19.015503 9.069774 l +19.918947 7.837805 19.918947 6.162195 19.015503 4.930227 c +16.973253 2.145341 l +15.984283 0.796747 14.411980 0.000000 12.739626 0.000000 c +3.500002 0.000000 l +1.567005 0.000000 0.000000 1.567004 0.000000 3.500001 c +0.000000 10.500000 l +0.000000 12.432997 1.567003 14.000000 3.500000 14.000000 c +12.739625 14.000000 l +14.411979 14.000000 15.984283 13.203254 16.973253 11.854660 c +h +14.350163 5.250000 m +15.316662 5.250000 16.100163 6.033502 16.100163 7.000000 c +16.100163 7.966498 15.316662 8.750000 14.350163 8.750000 c +13.383665 8.750000 12.600163 7.966498 12.600163 7.000000 c +12.600163 6.033502 13.383665 5.250000 14.350163 5.250000 c +h +f* +n +Q + +endstream +endobj + +2 0 obj + 809 +endobj + +3 0 obj + << /Type /XObject + /Length 4 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 30.000000 30.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +0.000000 0.000000 0.000000 scn +0.000000 18.799999 m +0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c +1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c +5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c +18.799999 30.000000 l +22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c +27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c +30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c +30.000000 11.200001 l +30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c +28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c +24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c +11.200000 0.000000 l +7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c +2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c +0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c +0.000000 18.799999 l +h +f +n +Q + +endstream +endobj + +4 0 obj + 944 +endobj + +5 0 obj + << /XObject << /X1 1 0 R >> + /ExtGState << /E1 << /SMask << /Type /Mask + /G 3 0 R + /S /Alpha + >> + /Type /ExtGState + >> >> + >> +endobj + +6 0 obj + << /Length 7 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +/E1 gs +/X1 Do +Q + +endstream +endobj + +7 0 obj + 46 +endobj + +8 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 5 0 R + /Contents 6 0 R + /Parent 9 0 R + >> +endobj + +9 0 obj + << /Kids [ 8 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +10 0 obj + << /Pages 9 0 R + /Type /Catalog + >> +endobj + +xref +0 11 +0000000000 65535 f +0000000010 00000 n +0000001067 00000 n +0000001089 00000 n +0000002281 00000 n +0000002303 00000 n +0000002601 00000 n +0000002703 00000 n +0000002724 00000 n +0000002897 00000 n +0000002971 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 10 0 R + /Size 11 +>> +startxref +3031 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stories/Quality.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stories/Quality.imageset/Contents.json new file mode 100644 index 0000000000..8f8aeb69e9 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stories/Quality.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "hd_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stories/Quality.imageset/hd_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stories/Quality.imageset/hd_30.pdf new file mode 100644 index 0000000000..fd38ff5908 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stories/Quality.imageset/hd_30.pdf @@ -0,0 +1,161 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 3.084961 4.960083 cm +0.000000 0.000000 0.000000 scn +6.665000 20.079956 m +6.635606 20.079956 l +6.635600 20.079956 l +5.610404 20.079960 4.800312 20.079964 4.147855 20.026657 c +3.481411 19.972206 2.921088 19.858839 2.410632 19.598749 c +1.579897 19.175468 0.904487 18.500059 0.481207 17.669323 c +0.221116 17.158869 0.107750 16.598545 0.053300 15.932100 c +-0.000008 15.279644 -0.000005 14.469551 0.000000 13.444355 c +0.000000 13.444349 l +0.000000 13.414955 l +0.000000 6.664956 l +0.000000 6.635563 l +-0.000005 5.610363 -0.000008 4.800270 0.053300 4.147811 c +0.107750 3.481365 0.221116 2.921043 0.481207 2.410587 c +0.904487 1.579851 1.579897 0.904442 2.410632 0.481161 c +2.921088 0.221071 3.481411 0.107704 4.147856 0.053255 c +4.800318 -0.000055 5.610417 -0.000051 6.635624 -0.000046 c +6.665001 -0.000046 l +17.165001 -0.000046 l +17.194378 -0.000046 l +18.219585 -0.000051 19.029684 -0.000055 19.682146 0.053255 c +20.348591 0.107704 20.908913 0.221071 21.419369 0.481161 c +22.250105 0.904442 22.925514 1.579851 23.348795 2.410587 c +23.608885 2.921043 23.722252 3.481365 23.776701 4.147811 c +23.830011 4.800273 23.830008 5.610373 23.830002 6.635580 c +23.830002 6.664956 l +23.830002 13.414955 l +23.830002 13.444332 l +23.830008 14.469540 23.830011 15.279638 23.776701 15.932100 c +23.722252 16.598545 23.608885 17.158869 23.348795 17.669323 c +22.925514 18.500059 22.250105 19.175468 21.419369 19.598749 c +20.908913 19.858839 20.348591 19.972206 19.682146 20.026657 c +19.029688 20.079964 18.219595 20.079960 17.194395 20.079956 c +17.165001 20.079956 l +6.665000 20.079956 l +h +3.014440 18.413712 m +3.306153 18.562346 3.671964 18.653343 4.256160 18.701073 c +4.848119 18.749439 5.603929 18.749956 6.665000 18.749956 c +17.165001 18.749956 l +18.226072 18.749956 18.981882 18.749439 19.573841 18.701073 c +20.158037 18.653343 20.523848 18.562346 20.815561 18.413712 c +21.396042 18.117941 21.867987 17.645996 22.163755 17.065517 c +22.312391 16.773804 22.403387 16.407993 22.451118 15.823796 c +22.499483 15.231836 22.500000 14.476027 22.500000 13.414955 c +22.500000 6.664956 l +22.500000 5.603885 22.499483 4.848075 22.451118 4.256116 c +22.403387 3.671919 22.312391 3.306108 22.163755 3.014395 c +21.867987 2.433914 21.396042 1.961969 20.815561 1.666201 c +20.523848 1.517565 20.158037 1.426569 19.573841 1.378838 c +18.981882 1.330473 18.226072 1.329956 17.165001 1.329956 c +6.665001 1.329956 l +5.603929 1.329956 4.848119 1.330473 4.256160 1.378838 c +3.671964 1.426569 3.306153 1.517565 3.014440 1.666201 c +2.433959 1.961969 1.962015 2.433914 1.666245 3.014395 c +1.517610 3.306108 1.426613 3.671919 1.378883 4.256116 c +1.330518 4.848075 1.330000 5.603885 1.330000 6.664956 c +1.330000 13.414955 l +1.330000 14.476027 1.330518 15.231836 1.378883 15.823796 c +1.426613 16.407993 1.517610 16.773804 1.666245 17.065517 c +1.962015 17.645996 2.433959 18.117941 3.014440 18.413712 c +h +3.975623 6.325036 m +3.975623 5.885583 4.231971 5.617028 4.659217 5.617028 c +5.080359 5.617028 5.342811 5.885583 5.342811 6.325036 c +5.342811 9.614831 l +9.865516 9.614831 l +9.865516 6.325036 l +9.865516 5.885583 10.121863 5.617028 10.549109 5.617028 c +10.970252 5.617028 11.232703 5.885583 11.232703 6.325036 c +11.232703 13.911706 l +11.232703 14.351159 10.970252 14.619714 10.549109 14.619714 c +10.121863 14.619714 9.865516 14.351159 9.865516 13.911706 c +9.865516 10.798913 l +5.342811 10.798913 l +5.342811 13.911706 l +5.342811 14.351159 5.080359 14.619714 4.659217 14.619714 c +4.231971 14.619714 3.975623 14.351159 3.975623 13.911706 c +3.975623 6.325036 l +h +12.752635 6.422692 m +12.752635 5.983239 13.008983 5.714684 13.436229 5.714684 c +15.859324 5.714684 l +18.483837 5.714684 20.028027 7.350427 20.028027 10.133630 c +20.028027 12.910729 18.477734 14.522058 15.859324 14.522058 c +13.436229 14.522058 l +13.008983 14.522058 12.752635 14.253503 12.752635 13.814050 c +12.752635 6.422692 l +h +15.731151 6.898767 m +14.119823 6.898767 l +14.119823 13.344079 l +15.731151 13.344079 l +17.592724 13.344079 18.630322 12.196618 18.630322 10.121423 c +18.630322 8.040124 17.598827 6.898767 15.731151 6.898767 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 4117 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000004207 00000 n +0000004230 00000 n +0000004403 00000 n +0000004477 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +4536 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index de6a40ac1a..b4391721f7 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4401,18 +4401,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self?.canReadHistory.set(true) } self.presentInGlobalOverlay(contextController) - }, openGroupBoostInfo: { [weak self] userId in + }, openGroupBoostInfo: { [weak self] userId, count in guard let self, let peerId = self.chatLocation.peerId else { return } - let boostController = PremiumBoostLevelsScreen( - context: self.context, - peerId: peerId, - mode: .user(mode: .groupPeer(userId)), - status: ChannelBoostStatus(level: 0, boosts: 0, giftBoosts: nil, currentLevelBoosts: 0, nextLevelBoosts: 10, premiumAudience: nil, url: "", prepaidGiveaways: [], boostedByMe: false), - myBoostStatus: nil - ) - self.push(boostController) + let _ = combineLatest(queue: Queue.mainQueue(), + context.engine.peers.getChannelBoostStatus(peerId: peerId), + context.engine.peers.getMyBoostStatus() + ).startStandalone(next: { [weak self] boostStatus, myBoostStatus in + guard let self, let boostStatus, let myBoostStatus else { + return + } + let boostController = PremiumBoostLevelsScreen( + context: self.context, + peerId: peerId, + mode: .user(mode: .groupPeer(userId, count)), + status: boostStatus, + myBoostStatus: myBoostStatus + ) + self.push(boostController) + }) }, requestMessageUpdate: { [weak self] id, scroll in if let self { self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll) diff --git a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift index 8cad60b219..8d556fb888 100644 --- a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift +++ b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift @@ -151,7 +151,6 @@ final class ManagedAudioRecorderContext { private let beganWithTone: (Bool) -> Void private var paused = true - private var manuallyPaused = false private let queue: Queue private let mediaManager: MediaManager @@ -403,7 +402,7 @@ final class ManagedAudioRecorderContext { if self.audioSessionDisposable == nil { let queue = self.queue - self.audioSessionDisposable = self.mediaManager.audioSession.push(audioSessionType: .record(speaker: self.beginWithTone, withOthers: false), activate: { [weak self] state in + self.audioSessionDisposable = self.mediaManager.audioSession.push(audioSessionType: .record(speaker: self.beginWithTone, video: false, withOthers: false), activate: { [weak self] state in queue.async { if let strongSelf = self, !strongSelf.paused { strongSelf.hasAudioSession = true @@ -415,11 +414,9 @@ final class ManagedAudioRecorderContext { return Signal { subscriber in queue.async { if let strongSelf = self { - if !strongSelf.manuallyPaused { - strongSelf.hasAudioSession = false - strongSelf.stop() - strongSelf.recordingState.set(.stopped) - } + strongSelf.hasAudioSession = false + strongSelf.stop() + strongSelf.recordingState.set(.stopped) subscriber.putCompletion() } } @@ -454,17 +451,13 @@ final class ManagedAudioRecorderContext { func pause() { assert(self.queue.isCurrent()) - self.manuallyPaused = true + self.stop() } func resume() { assert(self.queue.isCurrent()) - if self.manuallyPaused { - self.manuallyPaused = false - } else if self.paused { - self.start() - } + self.start() } func stop() { @@ -508,7 +501,7 @@ final class ManagedAudioRecorderContext { free(buffer.mData) } - if !self.processSamples || self.manuallyPaused { + if !self.processSamples { return } diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index d2cf237a25..2c35372a74 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -892,6 +892,9 @@ func openResolvedUrlImpl( mode: .user(mode: isCurrent ? .current : .external), status: status, myBoostStatus: myBoostStatus, + openPeer: { peer in + openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)) + }, forceDark: forceDark ) controller.disposed = { @@ -902,33 +905,6 @@ func openResolvedUrlImpl( if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext { storyProgressPauseContext.update(controller) } - -// PremiumBoostScreen( -// context: context, -// contentContext: contentContext, -// peerId: peerId, -// isCurrent: isCurrent, -// status: status, -// myBoostStatus: myBoostStatus, -// forceDark: forceDark, -// openPeer: { peer in -// openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)) -// }, -// presentController: { [weak navigationController] c in -// (navigationController?.viewControllers.last as? ViewController)?.present(c, in: .window(.root)) -// }, -// pushController: { [weak navigationController] c in -// navigationController?.pushViewController(c) -// -// if c is PremiumLimitScreen { -// if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext { -// storyProgressPauseContext.update(c) -// } -// } -// }, dismissed: { -// dismissedImpl?() -// } -// ) case let .premiumGiftCode(slug): var forceDark = false if let updatedPresentationData, updatedPresentationData.initial.theme.overallDarkAppearance { diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 98caec8e55..88ddf8a2ae 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -172,7 +172,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu }, displayGiveawayParticipationStatus: { _ in }, openPremiumStatusInfo: { _, _, _, _ in }, openRecommendedChannelContextMenu: { _, _, _ in - }, openGroupBoostInfo: { _ in + }, openGroupBoostInfo: { _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index ba589e1379..e351dfa31a 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1749,7 +1749,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, displayGiveawayParticipationStatus: { _ in }, openPremiumStatusInfo: { _, _, _, _ in }, openRecommendedChannelContextMenu: { _, _, _ in - }, openGroupBoostInfo: { _ in + }, openGroupBoostInfo: { _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { @@ -1939,6 +1939,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSource = .storiesExpirationDurations case .storiesSuggestedReactions: mappedSource = .storiesSuggestedReactions + case .storiesHigherQuality: + mappedSource = .storiesHigherQuality case let .channelBoost(peerId): mappedSource = .channelBoost(peerId) case .nameColor: @@ -1951,6 +1953,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSource = .presence case .readTime: mappedSource = .readTime + case .messageTags: + mappedSource = .messageTags } let controller = PremiumIntroScreen(context: context, modal: modal, source: mappedSource, forceDark: forceDark) controller.wasDismissed = dismissed @@ -1994,6 +1998,12 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSubject = .colors case .wallpapers: mappedSubject = .wallpapers + case .messageTags: + mappedSubject = .messageTags + case .lastSeen: + mappedSubject = .lastSeen + case .messagePrivacy: + mappedSubject = .messagePrivacy } return PremiumDemoScreen(context: context, subject: mappedSubject, action: action) } diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index 564c632f93..6b73742cdc 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit 564c632f9368409870631d3cef75a7fc4070d45b +Subproject commit 6b73742cdc140c46a1ab1b8e3390354a9738e429 diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index ff45af9c73..d3f5cc434d 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -248,7 +248,8 @@ public func generateWebAppThemeParams(_ presentationTheme: PresentationTheme) -> "section_bg_color": Int32(bitPattern: presentationTheme.list.itemBlocksBackgroundColor.rgb), "section_header_text_color": Int32(bitPattern: presentationTheme.list.freeTextColor.rgb), "subtitle_text_color": Int32(bitPattern: presentationTheme.list.itemSecondaryTextColor.rgb), - "destructive_text_color": Int32(bitPattern: presentationTheme.list.itemDestructiveColor.rgb) + "destructive_text_color": Int32(bitPattern: presentationTheme.list.itemDestructiveColor.rgb), + "section_separator_color": Int32(bitPattern: presentationTheme.list.itemBlocksSeparatorColor.rgb) ] }