From 5e92cc90e4ff3e08eeeb4f815993c04a97a6ee3e Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 10 Feb 2024 16:21:17 +0400 Subject: [PATCH 01/14] Group boosts --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 +- .../Sources/Node/ChatListItemStrings.swift | 6 +- .../Sources/CreateGiveawayController.swift | 2 +- .../Sources/PremiumLimitScreen.swift | 74 ++++++++++--------- .../Sources/BoostHeaderItem.swift | 43 +++++++---- .../Sources/ChannelStatsController.swift | 4 +- .../PeerInfoScreen/Sources/PeerInfoData.swift | 56 +++++++++++--- .../Sources/PeerInfoScreen.swift | 4 +- .../Sources/ChannelAppearanceScreen.swift | 2 +- 9 files changed, 126 insertions(+), 67 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 5c8659c02a..662c75ec4d 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -11013,7 +11013,7 @@ Sorry for the inconvenience."; "GroupBoost.EnableStoriesText" = "Your group needs %1$@ to enable posting stories."; "GroupBoost.IncreaseLimitText" = "Your group needs %1$@ to post %2$@."; -"BoostGift.Group.Description" = "Get more boosts for your group by gifting\nPremium to your subscribers."; +"BoostGift.Group.Description" = "Get more boosts for your group by gifting\nPremium to your members."; "BoostGift.Group.AllMembers" = "All members"; "BoostGift.Group.OnlyNewMembers" = "Only new members"; diff --git a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift index 2951299a18..bd08ffac40 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift @@ -309,7 +309,11 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: if let forwardInfo = message.forwardInfo, let author = forwardInfo.author { messageText = strings.Message_GiveawayStartedOther(EnginePeer(author).compactDisplayTitle).string } else { - messageText = isPeerGroup ? strings.Message_GiveawayStartedGroup : strings.Message_GiveawayStarted + if let author = message.author, case let .channel(channel) = author, case .group = channel.info { + messageText = strings.Message_GiveawayStartedGroup + } else { + messageText = strings.Message_GiveawayStarted + } } case let results as TelegramMediaGiveawayResults: if results.winnersCount == 0 { diff --git a/submodules/PremiumUI/Sources/CreateGiveawayController.swift b/submodules/PremiumUI/Sources/CreateGiveawayController.swift index d07a03aa9e..846d11ad8f 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayController.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayController.swift @@ -1210,7 +1210,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio navigationController.setViewControllers(controllers, animated: true) let title = presentationData.strings.BoostGift_GiveawayCreated_Title - let text = presentationData.strings.BoostGift_GiveawayCreated_Text + let text = isGroup ? presentationData.strings.BoostGift_Group_GiveawayCreated_Text : presentationData.strings.BoostGift_GiveawayCreated_Text let tooltipController = UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: title, text: text, customUndoText: nil, timeout: nil, linkAction: { [weak navigationController] _ in let statsController = context.sharedContext.makeChannelStatsController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, boosts: true, boostStatus: nil) diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index 2b5ae9062f..b25ef82a2a 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -252,49 +252,53 @@ public class PremiumLimitDisplayComponent: Component { rotationAngle = 0.26 } + let to: CGFloat = self.badgeView.center.x + let positionAnimation = CABasicAnimation(keyPath: "position.x") positionAnimation.fromValue = NSValue(cgPoint: CGPoint(x: from ?? 0.0, y: 0.0)) - positionAnimation.toValue = NSValue(cgPoint: self.badgeView.center) + positionAnimation.toValue = NSValue(cgPoint: CGPoint(x: to, y: 0.0)) positionAnimation.duration = 0.5 positionAnimation.fillMode = .forwards positionAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) self.badgeView.layer.add(positionAnimation, forKey: "appearance1") - let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z") - rotateAnimation.fromValue = 0.0 as NSNumber - rotateAnimation.toValue = -rotationAngle as NSNumber - rotateAnimation.duration = 0.15 - rotateAnimation.fillMode = .forwards - rotateAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) - rotateAnimation.isRemovedOnCompletion = false - self.badgeView.layer.add(rotateAnimation, forKey: "appearance2") - - Queue.mainQueue().after(0.5, { - let bounceAnimation = CABasicAnimation(keyPath: "transform.rotation.z") - bounceAnimation.fromValue = -rotationAngle as NSNumber - bounceAnimation.toValue = 0.04 as NSNumber - bounceAnimation.duration = 0.2 - bounceAnimation.fillMode = .forwards - bounceAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) - bounceAnimation.isRemovedOnCompletion = false - self.badgeView.layer.add(bounceAnimation, forKey: "appearance3") - self.badgeView.layer.removeAnimation(forKey: "appearance2") + if from != to { + let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + rotateAnimation.fromValue = 0.0 as NSNumber + rotateAnimation.toValue = -rotationAngle as NSNumber + rotateAnimation.duration = 0.15 + rotateAnimation.fillMode = .forwards + rotateAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) + rotateAnimation.isRemovedOnCompletion = false + self.badgeView.layer.add(rotateAnimation, forKey: "appearance2") - if !self.badgeView.isHidden { - self.hapticFeedback.impact(.light) - } - - Queue.mainQueue().after(0.2) { - let returnAnimation = CABasicAnimation(keyPath: "transform.rotation.z") - returnAnimation.fromValue = 0.04 as NSNumber - returnAnimation.toValue = 0.0 as NSNumber - returnAnimation.duration = 0.15 - returnAnimation.fillMode = .forwards - returnAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) - self.badgeView.layer.add(returnAnimation, forKey: "appearance4") - self.badgeView.layer.removeAnimation(forKey: "appearance3") - } - }) + Queue.mainQueue().after(0.5, { + let bounceAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + bounceAnimation.fromValue = -rotationAngle as NSNumber + bounceAnimation.toValue = 0.04 as NSNumber + bounceAnimation.duration = 0.2 + bounceAnimation.fillMode = .forwards + bounceAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) + bounceAnimation.isRemovedOnCompletion = false + self.badgeView.layer.add(bounceAnimation, forKey: "appearance3") + self.badgeView.layer.removeAnimation(forKey: "appearance2") + + if !self.badgeView.isHidden { + self.hapticFeedback.impact(.light) + } + + Queue.mainQueue().after(0.2) { + let returnAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + returnAnimation.fromValue = 0.04 as NSNumber + returnAnimation.toValue = 0.0 as NSNumber + returnAnimation.duration = 0.15 + returnAnimation.fillMode = .forwards + returnAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) + self.badgeView.layer.add(returnAnimation, forKey: "appearance4") + self.badgeView.layer.removeAnimation(forKey: "appearance3") + } + }) + } if from == nil { self.badgeView.alpha = 1.0 diff --git a/submodules/StatisticsUI/Sources/BoostHeaderItem.swift b/submodules/StatisticsUI/Sources/BoostHeaderItem.swift index 599188fa13..722a0f0384 100644 --- a/submodules/StatisticsUI/Sources/BoostHeaderItem.swift +++ b/submodules/StatisticsUI/Sources/BoostHeaderItem.swift @@ -19,7 +19,7 @@ final class BoostHeaderItem: ItemListControllerHeaderItem { let context: AccountContext let theme: PresentationTheme let strings: PresentationStrings - let status: ChannelBoostStatus + let status: ChannelBoostStatus? let title: String let text: String let openBoost: () -> Void @@ -28,7 +28,7 @@ final class BoostHeaderItem: ItemListControllerHeaderItem { let back: () -> Void let updateStatusBar: (StatusBarStyle) -> 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, updateStatusBar: @escaping (StatusBarStyle) -> 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, updateStatusBar: @escaping (StatusBarStyle) -> Void) { self.context = context self.theme = theme self.strings = strings @@ -44,7 +44,7 @@ final class BoostHeaderItem: ItemListControllerHeaderItem { func isEqual(to: ItemListControllerHeaderItem) -> Bool { if let item = to as? BoostHeaderItem { - return self.theme === item.theme && self.title == item.title && self.text == item.text + return self.theme === item.theme && self.title == item.title && self.text == item.text && self.status == item.status } else { return false } @@ -78,7 +78,7 @@ final class BoostHeaderItemNode: ItemListControllerHeaderItemNode { didSet { self.updateItem() if let layout = self.validLayout { - let _ = self.updateLayout(layout: layout, transition: .immediate) + let _ = self.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut)) } } } @@ -197,6 +197,7 @@ final class BoostHeaderItemNode: ItemListControllerHeaderItemNode { strings: self.item.strings, text: self.item.text, status: self.item.status, + insets: layout.safeInsets, openBoost: self.item.openBoost, createGiveaway: self.item.createGiveaway, openFeatures: self.item.openFeatures @@ -205,7 +206,7 @@ final class BoostHeaderItemNode: ItemListControllerHeaderItemNode { if let hostView = self.hostView { let size = hostView.update( - transition: .immediate, + transition: Transition(transition), component: component, environment: {}, containerSize: containerSize @@ -220,7 +221,7 @@ final class BoostHeaderItemNode: ItemListControllerHeaderItemNode { 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.backButton.frame = CGRect(origin: CGPoint(x: layout.safeInsets.left + 16.0, y: statusBarHeight), size: backSize) self.component = component self.validLayout = layout @@ -244,7 +245,8 @@ final class BoostHeaderItemNode: ItemListControllerHeaderItemNode { private final class BoostHeaderComponent: CombinedComponent { let strings: PresentationStrings let text: String - let status: ChannelBoostStatus + let status: ChannelBoostStatus? + let insets: UIEdgeInsets let openBoost: () -> Void let createGiveaway: () -> Void let openFeatures: () -> Void @@ -252,7 +254,8 @@ private final class BoostHeaderComponent: CombinedComponent { public init( strings: PresentationStrings, text: String, - status: ChannelBoostStatus, + status: ChannelBoostStatus?, + insets: UIEdgeInsets, openBoost: @escaping () -> Void, createGiveaway: @escaping () -> Void, openFeatures: @escaping () -> Void @@ -260,6 +263,7 @@ private final class BoostHeaderComponent: CombinedComponent { self.strings = strings self.text = text self.status = status + self.insets = insets self.openBoost = openBoost self.createGiveaway = createGiveaway self.openFeatures = openFeatures @@ -275,6 +279,9 @@ private final class BoostHeaderComponent: CombinedComponent { if lhs.status != rhs.status { return false } + if lhs.insets != rhs.insets { + return false + } return true } @@ -290,8 +297,9 @@ private final class BoostHeaderComponent: CombinedComponent { return { context in let size = context.availableSize - let sideInset: CGFloat = 16.0 + let component = context.component + let sideInset: CGFloat = 16.0 + component.insets.left let background = background.update( component: PremiumGradientBackgroundComponent( @@ -321,12 +329,19 @@ private final class BoostHeaderComponent: CombinedComponent { .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0 + 10.0)) ) - let level = component.status.level + let boosts: Int + let level = component.status?.level ?? 0 let position: CGFloat - if let nextLevelBoosts = component.status.nextLevelBoosts { - position = CGFloat(component.status.boosts - component.status.currentLevelBoosts) / CGFloat(nextLevelBoosts - component.status.currentLevelBoosts) + if let status = component.status { + if let nextLevelBoosts = status.nextLevelBoosts { + position = CGFloat(status.boosts - status.currentLevelBoosts) / CGFloat(nextLevelBoosts - status.currentLevelBoosts) + } else { + position = 1.0 + } + boosts = status.boosts } else { - position = 1.0 + boosts = 0 + position = 0.0 } let inactiveText = component.strings.ChannelBoost_Level("\(level)").string @@ -343,7 +358,7 @@ private final class BoostHeaderComponent: CombinedComponent { activeValue: activeText, activeTitleColor: UIColor(rgb: 0x6f8fff), badgeIconName: "Premium/Boost", - badgeText: "\(component.status.boosts)", + badgeText: "\(boosts)", badgePosition: position, badgeGraphPosition: position, invertProgress: true, diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 54c3b09cb8..62637353f2 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -1307,9 +1307,9 @@ public func channelStatsController(context: AccountContext, updatedPresentationD var headerItem: BoostHeaderItem? var leftNavigationButton: ItemListNavigationButton? var boostsOnly = false - if isGroup, section == .boosts, let boostStatus { + if isGroup, section == .boosts { title = .text("") - headerItem = BoostHeaderItem(context: context, theme: presentationData.theme, strings: presentationData.strings, status: boostStatus, title: presentationData.strings.GroupBoost_Title, text: presentationData.strings.GroupBoost_Info, openBoost: { + headerItem = BoostHeaderItem(context: context, theme: presentationData.theme, strings: presentationData.strings, status: boostData, title: presentationData.strings.GroupBoost_Title, text: presentationData.strings.GroupBoost_Info, openBoost: { openBoostImpl?(false) }, createGiveaway: { arguments.openGifts() diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index e1e7410355..77b84a1a44 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -208,6 +208,7 @@ final class PeerInfoScreenData { let isPowerSavingEnabled: Bool? let accountIsPremium: Bool let hasSavedMessageTags: Bool + let isPremiumRequiredForStoryPosting: Bool let _isContact: Bool var forceIsContact: Bool = false @@ -244,7 +245,8 @@ final class PeerInfoScreenData { appConfiguration: AppConfiguration?, isPowerSavingEnabled: Bool?, accountIsPremium: Bool, - hasSavedMessageTags: Bool + hasSavedMessageTags: Bool, + isPremiumRequiredForStoryPosting: Bool ) { self.peer = peer self.chatPeer = chatPeer @@ -270,6 +272,7 @@ final class PeerInfoScreenData { self.isPowerSavingEnabled = isPowerSavingEnabled self.accountIsPremium = accountIsPremium self.hasSavedMessageTags = hasSavedMessageTags + self.isPremiumRequiredForStoryPosting = isPremiumRequiredForStoryPosting } } @@ -666,7 +669,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, appConfiguration: appConfiguration, isPowerSavingEnabled: isPowerSavingEnabled, accountIsPremium: peer?.isPremium ?? false, - hasSavedMessageTags: false + hasSavedMessageTags: false, + isPremiumRequiredForStoryPosting: true ) } } @@ -702,7 +706,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen appConfiguration: nil, isPowerSavingEnabled: nil, accountIsPremium: false, - hasSavedMessageTags: false + hasSavedMessageTags: false, + isPremiumRequiredForStoryPosting: true )) case let .user(userPeerId, secretChatId, kind): let groupsInCommon: GroupsInCommonContext? @@ -973,7 +978,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen appConfiguration: nil, isPowerSavingEnabled: nil, accountIsPremium: accountIsPremium, - hasSavedMessageTags: hasSavedMessageTags + hasSavedMessageTags: hasSavedMessageTags, + isPremiumRequiredForStoryPosting: false ) } case .channel: @@ -1047,6 +1053,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen hasSavedMessageTags = .single(false) } + let isPremiumRequiredForStoryPosting: Signal = isPremiumRequiredForStoryPosting(context: context) + return combineLatest( context.account.viewTracker.peerView(peerId, updateData: true), peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), @@ -1061,9 +1069,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen context.engine.peers.recommendedChannels(peerId: peerId), hasSavedMessages, hasSavedMessagesChats, - hasSavedMessageTags + hasSavedMessageTags, + isPremiumRequiredForStoryPosting ) - |> map { peerView, availablePanes, globalNotificationSettings, status, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, accountIsPremium, recommendedChannels, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags -> PeerInfoScreenData in + |> map { peerView, availablePanes, globalNotificationSettings, status, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, accountIsPremium, recommendedChannels, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags, isPremiumRequiredForStoryPosting -> PeerInfoScreenData in var availablePanes = availablePanes if let hasStories { if hasStories { @@ -1138,7 +1147,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen appConfiguration: nil, isPowerSavingEnabled: nil, accountIsPremium: accountIsPremium, - hasSavedMessageTags: hasSavedMessageTags + hasSavedMessageTags: hasSavedMessageTags, + isPremiumRequiredForStoryPosting: isPremiumRequiredForStoryPosting ) } case let .group(groupId): @@ -1315,6 +1325,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen hasSavedMessageTags = .single(false) } + let isPremiumRequiredForStoryPosting: Signal = isPremiumRequiredForStoryPosting(context: context) + return combineLatest(queue: .mainQueue(), context.account.viewTracker.peerView(groupId, updateData: true), peerInfoAvailableMediaPanes(context: context, peerId: groupId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), @@ -1331,9 +1343,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen accountIsPremium, hasSavedMessages, hasSavedMessagesChats, - hasSavedMessageTags + hasSavedMessageTags, + isPremiumRequiredForStoryPosting ) - |> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags -> Signal in + |> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags, isPremiumRequiredForStoryPosting -> Signal in var discussionPeer: Peer? if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { discussionPeer = peer @@ -1426,7 +1439,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen appConfiguration: appConfiguration, isPowerSavingEnabled: nil, accountIsPremium: accountIsPremium, - hasSavedMessageTags: hasSavedMessageTags + hasSavedMessageTags: hasSavedMessageTags, + isPremiumRequiredForStoryPosting: isPremiumRequiredForStoryPosting )) } } @@ -1812,3 +1826,25 @@ func peerInfoIsChatMuted(peer: Peer?, peerNotificationSettings: TelegramPeerNoti } return chatIsMuted } + +private var isPremiumRequired: Bool? +private func isPremiumRequiredForStoryPosting(context: AccountContext) -> Signal { + if let isPremiumRequired { + return .single(isPremiumRequired) + } + + return .single(true) + |> then( + context.engine.messages.checkStoriesUploadAvailability(target: .myStories) + |> deliverOnMainQueue + |> map { status -> Bool in + if case .premiumRequired = status { + return true + } else { + return false + } + } |> afterNext { value in + isPremiumRequired = value + } + ) +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 5ee3816edc..311b8a32b9 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -5732,7 +5732,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }))) } - if case .broadcast = channel.info, channel.hasPermission(.editStories) { + if channel.hasPermission(.editStories) { items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_Channel_ArchivedStories, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in @@ -10353,7 +10353,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } else if peerInfoCanEdit(peer: self.data?.peer, chatLocation: self.chatLocation, threadData: self.data?.threadData, cachedData: self.data?.cachedData, isContact: self.data?.isContact) { rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) } - if let data = self.data, data.accountIsPremium, let channel = data.peer as? TelegramChannel, channel.hasPermission(.postStories) { + if let data = self.data, !data.isPremiumRequiredForStoryPosting || data.accountIsPremium, let channel = data.peer as? TelegramChannel, channel.hasPermission(.postStories) { rightNavigationButtons.insert(PeerInfoHeaderNavigationButtonSpec(key: .postStory, isForExpandedView: false), at: 0) } diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift index a326b26096..187fa627ab 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift @@ -1083,7 +1083,7 @@ final class ChannelAppearanceScreenComponent: Component { } self.backButton.updateContentsColor(backgroundColor: scrolledUp ? UIColor(white: 0.0, alpha: 0.1) : .clear, contentsColor: scrolledUp ? .white : environment.theme.rootController.navigationBar.accentTextColor, canBeExpanded: !scrolledUp, transition: .animated(duration: 0.2, curve: .easeInOut)) - self.backButton.frame = CGRect(origin: CGPoint(x: 16.0, y: 54.0), size: backSize) + self.backButton.frame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: environment.navigationHeight - 44.0), size: backSize) if self.backButton.view.superview == nil { if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar { navigationBar.view.addSubview(self.backButton.view) From 2718703ebba5dc9e2fce373172644b4d36a2c61b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 10 Feb 2024 19:15:14 +0400 Subject: [PATCH 02/14] Group boosts --- .../Telegram-iOS/en.lproj/Localizable.strings | 13 ++++ .../Sources/PremiumBoostLevelsScreen.swift | 60 ++++++++----------- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 662c75ec4d..3cc0905202 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10988,6 +10988,8 @@ Sorry for the inconvenience."; "ChannelBoost.MaxLevelReached.Text" = "**%1$@** reached **Level %2$@**."; "ChannelBoost.MoreBoostsNeeded.Boosted.Text" = "%@ needed to unlock new features."; +"ChannelBoost.MoreBoostsNeeded.Boosted.Level.Text" = "This channel reached **%@** and unlocked new features."; +"GroupBoost.MoreBoostsNeeded.Boosted.Level.Text" = "This group reached **%@** and unlocked new features."; "ContactList.Context.Delete" = "Delete Contact"; "ContactList.Context.Select" = "Select"; @@ -11276,3 +11278,14 @@ Sorry for the inconvenience."; "Chat.Giveaway.Info.OtherChannelsAndGroups_1" = "**%@** other listed channel and group"; "Chat.Giveaway.Info.OtherChannelsAndGroups_any" = "**%@** other listed channels and groups"; + +"GroupBoost.MemberBoosted.Times_1" = "**%@** time"; +"GroupBoost.MemberBoosted.Times_any" = "**%@** times"; + +"GroupBoost.MemberBoosted" = "**%1$@** boosted the group %2$@."; +"GroupBoost.MemberBoosted.BoostForBadge" = "Boost **%1$@** to help it unlock new features and get a booster **badge** for your messages."; + +"GroupBoost.BoostToUnrestrict.Times_1" = "**%@** time"; +"GroupBoost.BoostToUnrestrict.Times_any" = "**%@** times"; + +"GroupBoost.BoostToUnrestrict" = "Boost the group %1$@ to remove messaging restrictions. Your boosts will help **%2$@** to unlock new features."; diff --git a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift index 5f5e9cfb7c..bef4d74052 100644 --- a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift @@ -647,33 +647,37 @@ private final class SheetContent: CombinedComponent { } } else { 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): switch mode { case let .groupPeer(_, peerBoostCount): let memberName = state.memberPeer?.compactDisplayTitle ?? "" - //TODO:localize + let timesString = strings.GroupBoost_MemberBoosted_Times(Int32(peerBoostCount)) + let memberString = strings.GroupBoost_MemberBoosted(memberName, timesString).string if myBoostCount > 0 { - if let remaining { + if let remaining, remaining != 0 { let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining)) - textString = "**\(memberName)** boosted the group **\(peerBoostCount)** times. \(strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string)" + textString = "\(memberString) \(strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string)" } else { - textString = "**\(memberName)** boosted the group **\(peerBoostCount)** times." + textString = memberString } } else { - textString = "**\(memberName)** boosted the group **\(peerBoostCount)** times. Boost **\(peerName)** to help it unlock new features and get a booster **badge** for your messages." + textString = "\(memberString) \(strings.GroupBoost_MemberBoosted_BoostForBadge(peerName))" } 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." + let timesString = strings.GroupBoost_BoostToUnrestrict_Times(Int32(unrestrictCount)) + textString = strings.GroupBoost_BoostToUnrestrict(timesString, peerName).string isCurrent = true default: if let remaining { let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining)) if myBoostCount > 0 { - textString = strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string + if remaining == 0 { + textString = isGroup ? strings.GroupBoost_MoreBoostsNeeded_Boosted_Level_Text("\(level)").string : strings.ChannelBoost_MoreBoostsNeeded_Boosted_Level_Text("\(level)").string + } else { + textString = strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string + } } else { textString = strings.ChannelBoost_MoreBoostsNeeded_Text(peerName, boostsString).string } @@ -1612,25 +1616,26 @@ public class PremiumBoostLevelsScreen: ViewController { self.wrappingView.addSubview(self.containerView) self.containerView.addSubview(self.contentView) - if case .user = controller.mode, let status = controller.status { + if case .user = controller.mode { self.containerView.addSubview(self.footerContainerView) self.footerContainerView.addSubview(self.footerView) + } + if let status = controller.status, let myBoostStatus = controller.myBoostStatus { 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) - } + + for boost in myBoostStatus.boosts { + if let boostPeer = boost.peer { + if boostPeer.id == controller.peerId { + myBoostCount += 1 } else { - availableBoosts.append(boost) + occupiedBoosts.append(boost) } + } else { + availableBoosts.append(boost) } } @@ -1868,23 +1873,10 @@ public class PremiumBoostLevelsScreen: ViewController { status: controller.status, boostState: self.boostState, boost: { [weak controller] in - guard let controller, let navigationController = controller.navigationController else { + guard let controller else { return } - - controller.dismiss(animated: true) - - Queue.mainQueue().justDispatch { - 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) - } + controller.node.updateBoostState() }, copyLink: { [weak self, weak controller] link in guard let self else { From 50c186b56685843b7ac3bfa75006f09ea7072bb2 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 10 Feb 2024 20:29:18 +0400 Subject: [PATCH 03/14] Group boosts --- .../TelegramNotices/Sources/Notices.swift | 32 +++++++++ .../TelegramUI/Sources/ChatController.swift | 70 +++++++++++-------- 2 files changed, 72 insertions(+), 30 deletions(-) diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index 4ebdad3bab..627928f5ac 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -232,6 +232,7 @@ private struct ApplicationSpecificNoticeKeys { private static let botGameNoticeNamespace: Int32 = 7 private static let peerInviteRequestsNamespace: Int32 = 8 private static let dismissedPremiumGiftNamespace: Int32 = 9 + private static let groupEmojiPackNamespace: Int32 = 9 static func inlineBotLocationRequestNotice(peerId: PeerId) -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: inlineBotLocationRequestNamespace), key: noticeKey(peerId: peerId, key: 0)) @@ -253,6 +254,10 @@ private struct ApplicationSpecificNoticeKeys { return NoticeEntryKey(namespace: noticeNamespace(namespace: dismissedPremiumGiftNamespace), key: noticeKey(peerId: peerId, key: 0)) } + static func groupEmojiPackNotice(peerId: PeerId) -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: groupEmojiPackNamespace), key: noticeKey(peerId: peerId, key: 0)) + } + static func forcedPasswordSetup() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.secretChatInlineBotUsage.key) } @@ -1582,6 +1587,33 @@ public struct ApplicationSpecificNotice { } } + public static func groupEmojiPackSuggestion(accountManager: AccountManager, peerId: PeerId) -> Signal { + return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.groupEmojiPackNotice(peerId: peerId)) + |> map { view -> Int32 in + if let value = view.value?.get(ApplicationSpecificCounterNotice.self) { + return value.value + } else { + return 0 + } + } + } + + public static func incrementGroupEmojiPackSuggestion(accountManager: AccountManager, peerId: PeerId, count: Int32 = 1) -> Signal { + return accountManager.transaction { transaction -> Int32 in + var currentValue: Int32 = 0 + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.groupEmojiPackNotice(peerId: peerId))?.get(ApplicationSpecificCounterNotice.self) { + currentValue = value.value + } + let previousValue = currentValue + currentValue += count + + if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + transaction.setNotice(ApplicationSpecificNoticeKeys.groupEmojiPackNotice(peerId: peerId), entry) + } + return previousValue + } + } + public static func getSendWhenOnlineTip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Int32 in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.sendWhenOnlineTip())?.get(ApplicationSpecificCounterNotice.self) { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index a26dcec6c6..5519a74203 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -15714,42 +15714,52 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let rect = self.chatDisplayNode.frameForEmojiButton(), self.effectiveNavigationController?.topViewController === self else { return } - guard let emojiPack = (self.peerView?.cachedData as? CachedChannelData)?.emojiPack, let thumbnailFileId = emojiPack.thumbnailFileId else { + guard let peerId = self.chatLocation.peerId, let emojiPack = (self.peerView?.cachedData as? CachedChannelData)?.emojiPack, let thumbnailFileId = emojiPack.thumbnailFileId else { return } - let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [thumbnailFileId]) - |> deliverOnMainQueue).start(next: { [weak self] files in - guard let self, let emojiFile = files.values.first else { + + let _ = (ApplicationSpecificNotice.groupEmojiPackSuggestion(accountManager: self.context.sharedContext.accountManager, peerId: peerId) + |> deliverOnMainQueue).start(next: { [weak self] counter in + guard let self, counter == 0 else { return } - let textFont = Font.regular(self.presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0) - let boldTextFont = Font.bold(self.presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0) - let textColor = UIColor.white - let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in - return nil - }) - - let text = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(self.presentationData.strings.Chat_GroupEmojiTooltip(emojiPack.title).string, attributes: markdownAttributes)) - - let range = (text.string as NSString).range(of: "#") - if range.location != NSNotFound { - text.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: emojiFile.fileId.id, file: emojiFile), range: range) - } - - let tooltipScreen = TooltipScreen( - context: self.context, - account: self.context.account, - sharedContext: self.context.sharedContext, - text: .attributedString(text: text), - location: .point(rect.offsetBy(dx: 0.0, dy: -3.0), .bottom), - displayDuration: .default, - cornerRadius: 10.0, - shouldDismissOnTouch: { _, _ in - return .ignore + let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [thumbnailFileId]) + |> deliverOnMainQueue).start(next: { [weak self] files in + guard let self, let emojiFile = files.values.first else { + return } - ) - self.present(tooltipScreen, in: .current) + + let textFont = Font.regular(self.presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0) + let boldTextFont = Font.bold(self.presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0) + let textColor = UIColor.white + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in + return nil + }) + + let text = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(self.presentationData.strings.Chat_GroupEmojiTooltip(emojiPack.title).string, attributes: markdownAttributes)) + + let range = (text.string as NSString).range(of: "#") + if range.location != NSNotFound { + text.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: emojiFile.fileId.id, file: emojiFile), range: range) + } + + let tooltipScreen = TooltipScreen( + context: self.context, + account: self.context.account, + sharedContext: self.context.sharedContext, + text: .attributedString(text: text), + location: .point(rect.offsetBy(dx: 0.0, dy: -3.0), .bottom), + displayDuration: .default, + cornerRadius: 10.0, + shouldDismissOnTouch: { _, _ in + return .ignore + } + ) + self.present(tooltipScreen, in: .current) + + let _ = ApplicationSpecificNotice.incrementGroupEmojiPackSuggestion(accountManager: self.context.sharedContext.accountManager, peerId: peerId).startStandalone() + }) }) } From 64a49fc6ed485b29f5c7f823dc681fff6344dfbc Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 10 Feb 2024 23:41:41 +0400 Subject: [PATCH 04/14] Group boosts --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 ++ .../Sources/AccountContext.swift | 2 +- .../Sources/Node/ChatListNode.swift | 2 +- .../Sources/PremiumBoostLevelsScreen.swift | 12 +++++++-- .../Sources/PremiumBoostScreen.swift | 2 +- .../Sources/ReplaceBoostScreen.swift | 2 +- .../Sources/ChannelStatsController.swift | 25 +++++++++++++------ .../Sources/ChatEntityKeyboardInputNode.swift | 8 +++++- .../Sources/PeerInfoScreen.swift | 8 ++++-- .../TelegramUI/Sources/ChatController.swift | 2 ++ .../Sources/ChatControllerNode.swift | 11 ++++++++ .../TelegramUI/Sources/OpenResolvedUrl.swift | 2 +- .../Sources/SharedAccountContext.swift | 5 ++-- 13 files changed, 63 insertions(+), 20 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 3cc0905202..745f6064bd 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -11289,3 +11289,5 @@ Sorry for the inconvenience."; "GroupBoost.BoostToUnrestrict.Times_any" = "**%@** times"; "GroupBoost.BoostToUnrestrict" = "Boost the group %1$@ to remove messaging restrictions. Your boosts will help **%2$@** to unlock new features."; + +"Story.InputPlaceholderReplyInGroup" = "Comment Story..."; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 76d13f8575..f7d0186a4f 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -959,7 +959,7 @@ public protocol SharedAccountContext: AnyObject { func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> Void) -> ViewController func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Bool) -> ViewController - func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource) -> ViewController + func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (() -> Void)?) -> ViewController func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 40c8dda432..3eafa32fae 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -1683,7 +1683,7 @@ public final class ChatListNode: ListView { guard let self else { return } - let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .chatList) + let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .chatList, completion: nil) self.push?(controller) }, openActiveSessions: { [weak self] in guard let self else { diff --git a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift index bef4d74052..e721f4eb2f 100644 --- a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift @@ -2209,6 +2209,13 @@ public class PremiumBoostLevelsScreen: ViewController { 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) + |> beforeNext { [weak self] status in + if let self, let status { + Queue.mainQueue().async { + self.controller?.boostStatusUpdated(status) + } + } + } |> 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)) @@ -2353,7 +2360,7 @@ public class PremiumBoostLevelsScreen: ViewController { controller?.dismiss(animated: true, completion: nil) Queue.mainQueue().after(0.4) { - let giftController = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost) + let giftController = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost, completion: nil) navigationController.pushViewController(giftController, animated: true) } } @@ -2424,6 +2431,7 @@ public class PremiumBoostLevelsScreen: ViewController { private var currentLayout: ContainerViewLayout? + public var boostStatusUpdated: (ChannelBoostStatus) -> Void = { _ in } public var disposed: () -> Void = {} public init( @@ -2493,7 +2501,7 @@ public class PremiumBoostLevelsScreen: ViewController { override open func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - + self.node.updateIsVisible(isVisible: false) } diff --git a/submodules/PremiumUI/Sources/PremiumBoostScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostScreen.swift index 8cffe20868..00a0a4caa5 100644 --- a/submodules/PremiumUI/Sources/PremiumBoostScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumBoostScreen.swift @@ -239,7 +239,7 @@ public func PremiumBoostScreen( dismissImpl?() Queue.mainQueue().after(0.4) { - let controller = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost) + let controller = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost, completion: nil) pushController(controller) } }), diff --git a/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift b/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift index 453fdbc7ed..973e4d3fb4 100644 --- a/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift +++ b/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift @@ -880,7 +880,7 @@ public class ReplaceBoostScreen: ViewController { } let navigationController = self.navigationController self.dismiss(animated: true, completion: { - let giftController = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost) + let giftController = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost, completion: nil) navigationController?.pushViewController(giftController, animated: true) }) } diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 62637353f2..50350b227f 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -1046,7 +1046,7 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p return entries } -public func channelStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, section: ChannelStatsSection = .stats, boostStatus: ChannelBoostStatus? = nil) -> ViewController { +public func channelStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, section: ChannelStatsSection = .stats, boostStatus: ChannelBoostStatus? = nil, boostStatusUpdated: ((ChannelBoostStatus) -> Void)? = nil) -> ViewController { let statePromise = ValuePromise(ChannelStatsControllerState(section: section, boostersExpanded: false, moreBoostersDisplayed: 0, giftsSelected: false), ignoreRepeated: true) let stateValue = Atomic(value: ChannelStatsControllerState(section: section, boostersExpanded: false, moreBoostersDisplayed: 0, giftsSelected: false)) let updateState: ((ChannelStatsControllerState) -> ChannelStatsControllerState) -> Void = { f in @@ -1087,12 +1087,16 @@ public func channelStatsController(context: AccountContext, updatedPresentationD }) dataPromise.set(.single(nil) |> then(dataSignal)) - let boostData: Signal - if let boostStatus { - boostData = .single(boostStatus) - } else { - boostData = .single(nil) |> then(context.engine.peers.getChannelBoostStatus(peerId: peerId)) - } + let boostDataPromise = Promise() + boostDataPromise.set(.single(boostStatus) |> then(context.engine.peers.getChannelBoostStatus(peerId: peerId))) + + actionsDisposable.add((boostDataPromise.get() + |> deliverOnMainQueue).start(next: { boostStatus in + if let boostStatus, let boostStatusUpdated { + boostStatusUpdated(boostStatus) + } + })) + let boostsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: false) let giftsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: true) @@ -1253,7 +1257,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD dataPromise.get(), messagesPromise.get(), storiesPromise.get(), - boostData, + boostDataPromise.get(), boostsContext.state, giftsContext.state, longLoadingSignal @@ -1508,6 +1512,8 @@ public func channelStatsController(context: AccountContext, updatedPresentationD guard let boostStatus, let myBoostStatus else { return } + boostDataPromise.set(.single(boostStatus)) + let boostController = PremiumBoostLevelsScreen( context: context, peerId: peerId, @@ -1515,6 +1521,9 @@ public func channelStatsController(context: AccountContext, updatedPresentationD status: boostStatus, myBoostStatus: myBoostStatus ) + boostController.boostStatusUpdated = { boostStatus in + boostDataPromise.set(.single(boostStatus)) + } controller?.push(boostController) }) } diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index a907c8376c..1b55c9e79c 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -390,7 +390,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } return true } - + public var useExternalSearchContainer: Bool = false private var gifContext: GifContext? { @@ -2133,6 +2133,12 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { strongSelf.interaction?.presentGlobalOverlayController(contextController, nil) }) } + + public func scrollToGroupEmoji() { + if let pagerView = self.entityKeyboardView.componentView as? EntityKeyboardComponent.View { + pagerView.scrollToItemGroup(contentId: "emoji", groupId: "peerSpecific", subgroupId: nil) + } + } } private final class ContextControllerContentSourceImpl: ContextControllerContentSource { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 311b8a32b9..37578d14a1 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -6957,7 +6957,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } 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) + let boostsController = channelStatsController(context: self.context, updatedPresentationData: controller.updatedPresentationData, peerId: self.peerId, section: .boosts, boostStatus: self.boostStatus, boostStatusUpdated: { [weak self] boostStatus in + if let self { + self.boostStatus = boostStatus + } + }) controller.push(boostsController) } else { let _ = combineLatest( @@ -8814,7 +8818,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .settings, forceDark: false, dismissed: nil) self.controller?.push(controller) case .premiumGift: - let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .settings) + let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .settings, completion: nil) self.controller?.push(controller) case .stickers: if let settings = self.data?.globalSettings { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 5519a74203..2925b233eb 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -450,6 +450,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var mediaRestrictedTooltipControllerMode = true weak var checksTooltipController: TooltipController? weak var copyProtectionTooltipController: TooltipController? + weak var emojiPackTooltipController: TooltipScreen? var currentMessageTooltipScreens: [(TooltipScreen, ListViewItemNode)] = [] @@ -15757,6 +15758,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } ) self.present(tooltipScreen, in: .current) + self.emojiPackTooltipController = tooltipScreen let _ = ApplicationSpecificNotice.incrementGroupEmojiPackSuggestion(accountManager: self.context.sharedContext.accountManager, peerId: peerId).startStandalone() }) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index c4bcdfce2a..e81b8dfbc0 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -3705,6 +3705,17 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { strongSelf.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in return (.media(mode: .other, expanded: nil, focused: false), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId) }) + + if let emojiPackTooltipController = strongSelf.controller?.emojiPackTooltipController { + strongSelf.controller?.emojiPackTooltipController = nil + emojiPackTooltipController.dismiss() + + Queue.mainQueue().after(0.1) { + if let inputNode = strongSelf.inputNode as? ChatEntityKeyboardInputNode { + inputNode.scrollToGroupEmoji() + } + } + } }) } } diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 8cb1dbeab5..2a5d09ecf7 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -587,7 +587,7 @@ func openResolvedUrlImpl( } case let .premiumMultiGift(reference): dismissInput() - let controller = context.sharedContext.makePremiumGiftController(context: context, source: .deeplink(reference)) + let controller = context.sharedContext.makePremiumGiftController(context: context, source: .deeplink(reference), completion: nil) if let navigationController = navigationController { navigationController.pushViewController(controller, animated: true) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 38cf692079..7cc369c123 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2049,7 +2049,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { return PremiumLimitScreen(context: context, subject: mappedSubject, count: count, forceDark: forceDark, cancel: cancel, action: action) } - public func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource) -> ViewController { + public func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (() -> Void)?) -> ViewController { let options = Promise<[PremiumGiftCodeOption]>() options.set(context.engine.payments.premiumGiftCodeOptions(peerId: nil)) @@ -2101,6 +2101,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { pushImpl?(c) }, completion: { filterImpl?() + completion?() }) pushImpl = { [weak giftController] c in giftController?.push(c) @@ -2108,7 +2109,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { filterImpl = { [weak giftController] in if let navigationController = giftController?.navigationController as? NavigationController { var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is ContactMultiselectionController) } + controllers = controllers.filter { !($0 is ContactMultiselectionController) && !($0 is PremiumGiftScreen) } navigationController.setViewControllers(controllers, animated: true) } } From 2dd8e19143e7e8764c401a72a8c83b6cd22ac9ec Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 11 Feb 2024 02:09:24 +0400 Subject: [PATCH 05/14] Group stories --- .../Telegram-iOS/en.lproj/Localizable.strings | 8 ++ .../Sources/PeerInfoStoryGridScreen.swift | 95 +++++++++++-------- .../StoryItemSetContainerComponent.swift | 49 +++++++--- 3 files changed, 98 insertions(+), 54 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 745f6064bd..a623c46097 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -11291,3 +11291,11 @@ Sorry for the inconvenience."; "GroupBoost.BoostToUnrestrict" = "Boost the group %1$@ to remove messaging restrictions. Your boosts will help **%2$@** to unlock new features."; "Story.InputPlaceholderReplyInGroup" = "Comment Story..."; + +"Story.SendReactionAsGroupMessage" = "Send reaction as a message"; + +"StoryList.TooltipStoriesSavedToGroup_1" = "Story saved to groups's profile"; +"StoryList.TooltipStoriesSavedToGroup_any" = "%d stories saved to groups's profile."; +"Story.ToastRemovedFromGroupText" = "Story removed from the group page"; +"Story.ToastSavedToGroupTitle" = "Story saved to the group page"; +"Story.ToastSavedToGroupText" = "Posted stories can be viewed by others on the group page until an admin removes them."; diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift index 505d0b4b3b..8dba199363 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift @@ -348,50 +348,61 @@ final class PeerInfoStoryGridScreenComponent: Component { return } - switch component.scope { - case .saved: - let selectedCount = paneNode.selectedItems.count - let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: false).start() - - paneNode.setIsSelectionModeActive(false) - (self.environment?.controller() as? PeerInfoStoryGridScreen)?.updateTitle() - - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) - - let title: String = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(selectedCount)) - environment.controller()?.present(UndoOverlayController( - presentationData: presentationData, - content: .info(title: nil, text: title, timeout: nil, customUndoText: nil), - elevatedLayout: false, - animateInAsReplacement: false, - action: { _ in return false } - ), in: .current) - case .archive: - let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: true).start() - - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) - - let title: String - let text: String - - if component.peerId == component.context.account.peerId { - title = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(paneNode.selectedIds.count)) - text = presentationData.strings.StoryList_TooltipStoriesSavedToProfileText - } else { - title = presentationData.strings.StoryList_TooltipStoriesSavedToChannel(Int32(paneNode.selectedIds.count)) - text = presentationData.strings.Story_ToastSavedToChannelText + let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer else { + return + } + var isGroup = false + if case let .channel(channel) = peer, case .group = channel.info { + isGroup = true } - environment.controller()?.present(UndoOverlayController( - presentationData: presentationData, - content: .info(title: title, text: text, timeout: nil, customUndoText: nil), - elevatedLayout: false, - animateInAsReplacement: false, - action: { _ in return false } - ), in: .current) - - paneNode.clearSelection() - } + switch component.scope { + case .saved: + let selectedCount = paneNode.selectedItems.count + let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: false).start() + + paneNode.setIsSelectionModeActive(false) + (self.environment?.controller() as? PeerInfoStoryGridScreen)?.updateTitle() + + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) + + let title: String = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(selectedCount)) + environment.controller()?.present(UndoOverlayController( + presentationData: presentationData, + content: .info(title: nil, text: title, timeout: nil, customUndoText: nil), + elevatedLayout: false, + animateInAsReplacement: false, + action: { _ in return false } + ), in: .current) + case .archive: + let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: true).start() + + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) + + let title: String + let text: String + + if component.peerId == component.context.account.peerId { + title = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(paneNode.selectedIds.count)) + text = presentationData.strings.StoryList_TooltipStoriesSavedToProfileText + } else { + title = isGroup ? presentationData.strings.StoryList_TooltipStoriesSavedToGroup(Int32(paneNode.selectedIds.count)) : presentationData.strings.StoryList_TooltipStoriesSavedToChannel(Int32(paneNode.selectedIds.count)) + text = isGroup ? presentationData.strings.Story_ToastSavedToGroupText : presentationData.strings.Story_ToastSavedToChannelText + } + + environment.controller()?.present(UndoOverlayController( + presentationData: presentationData, + content: .info(title: title, text: text, timeout: nil, customUndoText: nil), + elevatedLayout: false, + animateInAsReplacement: false, + action: { _ in return false } + ), in: .current) + + paneNode.clearSelection() + } + }) } )), environment: {}, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 2b5467ff88..63b2c01cdd 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -1660,11 +1660,18 @@ public final class StoryItemSetContainerComponent: Component { var canShare = true var displayFooter = false if case let .channel(channel) = component.slice.peer { - displayFooter = true isChannel = true if channel.addressName == nil { canShare = false } + switch channel.info { + case .broadcast: + displayFooter = true + case .group: + if channel.flags.contains(.isCreator) || channel.hasPermission(.postStories) { + displayFooter = true + } + } } else if component.slice.peer.id == component.context.account.peerId { displayFooter = true } else if component.slice.item.storyItem.isPending { @@ -2752,6 +2759,24 @@ public final class StoryItemSetContainerComponent: Component { disabledPlaceholder = .text(component.strings.Story_FooterReplyUnavailable) } + var isChannel = false + var isGroup = false + var showMessageInputPanel = true + if case let .channel(channel) = component.slice.peer { + switch channel.info { + case .broadcast: + isChannel = true + showMessageInputPanel = false + case .group: + isGroup = true + if channel.flags.contains(.isCreator) || channel.hasPermission(.postStories) { + showMessageInputPanel = false + } + } + } else { + showMessageInputPanel = component.slice.peer.id != component.context.account.peerId + } + let inputPlaceholder: MessageInputPanelComponent.Placeholder if let stealthModeTimeout = component.stealthModeTimeout { let minutes = Int(stealthModeTimeout / 60) @@ -2787,7 +2812,7 @@ public final class StoryItemSetContainerComponent: Component { inputPlaceholder = .counter(items) } else { - inputPlaceholder = .plain(component.strings.Story_InputPlaceholderReplyPrivately) + inputPlaceholder = .plain(isGroup ? component.strings.Story_InputPlaceholderReplyInGroup : component.strings.Story_InputPlaceholderReplyPrivately) } let startTime22 = CFAbsoluteTimeGetCurrent() @@ -2811,13 +2836,8 @@ public final class StoryItemSetContainerComponent: Component { var inputPanelSize: CGSize? let startTime23 = CFAbsoluteTimeGetCurrent() - - var isChannel = false - if case .channel = component.slice.peer { - isChannel = true - } - - if component.slice.peer.id != component.context.account.peerId && !isChannel { + + if showMessageInputPanel { var haveLikeOptions = false if case .user = component.slice.peer { haveLikeOptions = true @@ -4381,7 +4401,7 @@ public final class StoryItemSetContainerComponent: Component { presentationData: component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme), items: reactionItems.map(ReactionContextItem.reaction), selectedItems: component.slice.item.storyItem.myReaction.flatMap { Set([$0]) } ?? Set(), - title: self.displayLikeReactions ? nil : component.strings.Story_SendReactionAsMessage, + title: self.displayLikeReactions ? nil : (isGroup ? component.strings.Story_SendReactionAsGroupMessage : component.strings.Story_SendReactionAsMessage), reactionsLocked: false, alwaysAllowPremiumReactions: false, allPresetReactionsAreAvailable: false, @@ -6394,11 +6414,16 @@ public final class StoryItemSetContainerComponent: Component { let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.slice.peer.id, ids: [component.slice.item.storyItem.id: component.slice.item.storyItem], isPinned: !component.slice.item.storyItem.isPinned).startStandalone() + var isGroup = false + if case let .channel(channel) = component.slice.peer, case .group = channel.info { + isGroup = true + } + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) if component.slice.item.storyItem.isPinned { self.scheduledStoryUnpinnedUndoOverlay = UndoOverlayController( presentationData: presentationData, - content: .info(title: nil, text: presentationData.strings.Story_ToastRemovedFromChannelText, timeout: nil, customUndoText: nil), + content: .info(title: nil, text: isGroup ? presentationData.strings.Story_ToastRemovedFromGroupText : presentationData.strings.Story_ToastRemovedFromChannelText, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, blurred: true, @@ -6407,7 +6432,7 @@ public final class StoryItemSetContainerComponent: Component { } else { self.component?.presentController(UndoOverlayController( presentationData: presentationData, - content: .info(title: presentationData.strings.Story_ToastSavedToChannelTitle, text: presentationData.strings.Story_ToastSavedToChannelText, timeout: nil, customUndoText: nil), + content: .info(title: isGroup ? presentationData.strings.Story_ToastSavedToGroupTitle : presentationData.strings.Story_ToastSavedToChannelTitle, text: isGroup ? presentationData.strings.Story_ToastSavedToGroupText : presentationData.strings.Story_ToastSavedToChannelText, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, blurred: true, From f6f64109024d11892befef6adf69c7235009db7f Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 11 Feb 2024 04:18:21 +0400 Subject: [PATCH 06/14] Update API --- submodules/TelegramApi/Sources/Api0.swift | 2 +- submodules/TelegramApi/Sources/Api14.swift | 20 ++++++++++--------- .../ApiUtils/StoreMessage_Telegram.swift | 12 +++++------ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 90ff7307ba..5c4d0c042d 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -566,7 +566,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1328256121] = { return Api.MessageReactions.parse_messageReactions($0) } dict[-2083123262] = { return Api.MessageReplies.parse_messageReplies($0) } dict[-1346631205] = { return Api.MessageReplyHeader.parse_messageReplyHeader($0) } - dict[-1667711039] = { return Api.MessageReplyHeader.parse_messageReplyStoryHeader($0) } + dict[240843065] = { return Api.MessageReplyHeader.parse_messageReplyStoryHeader($0) } dict[1163625789] = { return Api.MessageViews.parse_messageViews($0) } dict[975236280] = { return Api.MessagesFilter.parse_inputMessagesFilterChatPhotos($0) } dict[-530392189] = { return Api.MessagesFilter.parse_inputMessagesFilterContacts($0) } diff --git a/submodules/TelegramApi/Sources/Api14.swift b/submodules/TelegramApi/Sources/Api14.swift index 9d7878ffec..4c26a4d880 100644 --- a/submodules/TelegramApi/Sources/Api14.swift +++ b/submodules/TelegramApi/Sources/Api14.swift @@ -323,7 +323,7 @@ public extension Api { public extension Api { indirect enum MessageReplyHeader: TypeConstructorDescription { case messageReplyHeader(flags: Int32, replyToMsgId: Int32?, replyToPeerId: Api.Peer?, replyFrom: Api.MessageFwdHeader?, replyMedia: Api.MessageMedia?, replyToTopId: Int32?, quoteText: String?, quoteEntities: [Api.MessageEntity]?, quoteOffset: Int32?) - case messageReplyStoryHeader(userId: Int64, storyId: Int32) + case messageReplyStoryHeader(peer: Api.Peer, storyId: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -345,11 +345,11 @@ public extension Api { }} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(quoteOffset!, buffer: buffer, boxed: false)} break - case .messageReplyStoryHeader(let userId, let storyId): + case .messageReplyStoryHeader(let peer, let storyId): if boxed { - buffer.appendInt32(-1667711039) + buffer.appendInt32(240843065) } - serializeInt64(userId, buffer: buffer, boxed: false) + peer.serialize(buffer, true) serializeInt32(storyId, buffer: buffer, boxed: false) break } @@ -359,8 +359,8 @@ public extension Api { switch self { case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyFrom, let replyMedia, let replyToTopId, let quoteText, let quoteEntities, let quoteOffset): return ("messageReplyHeader", [("flags", flags as Any), ("replyToMsgId", replyToMsgId as Any), ("replyToPeerId", replyToPeerId as Any), ("replyFrom", replyFrom as Any), ("replyMedia", replyMedia as Any), ("replyToTopId", replyToTopId as Any), ("quoteText", quoteText as Any), ("quoteEntities", quoteEntities as Any), ("quoteOffset", quoteOffset as Any)]) - case .messageReplyStoryHeader(let userId, let storyId): - return ("messageReplyStoryHeader", [("userId", userId as Any), ("storyId", storyId as Any)]) + case .messageReplyStoryHeader(let peer, let storyId): + return ("messageReplyStoryHeader", [("peer", peer as Any), ("storyId", storyId as Any)]) } } @@ -408,14 +408,16 @@ public extension Api { } } public static func parse_messageReplyStoryHeader(_ reader: BufferReader) -> MessageReplyHeader? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } var _2: Int32? _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.MessageReplyHeader.messageReplyStoryHeader(userId: _1!, storyId: _2!) + return Api.MessageReplyHeader.messageReplyStoryHeader(peer: _1!, storyId: _2!) } else { return nil diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index ec3557d83e..76754ca04c 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -200,8 +200,8 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { if let replyTo = replyTo { switch replyTo { - case let .messageReplyStoryHeader(userId, _): - let storyPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) + case let .messageReplyStoryHeader(peer, _): + let storyPeerId = peer.peerId if !result.contains(storyPeerId) { result.append(storyPeerId) } @@ -668,8 +668,8 @@ extension StoreMessage { if let replyHeader = replyHeader { attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote, isQuote: isQuote)) } - case let .messageReplyStoryHeader(userId, storyId): - attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: storyId))) + case let .messageReplyStoryHeader(peer, storyId): + attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: peer.peerId, id: storyId))) } } @@ -952,8 +952,8 @@ extension StoreMessage { } else if let replyHeader = replyHeader { attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote, isQuote: isQuote)) } - case let .messageReplyStoryHeader(userId, storyId): - attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: storyId))) + case let .messageReplyStoryHeader(peer, storyId): + attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: peer.peerId, id: storyId))) } } else { switch action { From 6728d7ba9c5deb74506695725884fda1f151c06c Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 11 Feb 2024 04:20:47 +0400 Subject: [PATCH 07/14] Group boosts --- Telegram/Telegram-iOS/en.lproj/Localizable.strings | 3 +++ .../Sources/ChatRecentActionsHistoryTransition.swift | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index a623c46097..2bf37b09be 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -11299,3 +11299,6 @@ Sorry for the inconvenience."; "Story.ToastRemovedFromGroupText" = "Story removed from the group page"; "Story.ToastSavedToGroupTitle" = "Story saved to the group page"; "Story.ToastSavedToGroupText" = "Posted stories can be viewed by others on the group page until an admin removes them."; + +"Channel.AdminLog.MessageChangedGroupEmojiPack" = "%@ changed group emoji pack"; +"Channel.AdminLog.MessageRemovedGroupEmojiPack" = "%@ removed group emoji pack"; diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift index 05809cbc03..35f4d5ab0e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift @@ -2129,14 +2129,14 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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 + appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageChangedGroupEmojiPack(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 + appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageRemovedGroupEmojiPack(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)] } From c3af008cd261ff25038498da7bd2d3e7469ff9fa Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 11 Feb 2024 15:01:11 +0400 Subject: [PATCH 08/14] Disable haptics while recording video messages --- submodules/Camera/Sources/Camera.swift | 2 +- .../Sources/VideoMessageCameraScreen.swift | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index 9e693c2574..5e2e736e8a 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -726,9 +726,9 @@ public final class Camera { self.metrics = Camera.Metrics(model: DeviceModel.current) let session = CameraSession() - session.session.usesApplicationAudioSession = true session.session.automaticallyConfiguresApplicationAudioSession = false session.session.automaticallyConfiguresCaptureDeviceForWideColor = false + session.session.usesApplicationAudioSession = true if let previewView { previewView.setSession(session.session, autoConnect: !session.hasMultiCam) } diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index dc23023e42..8d6d2e45fc 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -1443,9 +1443,6 @@ public class VideoMessageCameraScreen: ViewController { deinit { self.audioSessionDisposable?.dispose() - if #available(iOS 13.0, *) { - try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(false) - } } override public func loadDisplayNode() { @@ -1688,9 +1685,6 @@ public class VideoMessageCameraScreen: ViewController { } self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: audioSessionType, activate: { [weak self] _ in - if #available(iOS 13.0, *) { - try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(true) - } if let self { Queue.mainQueue().async { self.node.setupCamera() From 2533b6587946b20735550ff0e98723dfa0dc56f2 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 11 Feb 2024 20:31:36 +0400 Subject: [PATCH 09/14] Group stories --- submodules/TelegramApi/Sources/Api0.swift | 2 +- submodules/TelegramApi/Sources/Api21.swift | 74 ++++++++++--------- .../State/AccountStateManagementUtils.swift | 6 +- .../Sources/Statistics/StoryStatistics.swift | 3 +- .../Messages/EngineStoryViewListContext.swift | 12 ++- .../TelegramEngine/Messages/Stories.swift | 46 ++++++++---- .../Messages/StoryListContext.swift | 36 ++++++--- .../Messages/TelegramEngineMessages.swift | 3 +- .../Sources/StoryAuthorInfoComponent.swift | 26 ++++++- .../Sources/StoryChatContent.swift | 22 +++++- .../StoryItemSetContainerComponent.swift | 7 ++ 11 files changed, 166 insertions(+), 71 deletions(-) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 5c4d0c042d..4188aaf0c2 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -833,7 +833,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[2008112412] = { return Api.StickerSetCovered.parse_stickerSetNoCovered($0) } dict[1898850301] = { return Api.StoriesStealthMode.parse_storiesStealthMode($0) } dict[-1205411504] = { return Api.StoryFwdHeader.parse_storyFwdHeader($0) } - dict[-1352440415] = { return Api.StoryItem.parse_storyItem($0) } + dict[2041735716] = { return Api.StoryItem.parse_storyItem($0) } dict[1374088783] = { return Api.StoryItem.parse_storyItemDeleted($0) } dict[-5388013] = { return Api.StoryItem.parse_storyItemSkipped($0) } dict[1620104917] = { return Api.StoryReaction.parse_storyReaction($0) } diff --git a/submodules/TelegramApi/Sources/Api21.swift b/submodules/TelegramApi/Sources/Api21.swift index 53dc6c246e..174f9c3cb7 100644 --- a/submodules/TelegramApi/Sources/Api21.swift +++ b/submodules/TelegramApi/Sources/Api21.swift @@ -1034,19 +1034,20 @@ public extension Api { } public extension Api { indirect enum StoryItem: TypeConstructorDescription { - case storyItem(flags: Int32, id: Int32, date: Int32, fwdFrom: Api.StoryFwdHeader?, expireDate: Int32, caption: String?, entities: [Api.MessageEntity]?, media: Api.MessageMedia, mediaAreas: [Api.MediaArea]?, privacy: [Api.PrivacyRule]?, views: Api.StoryViews?, sentReaction: Api.Reaction?) + case storyItem(flags: Int32, id: Int32, date: Int32, fromId: Api.Peer?, fwdFrom: Api.StoryFwdHeader?, expireDate: Int32, caption: String?, entities: [Api.MessageEntity]?, media: Api.MessageMedia, mediaAreas: [Api.MediaArea]?, privacy: [Api.PrivacyRule]?, views: Api.StoryViews?, sentReaction: Api.Reaction?) case storyItemDeleted(id: Int32) case storyItemSkipped(flags: Int32, id: Int32, date: Int32, expireDate: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .storyItem(let flags, let id, let date, let fwdFrom, let expireDate, let caption, let entities, let media, let mediaAreas, let privacy, let views, let sentReaction): + case .storyItem(let flags, let id, let date, let fromId, let fwdFrom, let expireDate, let caption, let entities, let media, let mediaAreas, let privacy, let views, let sentReaction): if boxed { - buffer.appendInt32(-1352440415) + buffer.appendInt32(2041735716) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 18) != 0 {fromId!.serialize(buffer, true)} if Int(flags) & Int(1 << 17) != 0 {fwdFrom!.serialize(buffer, true)} serializeInt32(expireDate, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeString(caption!, buffer: buffer, boxed: false)} @@ -1089,8 +1090,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .storyItem(let flags, let id, let date, let fwdFrom, let expireDate, let caption, let entities, let media, let mediaAreas, let privacy, let views, let sentReaction): - return ("storyItem", [("flags", flags as Any), ("id", id as Any), ("date", date as Any), ("fwdFrom", fwdFrom as Any), ("expireDate", expireDate as Any), ("caption", caption as Any), ("entities", entities as Any), ("media", media as Any), ("mediaAreas", mediaAreas as Any), ("privacy", privacy as Any), ("views", views as Any), ("sentReaction", sentReaction as Any)]) + case .storyItem(let flags, let id, let date, let fromId, let fwdFrom, let expireDate, let caption, let entities, let media, let mediaAreas, let privacy, let views, let sentReaction): + return ("storyItem", [("flags", flags as Any), ("id", id as Any), ("date", date as Any), ("fromId", fromId as Any), ("fwdFrom", fwdFrom as Any), ("expireDate", expireDate as Any), ("caption", caption as Any), ("entities", entities as Any), ("media", media as Any), ("mediaAreas", mediaAreas as Any), ("privacy", privacy as Any), ("views", views as Any), ("sentReaction", sentReaction as Any)]) case .storyItemDeleted(let id): return ("storyItemDeleted", [("id", id as Any)]) case .storyItemSkipped(let flags, let id, let date, let expireDate): @@ -1105,52 +1106,57 @@ public extension Api { _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() - var _4: Api.StoryFwdHeader? + var _4: Api.Peer? + if Int(_1!) & Int(1 << 18) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _5: Api.StoryFwdHeader? if Int(_1!) & Int(1 << 17) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.StoryFwdHeader + _5 = Api.parse(reader, signature: signature) as? Api.StoryFwdHeader } } - var _5: Int32? - _5 = reader.readInt32() - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } - var _7: [Api.MessageEntity]? + var _6: Int32? + _6 = reader.readInt32() + var _7: String? + if Int(_1!) & Int(1 << 0) != 0 {_7 = parseString(reader) } + var _8: [Api.MessageEntity]? if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } - var _8: Api.MessageMedia? + var _9: Api.MessageMedia? if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.MessageMedia + _9 = Api.parse(reader, signature: signature) as? Api.MessageMedia } - var _9: [Api.MediaArea]? + var _10: [Api.MediaArea]? if Int(_1!) & Int(1 << 14) != 0 {if let _ = reader.readInt32() { - _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MediaArea.self) + _10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MediaArea.self) } } - var _10: [Api.PrivacyRule]? + var _11: [Api.PrivacyRule]? if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() { - _10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrivacyRule.self) + _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrivacyRule.self) } } - var _11: Api.StoryViews? + var _12: Api.StoryViews? if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.StoryViews + _12 = Api.parse(reader, signature: signature) as? Api.StoryViews } } - var _12: Api.Reaction? + var _13: Api.Reaction? if Int(_1!) & Int(1 << 15) != 0 {if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.Reaction + _13 = Api.parse(reader, signature: signature) as? Api.Reaction } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 17) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 14) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 2) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 15) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.StoryItem.storyItem(flags: _1!, id: _2!, date: _3!, fwdFrom: _4, expireDate: _5!, caption: _6, entities: _7, media: _8!, mediaAreas: _9, privacy: _10, views: _11, sentReaction: _12) + let _c4 = (Int(_1!) & Int(1 << 18) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 17) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil + let _c9 = _9 != nil + let _c10 = (Int(_1!) & Int(1 << 14) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 2) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 3) == 0) || _12 != nil + let _c13 = (Int(_1!) & Int(1 << 15) == 0) || _13 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { + return Api.StoryItem.storyItem(flags: _1!, id: _2!, date: _3!, fromId: _4, fwdFrom: _5, expireDate: _6!, caption: _7, entities: _8, media: _9!, mediaAreas: _10, privacy: _11, views: _12, sentReaction: _13) } else { return nil diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 5690d6bd4b..a68c802ace 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -4712,7 +4712,8 @@ func replayFinalState( isEdited: item.isEdited, isMy: item.isMy, myReaction: updatedReaction, - forwardInfo: item.forwardInfo + forwardInfo: item.forwardInfo, + authorId: item.authorId )) if let entry = CodableEntry(updatedItem) { updatedPeerEntries[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: item.expirationTimestamp, isCloseFriends: item.isCloseFriends) @@ -4745,7 +4746,8 @@ func replayFinalState( isEdited: item.isEdited, isMy: item.isMy, myReaction: MessageReaction.Reaction(apiReaction: reaction), - forwardInfo: item.forwardInfo + forwardInfo: item.forwardInfo, + authorId: item.authorId )) if let entry = CodableEntry(updatedItem) { transaction.setStory(id: StoryId(peerId: peerId, id: id), value: entry) diff --git a/submodules/TelegramCore/Sources/Statistics/StoryStatistics.swift b/submodules/TelegramCore/Sources/Statistics/StoryStatistics.swift index 1c0b69c35e..fd19a6cba0 100644 --- a/submodules/TelegramCore/Sources/Statistics/StoryStatistics.swift +++ b/submodules/TelegramCore/Sources/Statistics/StoryStatistics.swift @@ -349,7 +349,8 @@ private final class StoryStatsPublicForwardsContextImpl { isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) } + forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }, + author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) } ) resultForwards.append(.story(EnginePeer(peer), mappedItem)) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift index a57ff9e065..136993ab0e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift @@ -556,7 +556,8 @@ public final class EngineStoryViewListContext { isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) } + forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }, + author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) } ), storyStats: transaction.getPeerStoryStats(peerId: peer.id) ))) @@ -595,7 +596,8 @@ public final class EngineStoryViewListContext { isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo + forwardInfo: item.forwardInfo, + authorId: item.authorId )) if let entry = CodableEntry(updatedItem) { transaction.setStory(id: StoryId(peerId: account.peerId, id: storyId), value: entry) @@ -634,7 +636,8 @@ public final class EngineStoryViewListContext { isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo + forwardInfo: item.forwardInfo, + authorId: item.authorId )) if let entry = CodableEntry(updatedItem) { currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends) @@ -748,7 +751,8 @@ public final class EngineStoryViewListContext { isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) } + forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }, + author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) } ), storyStats: transaction.getPeerStoryStats(peerId: peer.id) ))) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index 22654b8f7d..883ebd894c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -261,6 +261,7 @@ public enum Stories { case isMy case myReaction case forwardInfo + case authorId } public let id: Int32 @@ -284,6 +285,7 @@ public enum Stories { public let isMy: Bool public let myReaction: MessageReaction.Reaction? public let forwardInfo: ForwardInfo? + public let authorId: PeerId? public init( id: Int32, @@ -306,7 +308,8 @@ public enum Stories { isEdited: Bool, isMy: Bool, myReaction: MessageReaction.Reaction?, - forwardInfo: ForwardInfo? + forwardInfo: ForwardInfo?, + authorId: PeerId? ) { self.id = id self.timestamp = timestamp @@ -329,6 +332,7 @@ public enum Stories { self.isMy = isMy self.myReaction = myReaction self.forwardInfo = forwardInfo + self.authorId = authorId } public init(from decoder: Decoder) throws { @@ -367,6 +371,7 @@ public enum Stories { self.isMy = try container.decodeIfPresent(Bool.self, forKey: .isMy) ?? false self.myReaction = try container.decodeIfPresent(MessageReaction.Reaction.self, forKey: .myReaction) self.forwardInfo = try container.decodeIfPresent(ForwardInfo.self, forKey: .forwardInfo) + self.authorId = try container.decodeIfPresent(Int64.self, forKey: .authorId).flatMap { PeerId($0) } } public func encode(to encoder: Encoder) throws { @@ -407,6 +412,7 @@ public enum Stories { try container.encode(self.isMy, forKey: .isMy) try container.encodeIfPresent(self.myReaction, forKey: .myReaction) try container.encodeIfPresent(self.forwardInfo, forKey: .forwardInfo) + try container.encodeIfPresent(self.authorId?.toInt64(), forKey: .authorId) } public static func ==(lhs: Item, rhs: Item) -> Bool { @@ -485,6 +491,9 @@ public enum Stories { if lhs.forwardInfo != rhs.forwardInfo { return false } + if lhs.authorId != rhs.authorId { + return false + } return true } } @@ -1173,7 +1182,7 @@ func _internal_uploadStoryImpl( for update in updates.allUpdates { if case let .updateStory(_, story) = update { switch story { - case let .storyItem(_, idValue, _, _, _, _, _, media, _, _, _, _): + case let .storyItem(_, idValue, _, fromId, _, _, _, _, media, _, _, _, _): if let parsedStory = Stories.StoredItem(apiStoryItem: story, peerId: toPeerId, transaction: transaction) { var items = transaction.getStoryItems(peerId: toPeerId) var updatedItems: [Stories.Item] = [] @@ -1199,7 +1208,8 @@ func _internal_uploadStoryImpl( isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo + forwardInfo: item.forwardInfo, + authorId: fromId?.peerId ) if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { items.append(StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)) @@ -1336,7 +1346,7 @@ func _internal_editStory(account: Account, peerId: PeerId, id: Int32, media: Eng for update in updates.allUpdates { if case let .updateStory(_, story) = update { switch story { - case let .storyItem(_, _, _, _, _, _, _, media, _, _, _, _): + case let .storyItem(_, _, _, _, _, _, _, _, media, _, _, _, _): let (parsedMedia, _, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, account.peerId) if let parsedMedia = parsedMedia, let originalMedia = originalMedia { applyMediaResourceChanges(from: originalMedia, to: parsedMedia, postbox: account.postbox, force: false) @@ -1381,7 +1391,8 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo + forwardInfo: item.forwardInfo, + authorId: item.authorId ) if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { transaction.setStory(id: storyId, value: entry) @@ -1412,7 +1423,8 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo + forwardInfo: item.forwardInfo, + authorId: item.authorId ) if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends) @@ -1606,7 +1618,8 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo + forwardInfo: item.forwardInfo, + authorId: item.authorId ) if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends) @@ -1636,7 +1649,8 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo + forwardInfo: item.forwardInfo, + authorId: item.authorId ) updatedItems.append(updatedItem) } @@ -1668,7 +1682,7 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In extension Api.StoryItem { var id: Int32 { switch self { - case let .storyItem(_, id, _, _, _, _, _, _, _, _, _, _): + case let .storyItem(_, id, _, _, _, _, _, _, _, _, _, _, _): return id case let .storyItemDeleted(id): return id @@ -1731,7 +1745,7 @@ extension Stories.Item.ForwardInfo { extension Stories.StoredItem { init?(apiStoryItem: Api.StoryItem, existingItem: Stories.Item? = nil, peerId: PeerId, transaction: Transaction) { switch apiStoryItem { - case let .storyItem(flags, id, date, forwardFrom, expireDate, caption, entities, media, mediaAreas, privacy, views, sentReaction): + case let .storyItem(flags, id, date, fromId, forwardFrom, expireDate, caption, entities, media, mediaAreas, privacy, views, sentReaction): let (parsedMedia, _, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, peerId) if let parsedMedia = parsedMedia { var parsedPrivacy: Stories.Item.Privacy? @@ -1840,7 +1854,8 @@ extension Stories.StoredItem { isEdited: isEdited, isMy: mergedIsMy, myReaction: mergedMyReaction, - forwardInfo: mergedForwardInfo + forwardInfo: mergedForwardInfo, + authorId: fromId?.peerId ) self = .item(item) } else { @@ -1916,7 +1931,8 @@ func _internal_getStoryById(accountPeerId: PeerId, postbox: Postbox, network: Ne isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) } + forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }, + author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) } ) } } @@ -2386,7 +2402,8 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int isEdited: item.isEdited, isMy: item.isMy, myReaction: reaction, - forwardInfo: item.forwardInfo + forwardInfo: item.forwardInfo, + authorId: item.authorId )) updatedItemValue = updatedItem if let entry = CodableEntry(updatedItem) { @@ -2419,7 +2436,8 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int isEdited: item.isEdited, isMy: item.isMy, myReaction: reaction, - forwardInfo: item.forwardInfo + forwardInfo: item.forwardInfo, + authorId: item.authorId )) updatedItemValue = updatedItem if let entry = CodableEntry(updatedItem) { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift index 6f9d330c65..acc6fc8b9e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift @@ -78,8 +78,9 @@ public final class EngineStoryItem: Equatable { public let isMy: Bool public let myReaction: MessageReaction.Reaction? public let forwardInfo: ForwardInfo? + public let author: EnginePeer? - public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, alternativeMedia: EngineMedia?, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool, isContacts: Bool, isSelectedContacts: Bool, isForwardingDisabled: Bool, isEdited: Bool, isMy: Bool, myReaction: MessageReaction.Reaction?, forwardInfo: ForwardInfo?) { + public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, alternativeMedia: EngineMedia?, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool, isContacts: Bool, isSelectedContacts: Bool, isForwardingDisabled: Bool, isEdited: Bool, isMy: Bool, myReaction: MessageReaction.Reaction?, forwardInfo: ForwardInfo?, author: EnginePeer?) { self.id = id self.timestamp = timestamp self.expirationTimestamp = expirationTimestamp @@ -102,6 +103,7 @@ public final class EngineStoryItem: Equatable { self.isMy = isMy self.myReaction = myReaction self.forwardInfo = forwardInfo + self.author = author } public static func ==(lhs: EngineStoryItem, rhs: EngineStoryItem) -> Bool { @@ -171,6 +173,9 @@ public final class EngineStoryItem: Equatable { if lhs.forwardInfo != rhs.forwardInfo { return false } + if lhs.author != rhs.author { + return false + } return true } } @@ -223,7 +228,8 @@ public extension EngineStoryItem { isEdited: self.isEdited, isMy: self.isMy, myReaction: self.myReaction, - forwardInfo: self.forwardInfo?.storedForwardInfo + forwardInfo: self.forwardInfo?.storedForwardInfo, + authorId: self.author?.id ) } } @@ -600,7 +606,8 @@ public final class PeerStoryListContext { isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) } + forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }, + author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) } ) items.append(mappedItem) @@ -745,7 +752,8 @@ public final class PeerStoryListContext { isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) } + forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }, + author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) } ) storyItems.append(mappedItem) } @@ -839,6 +847,11 @@ public final class PeerStoryListContext { peers[peer.id] = peer } } + if let peerId = item.authorId { + if let peer = transaction.getPeer(peerId) { + peers[peer.id] = peer + } + } } } default: @@ -907,7 +920,8 @@ public final class PeerStoryListContext { isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) } + forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) }, + author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) } ) finalUpdatedState = updatedState } @@ -955,7 +969,8 @@ public final class PeerStoryListContext { isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) } + forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) }, + author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) } ) finalUpdatedState = updatedState } else { @@ -1005,7 +1020,8 @@ public final class PeerStoryListContext { isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) } + forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) }, + author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) } )) updatedState.items.sort(by: { lhs, rhs in return lhs.timestamp > rhs.timestamp @@ -1051,7 +1067,8 @@ public final class PeerStoryListContext { isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) } + forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) }, + author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) } )) updatedState.items.sort(by: { lhs, rhs in return lhs.timestamp > rhs.timestamp @@ -1221,7 +1238,8 @@ public final class PeerExpiringStoryListContext { isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) } + forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }, + author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) } ) items.append(.item(mappedItem)) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index ae5277c131..72ae2f0dbe 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -1249,7 +1249,8 @@ public extension TelegramEngine { isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo + forwardInfo: item.forwardInfo, + authorId: item.authorId )) if let entry = CodableEntry(updatedItem) { currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift index 04ace550cd..810596bab8 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift @@ -19,15 +19,17 @@ final class StoryAuthorInfoComponent: Component { let strings: PresentationStrings let peer: EnginePeer? let forwardInfo: EngineStoryItem.ForwardInfo? + let author: EnginePeer? let timestamp: Int32 let counters: Counters? let isEdited: Bool - init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer?, forwardInfo: EngineStoryItem.ForwardInfo?, timestamp: Int32, counters: Counters?, isEdited: Bool) { + init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer?, forwardInfo: EngineStoryItem.ForwardInfo?, author: EnginePeer?, timestamp: Int32, counters: Counters?, isEdited: Bool) { self.context = context self.strings = strings self.peer = peer self.forwardInfo = forwardInfo + self.author = author self.timestamp = timestamp self.counters = counters self.isEdited = isEdited @@ -46,6 +48,9 @@ final class StoryAuthorInfoComponent: Component { if lhs.forwardInfo != rhs.forwardInfo { return false } + if lhs.author != rhs.author { + return false + } if lhs.timestamp != rhs.timestamp { return false } @@ -121,6 +126,16 @@ final class StoryAuthorInfoComponent: Component { } subtitle = combinedString subtitleTruncationType = .middle + } else if let author = component.author { + let authorName = author.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let timeString = stringForStoryActivityTimestamp(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, preciseTime: true, relativeTimestamp: component.timestamp, relativeTo: timestamp, short: true) + let combinedString = NSMutableAttributedString() + combinedString.append(NSAttributedString(string: authorName, font: Font.medium(11.0), textColor: titleColor)) + if timeString.count < 6 { + combinedString.append(NSAttributedString(string: " • \(timeString)", font: Font.regular(11.0), textColor: subtitleColor)) + } + subtitle = combinedString + subtitleTruncationType = .middle } else { var subtitleString = stringForStoryActivityTimestamp(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, preciseTime: true, relativeTimestamp: component.timestamp, relativeTo: timestamp) if component.isEdited { @@ -176,7 +191,16 @@ final class StoryAuthorInfoComponent: Component { self.repostIconView = nil repostIconView.removeFromSuperview() } + + + var authorPeer: EnginePeer? if let forwardInfo = component.forwardInfo, case let .known(peer, _, _) = forwardInfo { + authorPeer = peer + } else if let author = component.author { + authorPeer = author + } + + if let peer = authorPeer { let avatarNode: AvatarNode if let current = self.avatarNode { avatarNode = current diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift index 93dd8ed0e1..d76bb2a168 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift @@ -114,6 +114,11 @@ public final class StoryContentContextImpl: StoryContentContext { forwardInfoStories.updateValue(nil, forKey: storyId) } } + if let peerId = itemValue.authorId { + if let peer = transaction.getPeer(peerId) { + peers[peer.id] = peer + } + } for entity in itemValue.entities { if case let .CustomEmoji(_, fileId) = entity.type { let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId) @@ -296,7 +301,8 @@ public final class StoryContentContextImpl: StoryContentContext { isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: forwardInfo + forwardInfo: forwardInfo, + author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) } ) } var totalCount = peerStoryItemsView.items.count @@ -332,7 +338,8 @@ public final class StoryContentContextImpl: StoryContentContext { isEdited: false, isMy: true, myReaction: nil, - forwardInfo: pendingForwardsInfo[item.randomId] + forwardInfo: pendingForwardsInfo[item.randomId], + author: nil )) totalCount += 1 } @@ -1187,6 +1194,11 @@ public final class SingleStoryContentContextImpl: StoryContentContext { stories.updateValue(nil, forKey: storyId) } } + if let peerId = item.authorId { + if let peer = transaction.getPeer(peerId) { + peers[peer.id] = peer + } + } for entity in item.entities { if case let .CustomEmoji(_, fileId) = entity.type { let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId) @@ -1312,7 +1324,8 @@ public final class SingleStoryContentContextImpl: StoryContentContext { isEdited: itemValue.isEdited, isMy: itemValue.isMy, myReaction: itemValue.myReaction, - forwardInfo: forwardInfo + forwardInfo: forwardInfo, + author: itemValue.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) } ) let mainItem = StoryContentItem( @@ -2191,7 +2204,8 @@ private func getCachedStory(storyId: StoryId, transaction: Transaction) -> Engin isEdited: item.isEdited, isMy: item.isMy, myReaction: item.myReaction, - forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) } + forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }, + author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) } ) } else { return nil diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 63b2c01cdd..7f0a044e26 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -4018,6 +4018,7 @@ public final class StoryItemSetContainerComponent: Component { strings: component.strings, peer: component.slice.peer, forwardInfo: component.slice.item.storyItem.forwardInfo, + author: component.slice.item.storyItem.author, timestamp: component.slice.item.storyItem.timestamp, counters: counters, isEdited: component.slice.item.storyItem.isEdited @@ -4051,6 +4052,12 @@ public final class StoryItemSetContainerComponent: Component { } else { self.navigateToPeer(peer: peer, chat: false) } + } else if let author = component.slice.item.storyItem.author { + if author.id == component.context.account.peerId { + self.navigateToMyStories() + } else { + self.navigateToPeer(peer: author, chat: false) + } } else { if component.slice.peer.id == component.context.account.peerId { self.navigateToMyStories() From b757250211d8bdd52061ecd6793cbd59209d31f3 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 12 Feb 2024 02:35:46 +0400 Subject: [PATCH 10/14] Group boosts --- submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift | 2 +- .../Sources/ChatMessageBubbleItemNode.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift index e721f4eb2f..b1f13ca380 100644 --- a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift @@ -662,7 +662,7 @@ private final class SheetContent: CombinedComponent { textString = memberString } } else { - textString = "\(memberString) \(strings.GroupBoost_MemberBoosted_BoostForBadge(peerName))" + textString = "\(memberString) \(strings.GroupBoost_MemberBoosted_BoostForBadge(peerName).string)" } isCurrent = true case let .unrestrict(unrestrictCount): diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index adb7ea06bf..d5e4205c45 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -2274,7 +2274,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } nameNodeOriginY = headerSize.height - headerSize.width = max(headerSize.width, nameNodeSizeApply.0.width + adminBadgeSizeAndApply.0.size.width + credibilityIconWidth + boostBadgeWidth + closeButtonWidth + bubbleWidthInsets) + + headerSize.width = max(headerSize.width, nameNodeSizeApply.0.width + 8.0 + adminBadgeSizeAndApply.0.size.width + credibilityIconWidth + boostBadgeWidth + closeButtonWidth + bubbleWidthInsets) headerSize.height += nameNodeSizeApply.0.height } From af9a3729ca968959a0822ee1050539d1ccbc5e75 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 12 Feb 2024 02:39:34 +0400 Subject: [PATCH 11/14] Group boosts --- submodules/StatisticsUI/Sources/ChannelStatsController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 50350b227f..57ca6ab5b5 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -1517,7 +1517,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD let boostController = PremiumBoostLevelsScreen( context: context, peerId: peerId, - mode: .user(mode: .current), + mode: .owner(subject: nil), status: boostStatus, myBoostStatus: myBoostStatus ) From 977435c9e85c52241f379960f5b4c0bd1dbb6563 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 12 Feb 2024 05:30:08 +0400 Subject: [PATCH 12/14] Group boosts --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 + .../GroupStickerPackSetupController/BUILD | 1 + .../Sources/GroupStickerPackCurrentItem.swift | 2 + .../GroupStickerPackSetupController.swift | 71 +++++++++++++------ .../WebUI/Sources/WebAppController.swift | 12 +++- 5 files changed, 62 insertions(+), 26 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 2bf37b09be..1077520e62 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -11302,3 +11302,5 @@ Sorry for the inconvenience."; "Channel.AdminLog.MessageChangedGroupEmojiPack" = "%@ changed group emoji pack"; "Channel.AdminLog.MessageRemovedGroupEmojiPack" = "%@ removed group emoji pack"; + +"Group.Appearance.EmojiPackUpdated" = "Group emoji pack updated."; diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD b/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD index 8122488eeb..8b9993c933 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD @@ -23,6 +23,7 @@ swift_library( "//submodules/SearchBarNode:SearchBarNode", "//submodules/SearchUI:SearchUI", "//submodules/MergeLists:MergeLists", + "//submodules/UndoUI:UndoUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift index d85f62ab60..cc3e992b8c 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift @@ -344,6 +344,7 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { if case .searching = item.content { strongSelf.activityIndicator.isHidden = false + strongSelf.imageNode.isHidden = true } else { strongSelf.activityIndicator.isHidden = true } @@ -351,6 +352,7 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { if case .found = item.content { strongSelf.removeButtonIcon.isHidden = false strongSelf.removeButton.isHidden = false + strongSelf.imageNode.isHidden = false } else { strongSelf.removeButtonIcon.isHidden = true strongSelf.removeButton.isHidden = true diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift index 7a925433d1..3c0dd74cdc 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift @@ -11,6 +11,7 @@ import PresentationDataUtils import AccountContext import StickerPackPreviewUI import ItemListStickerPackItem +import UndoUI private final class GroupStickerPackSetupControllerArguments { let context: AccountContext @@ -343,6 +344,20 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres initialData.set(.single(.noData)) } + var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? + + var completionImpl: ((StickerPackCollectionInfo?) -> Void)? + if let completion { + completionImpl = { value in + completion(value) + if let _ = value { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: presentationData.strings.Group_Appearance_EmojiPackUpdated, cancel: nil, destructive: false), elevatedLayout: false, action: { _ in return true }) + presentControllerImpl?(controller, nil) + } + } + } + let stickerPacks = Promise() stickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [isEmoji ? Namespaces.ItemCollection.CloudEmojiPacks : Namespaces.ItemCollection.CloudStickerPacks])])) @@ -353,6 +368,9 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres if searchText.isEmpty { return .single((searchText, .none)) } else if case let .data(data) = initialData, searchText.lowercased() == data.info.shortName { + Queue.mainQueue().async { + completionImpl?(data.info) + } return .single((searchText, .found(StickerPackData(info: data.info, item: data.item)))) } else { let namespace = isEmoji ? Namespaces.ItemCollection.CloudEmojiPacks : Namespaces.ItemCollection.CloudStickerPacks @@ -378,6 +396,13 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres case let .result(info, items, _): return .single((searchText, .found(StickerPackData(info: info, item: items.first)))) } + } + |> afterNext { value in + if case let .found(data) = value.1 { + Queue.mainQueue().async { + completionImpl?(data.info) + } + } }) } } else { @@ -385,7 +410,6 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres } }) - var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? var navigateToChatControllerImpl: ((PeerId) -> Void)? var dismissInputImpl: (() -> Void)? var dismissImpl: (() -> Void)? @@ -402,15 +426,13 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres let arguments = GroupStickerPackSetupControllerArguments(context: context, selectStickerPack: { info in searchText.set(info.shortName) - if let completion { - completion(info) - } + completionImpl?(info) }, openStickerPack: { info in presentStickerPackController?(info) }, updateSearchText: { text in searchText.set(text) - if text == "", let completion { - completion(nil) + if text == "" { + completionImpl?(nil) } }, openStickersBot: { resolveDisposable.set((context.engine.peers.resolvePeerByName(name: "stickers") @@ -473,29 +495,24 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres info = data.info } rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: enabled, action: { - if let completion { - completion(info) + if info?.id == currentPackInfo?.id { 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 = true + state.isSaving = false 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?() - })) - } + }, completed: { + dismissImpl?() + })) } }) } @@ -537,6 +554,14 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres presentControllerImpl = { [weak controller] c, p in if let controller = controller { + if c is UndoOverlayController { + controller.window?.forEachController { c in + if let controller = c as? UndoOverlayController { + controller.dismiss() + } + } + } + controller.present(c, in: .window(.root), with: p) } } diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index d3f5cc434d..642b3d682d 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -291,6 +291,8 @@ public final class WebAppController: ViewController, AttachmentContainable { private var paymentDisposable: Disposable? + private var lastExpansionTimestamp: Double? + private var didTransitionIn = false private var dismissed = false @@ -739,7 +741,7 @@ public final class WebAppController: ViewController, AttachmentContainable { guard let eventName = body["eventName"] as? String else { return } - + let currentTimestamp = CACurrentMediaTime() let eventData = (body["eventData"] as? String)?.data(using: .utf8) let json = try? JSONSerialization.jsonObject(with: eventData ?? Data(), options: []) as? [String: Any] @@ -804,7 +806,12 @@ public final class WebAppController: ViewController, AttachmentContainable { case "web_app_request_theme": self.sendThemeChangedEvent() case "web_app_expand": - controller.requestAttachmentMenuExpansion() + if let lastExpansionTimestamp = self.lastExpansionTimestamp, currentTimestamp < lastExpansionTimestamp + 1.0 { + + } else { + self.lastExpansionTimestamp = currentTimestamp + controller.requestAttachmentMenuExpansion() + } case "web_app_close": controller.dismiss() case "web_app_open_tg_link": @@ -846,7 +853,6 @@ public final class WebAppController: ViewController, AttachmentContainable { case "web_app_open_link": if let json = json, let url = json["url"] as? String { let tryInstantView = json["try_instant_view"] as? Bool ?? false - let currentTimestamp = CACurrentMediaTime() if let lastTouchTimestamp = self.webView?.lastTouchTimestamp, currentTimestamp < lastTouchTimestamp + 10.0 { self.webView?.lastTouchTimestamp = nil if tryInstantView { From 85ef0b00b9df15881079b560c341cc088ce31943 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 12 Feb 2024 05:49:39 +0400 Subject: [PATCH 13/14] Group boosts --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 ++ .../Sources/MediaPickerPlaceholderNode.swift | 21 ++++++++++++++++--- .../Sources/MediaPickerScreen.swift | 13 +++++++++--- .../ChatControllerOpenAttachmentMenu.swift | 20 +++++++++++++++++- 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 1077520e62..143fb6f956 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -11304,3 +11304,5 @@ Sorry for the inconvenience."; "Channel.AdminLog.MessageRemovedGroupEmojiPack" = "%@ removed group emoji pack"; "Group.Appearance.EmojiPackUpdated" = "Group emoji pack updated."; + +"Attachment.BoostToUnlock" = "Boost to Unlock"; diff --git a/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift index 3e9f7153f2..24e03f5f77 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift @@ -26,6 +26,7 @@ final class MediaPickerPlaceholderNode: ASDisplayNode { private var cameraTextNode: ImmediateTextNode private var cameraIconNode: ASImageNode + var boostPressed: () -> Void = {} var settingsPressed: () -> Void = {} var cameraPressed: () -> Void = {} @@ -76,7 +77,13 @@ final class MediaPickerPlaceholderNode: ASDisplayNode { self.addSubnode(self.animationNode) self.addSubnode(self.textNode) - if case .intro = self.content { + switch self.content { + case .bannedSendMedia(_, true): + self.addSubnode(self.buttonNode) + self.buttonNode.pressed = { [weak self] in + self?.boostPressed() + } + case .intro: self.addSubnode(self.titleNode) self.addSubnode(self.buttonNode) @@ -104,6 +111,8 @@ final class MediaPickerPlaceholderNode: ASDisplayNode { self.buttonNode.pressed = { [weak self] in self?.settingsPressed() } + default: + break } } @@ -128,7 +137,7 @@ final class MediaPickerPlaceholderNode: ASDisplayNode { let imageSpacing: CGFloat = 12.0 let textSpacing: CGFloat = 12.0 - let buttonSpacing: CGFloat = 15.0 + let buttonSpacing: CGFloat = 20.0 let cameraSpacing: CGFloat = 13.0 let imageHeight = layout.size.width < layout.size.height ? imageSize.height + imageSpacing : 0.0 @@ -137,7 +146,13 @@ final class MediaPickerPlaceholderNode: ASDisplayNode { self.buttonNode.updateTheme(SolidRoundedButtonTheme(theme: theme)) self.cameraIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/OpenCamera"), color: theme.list.itemAccentColor) } - self.buttonNode.title = strings.Attachment_OpenSettings + switch self.content { + case .intro: + self.buttonNode.title = strings.Attachment_OpenSettings + case .bannedSendMedia: + self.buttonNode.title = strings.Attachment_BoostToUnlock + } + let buttonWidth: CGFloat = 248.0 let buttonHeight = self.buttonNode.updateLayout(width: buttonWidth, transition: transition) diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 04eda3be56..cd210579e8 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -174,6 +174,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { private let chatLocation: ChatLocation? private let bannedSendPhotos: (Int32, Bool)? private let bannedSendVideos: (Int32, Bool)? + private let canBoostToUnrestrict: Bool private let subject: Subject private let saveEditedPhotos: Bool @@ -187,6 +188,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { public var presentTimerPicker: (@escaping (Int32) -> Void) -> Void = { _ in } public var presentWebSearch: (MediaGroupsScreen, Bool) -> Void = { _, _ in } public var getCaptionPanelView: () -> TGCaptionPanelView? = { return nil } + public var openBoost: () -> Void = { } public var customSelection: ((MediaPickerScreen, Any) -> Void)? = nil @@ -1324,7 +1326,10 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { if let current = self.placeholderNode { placeholderNode = current } else { - placeholderNode = MediaPickerPlaceholderNode(content: .bannedSendMedia(text: banDescription, canBoost: false)) + placeholderNode = MediaPickerPlaceholderNode(content: .bannedSendMedia(text: banDescription, canBoost: controller.canBoostToUnrestrict)) + placeholderNode.boostPressed = { [weak controller] in + controller?.openBoost() + } self.containerNode.insertSubnode(placeholderNode, aboveSubnode: self.gridNode) self.placeholderNode = placeholderNode @@ -1525,8 +1530,9 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { threadTitle: String?, chatLocation: ChatLocation?, isScheduledMessages: Bool = false, - bannedSendPhotos: (Int32, Bool)?, - bannedSendVideos: (Int32, Bool)?, + bannedSendPhotos: (Int32, Bool)? = nil, + bannedSendVideos: (Int32, Bool)? = nil, + canBoostToUnrestrict: Bool = false, subject: Subject, editingContext: TGMediaEditingContext? = nil, selectionContext: TGMediaSelectionContext? = nil, @@ -1545,6 +1551,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.isScheduledMessages = isScheduledMessages self.bannedSendPhotos = bannedSendPhotos self.bannedSendVideos = bannedSendVideos + self.canBoostToUnrestrict = canBoostToUnrestrict self.subject = subject self.saveEditedPhotos = saveEditedPhotos self.mainButtonState = mainButtonState diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index a72fd06e61..f519a12098 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -1070,7 +1070,25 @@ extension ChatControllerImpl { if case .scheduledMessages = self.presentationInterfaceState.subject { isScheduledMessages = true } - let controller = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), threadTitle: self.threadInfo?.title, chatLocation: self.chatLocation, isScheduledMessages: isScheduledMessages, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, subject: subject, saveEditedPhotos: saveEditedPhotos) + let controller = MediaPickerScreen( + context: self.context, + updatedPresentationData: self.updatedPresentationData, + peer: EnginePeer(peer), + threadTitle: self.threadInfo?.title, + chatLocation: self.chatLocation, + isScheduledMessages: isScheduledMessages, + bannedSendPhotos: bannedSendPhotos, + bannedSendVideos: bannedSendVideos, + canBoostToUnrestrict: (self.presentationInterfaceState.boostsToUnrestrict ?? 0) > 0 && bannedSendPhotos?.1 != true && bannedSendVideos?.1 != true, + subject: subject, + saveEditedPhotos: saveEditedPhotos + ) + controller.openBoost = { [weak self, weak controller] in + if let self { + controller?.dismiss() + self.interfaceInteraction?.openBoostToUnrestrict() + } + } let mediaPickerContext = controller.mediaPickerContext controller.openCamera = { [weak self] cameraView in self?.openCamera(cameraView: cameraView) From 1bbfc8da52b41aeedf68ed0e5b6635889685a2ca Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 12 Feb 2024 15:06:35 +0400 Subject: [PATCH 14/14] Group stories --- .../Sources/AccountContext.swift | 4 +++- .../StoryItemSetContainerComponent.swift | 1 + .../TelegramUI/Sources/ChatController.swift | 19 +++++++++++-------- .../Sources/TelegramRootController.swift | 9 +++++++-- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index f7d0186a4f..56ae3fa70b 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -821,11 +821,13 @@ public struct StoryCameraTransitionInCoordinator { public class MediaEditorTransitionOutExternalState { public var storyTarget: Stories.PendingTarget? + public var isForcedTarget: Bool public var isPeerArchived: Bool public var transitionOut: ((Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?)? - public init(storyTarget: Stories.PendingTarget?, isPeerArchived: Bool, transitionOut: ((Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?)?) { + public init(storyTarget: Stories.PendingTarget?, isForcedTarget: Bool, isPeerArchived: Bool, transitionOut: ((Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?)?) { self.storyTarget = storyTarget + self.isForcedTarget = isForcedTarget self.isPeerArchived = isPeerArchived self.transitionOut = transitionOut } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 7f0a044e26..7d79ac1fb3 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -5383,6 +5383,7 @@ public final class StoryItemSetContainerComponent: Component { let externalState = MediaEditorTransitionOutExternalState( storyTarget: nil, + isForcedTarget: false, isPeerArchived: false, transitionOut: nil ) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 2925b233eb..6ea3fb7ff7 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -966,14 +966,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { return true } - if let peer = peer as? TelegramChannel, peer.hasPermission(.changeInfo) { - let _ = (context.engine.peers.getChannelBoostStatus(peerId: peer.id) - |> deliverOnMainQueue).start(next: { [weak self] boostStatus in - guard let self else { - return - } - self.push(ChannelAppearanceScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peerId: peer.id, boostStatus: boostStatus)) - }) + if let peer = peer as? TelegramChannel { + if peer.flags.contains(.isCreator) || peer.adminRights?.rights.contains(.canChangeInfo) == true { + let _ = (context.engine.peers.getChannelBoostStatus(peerId: peer.id) + |> deliverOnMainQueue).start(next: { [weak self] boostStatus in + guard let self else { + return + } + self.push(ChannelAppearanceScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peerId: peer.id, boostStatus: boostStatus)) + }) + } return true } guard message.effectivelyIncoming(strongSelf.context.account.peerId), let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { @@ -16706,6 +16708,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let externalState = MediaEditorTransitionOutExternalState( storyTarget: nil, + isForcedTarget: false, isPeerArchived: false, transitionOut: nil ) diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index cabf003a6c..b753edea70 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -277,6 +277,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon let externalState = MediaEditorTransitionOutExternalState( storyTarget: nil, + isForcedTarget: customTarget != nil, isPeerArchived: false, transitionOut: nil ) @@ -522,13 +523,17 @@ public final class TelegramRootController: NavigationController, TelegramRootCon var viewControllers = self.viewControllers let archiveController = ChatListControllerImpl(context: context, location: .chatList(groupId: .archive), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false) - externalState.transitionOut = archiveController.storyCameraTransitionOut() + if !externalState.isForcedTarget { + externalState.transitionOut = archiveController.storyCameraTransitionOut() + } chatListController = archiveController viewControllers.insert(archiveController, at: 1) self.setViewControllers(viewControllers, animated: false) } else { chatListController = self.chatListController as? ChatListControllerImpl - externalState.transitionOut = chatListController?.storyCameraTransitionOut() + if !externalState.isForcedTarget { + externalState.transitionOut = chatListController?.storyCameraTransitionOut() + } } if let chatListController {