Merge commit '1bbfc8da52b41aeedf68ed0e5b6635889685a2ca'

This commit is contained in:
Isaac 2024-02-12 16:24:26 +04:00
commit 40246a3b11
46 changed files with 724 additions and 347 deletions

View File

@ -10988,6 +10988,8 @@ Sorry for the inconvenience.";
"ChannelBoost.MaxLevelReached.Text" = "**%1$@** reached **Level %2$@**."; "ChannelBoost.MaxLevelReached.Text" = "**%1$@** reached **Level %2$@**.";
"ChannelBoost.MoreBoostsNeeded.Boosted.Text" = "%@ needed to unlock new features."; "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.Delete" = "Delete Contact";
"ContactList.Context.Select" = "Select"; "ContactList.Context.Select" = "Select";
@ -11013,7 +11015,7 @@ Sorry for the inconvenience.";
"GroupBoost.EnableStoriesText" = "Your group needs %1$@ to enable posting stories."; "GroupBoost.EnableStoriesText" = "Your group needs %1$@ to enable posting stories.";
"GroupBoost.IncreaseLimitText" = "Your group needs %1$@ to post %2$@."; "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.AllMembers" = "All members";
"BoostGift.Group.OnlyNewMembers" = "Only new members"; "BoostGift.Group.OnlyNewMembers" = "Only new members";
@ -11276,3 +11278,31 @@ Sorry for the inconvenience.";
"Chat.Giveaway.Info.OtherChannelsAndGroups_1" = "**%@** other listed channel and group"; "Chat.Giveaway.Info.OtherChannelsAndGroups_1" = "**%@** other listed channel and group";
"Chat.Giveaway.Info.OtherChannelsAndGroups_any" = "**%@** other listed channels and groups"; "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.";
"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.";
"Channel.AdminLog.MessageChangedGroupEmojiPack" = "%@ changed group emoji pack";
"Channel.AdminLog.MessageRemovedGroupEmojiPack" = "%@ removed group emoji pack";
"Group.Appearance.EmojiPackUpdated" = "Group emoji pack updated.";
"Attachment.BoostToUnlock" = "Boost to Unlock";

View File

@ -821,11 +821,13 @@ public struct StoryCameraTransitionInCoordinator {
public class MediaEditorTransitionOutExternalState { public class MediaEditorTransitionOutExternalState {
public var storyTarget: Stories.PendingTarget? public var storyTarget: Stories.PendingTarget?
public var isForcedTarget: Bool
public var isPeerArchived: Bool public var isPeerArchived: Bool
public var transitionOut: ((Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?)? 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.storyTarget = storyTarget
self.isForcedTarget = isForcedTarget
self.isPeerArchived = isPeerArchived self.isPeerArchived = isPeerArchived
self.transitionOut = transitionOut self.transitionOut = transitionOut
} }
@ -959,7 +961,7 @@ public protocol SharedAccountContext: AnyObject {
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> 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 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 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 func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController

View File

@ -726,9 +726,9 @@ public final class Camera {
self.metrics = Camera.Metrics(model: DeviceModel.current) self.metrics = Camera.Metrics(model: DeviceModel.current)
let session = CameraSession() let session = CameraSession()
session.session.usesApplicationAudioSession = true
session.session.automaticallyConfiguresApplicationAudioSession = false session.session.automaticallyConfiguresApplicationAudioSession = false
session.session.automaticallyConfiguresCaptureDeviceForWideColor = false session.session.automaticallyConfiguresCaptureDeviceForWideColor = false
session.session.usesApplicationAudioSession = true
if let previewView { if let previewView {
previewView.setSession(session.session, autoConnect: !session.hasMultiCam) previewView.setSession(session.session, autoConnect: !session.hasMultiCam)
} }

View File

@ -309,7 +309,11 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
if let forwardInfo = message.forwardInfo, let author = forwardInfo.author { if let forwardInfo = message.forwardInfo, let author = forwardInfo.author {
messageText = strings.Message_GiveawayStartedOther(EnginePeer(author).compactDisplayTitle).string messageText = strings.Message_GiveawayStartedOther(EnginePeer(author).compactDisplayTitle).string
} else { } 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: case let results as TelegramMediaGiveawayResults:
if results.winnersCount == 0 { if results.winnersCount == 0 {

View File

@ -1683,7 +1683,7 @@ public final class ChatListNode: ListView {
guard let self else { guard let self else {
return 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) self.push?(controller)
}, openActiveSessions: { [weak self] in }, openActiveSessions: { [weak self] in
guard let self else { guard let self else {

View File

@ -26,6 +26,7 @@ final class MediaPickerPlaceholderNode: ASDisplayNode {
private var cameraTextNode: ImmediateTextNode private var cameraTextNode: ImmediateTextNode
private var cameraIconNode: ASImageNode private var cameraIconNode: ASImageNode
var boostPressed: () -> Void = {}
var settingsPressed: () -> Void = {} var settingsPressed: () -> Void = {}
var cameraPressed: () -> Void = {} var cameraPressed: () -> Void = {}
@ -76,7 +77,13 @@ final class MediaPickerPlaceholderNode: ASDisplayNode {
self.addSubnode(self.animationNode) self.addSubnode(self.animationNode)
self.addSubnode(self.textNode) 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.titleNode)
self.addSubnode(self.buttonNode) self.addSubnode(self.buttonNode)
@ -104,6 +111,8 @@ final class MediaPickerPlaceholderNode: ASDisplayNode {
self.buttonNode.pressed = { [weak self] in self.buttonNode.pressed = { [weak self] in
self?.settingsPressed() self?.settingsPressed()
} }
default:
break
} }
} }
@ -128,7 +137,7 @@ final class MediaPickerPlaceholderNode: ASDisplayNode {
let imageSpacing: CGFloat = 12.0 let imageSpacing: CGFloat = 12.0
let textSpacing: CGFloat = 12.0 let textSpacing: CGFloat = 12.0
let buttonSpacing: CGFloat = 15.0 let buttonSpacing: CGFloat = 20.0
let cameraSpacing: CGFloat = 13.0 let cameraSpacing: CGFloat = 13.0
let imageHeight = layout.size.width < layout.size.height ? imageSize.height + imageSpacing : 0.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.buttonNode.updateTheme(SolidRoundedButtonTheme(theme: theme))
self.cameraIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/OpenCamera"), color: theme.list.itemAccentColor) 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 buttonWidth: CGFloat = 248.0
let buttonHeight = self.buttonNode.updateLayout(width: buttonWidth, transition: transition) let buttonHeight = self.buttonNode.updateLayout(width: buttonWidth, transition: transition)

View File

@ -174,6 +174,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
private let chatLocation: ChatLocation? private let chatLocation: ChatLocation?
private let bannedSendPhotos: (Int32, Bool)? private let bannedSendPhotos: (Int32, Bool)?
private let bannedSendVideos: (Int32, Bool)? private let bannedSendVideos: (Int32, Bool)?
private let canBoostToUnrestrict: Bool
private let subject: Subject private let subject: Subject
private let saveEditedPhotos: Bool private let saveEditedPhotos: Bool
@ -187,6 +188,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
public var presentTimerPicker: (@escaping (Int32) -> Void) -> Void = { _ in } public var presentTimerPicker: (@escaping (Int32) -> Void) -> Void = { _ in }
public var presentWebSearch: (MediaGroupsScreen, Bool) -> Void = { _, _ in } public var presentWebSearch: (MediaGroupsScreen, Bool) -> Void = { _, _ in }
public var getCaptionPanelView: () -> TGCaptionPanelView? = { return nil } public var getCaptionPanelView: () -> TGCaptionPanelView? = { return nil }
public var openBoost: () -> Void = { }
public var customSelection: ((MediaPickerScreen, Any) -> Void)? = nil public var customSelection: ((MediaPickerScreen, Any) -> Void)? = nil
@ -1324,7 +1326,10 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
if let current = self.placeholderNode { if let current = self.placeholderNode {
placeholderNode = current placeholderNode = current
} else { } 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.containerNode.insertSubnode(placeholderNode, aboveSubnode: self.gridNode)
self.placeholderNode = placeholderNode self.placeholderNode = placeholderNode
@ -1525,8 +1530,9 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
threadTitle: String?, threadTitle: String?,
chatLocation: ChatLocation?, chatLocation: ChatLocation?,
isScheduledMessages: Bool = false, isScheduledMessages: Bool = false,
bannedSendPhotos: (Int32, Bool)?, bannedSendPhotos: (Int32, Bool)? = nil,
bannedSendVideos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)? = nil,
canBoostToUnrestrict: Bool = false,
subject: Subject, subject: Subject,
editingContext: TGMediaEditingContext? = nil, editingContext: TGMediaEditingContext? = nil,
selectionContext: TGMediaSelectionContext? = nil, selectionContext: TGMediaSelectionContext? = nil,
@ -1545,6 +1551,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.isScheduledMessages = isScheduledMessages self.isScheduledMessages = isScheduledMessages
self.bannedSendPhotos = bannedSendPhotos self.bannedSendPhotos = bannedSendPhotos
self.bannedSendVideos = bannedSendVideos self.bannedSendVideos = bannedSendVideos
self.canBoostToUnrestrict = canBoostToUnrestrict
self.subject = subject self.subject = subject
self.saveEditedPhotos = saveEditedPhotos self.saveEditedPhotos = saveEditedPhotos
self.mainButtonState = mainButtonState self.mainButtonState = mainButtonState

View File

@ -1210,7 +1210,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
navigationController.setViewControllers(controllers, animated: true) navigationController.setViewControllers(controllers, animated: true)
let title = presentationData.strings.BoostGift_GiveawayCreated_Title 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 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) let statsController = context.sharedContext.makeChannelStatsController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, boosts: true, boostStatus: nil)

View File

@ -647,33 +647,37 @@ private final class SheetContent: CombinedComponent {
} }
} else { } else {
textString = strings.ChannelBoost_MaxLevelReached_Text(peerName, "\(level)").string textString = strings.ChannelBoost_MaxLevelReached_Text(peerName, "\(level)").string
// let storiesString = strings.ChannelBoost_StoriesPerDay(Int32(level))
// textString = strings.ChannelBoost_MaxLevelReachedTextAuthor("\(level)", storiesString).string
} }
case let .user(mode): case let .user(mode):
switch mode { switch mode {
case let .groupPeer(_, peerBoostCount): case let .groupPeer(_, peerBoostCount):
let memberName = state.memberPeer?.compactDisplayTitle ?? "" 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 myBoostCount > 0 {
if let remaining { if let remaining, remaining != 0 {
let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining)) 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 { } else {
textString = "**\(memberName)** boosted the group **\(peerBoostCount)** times." textString = memberString
} }
} else { } 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).string)"
} }
isCurrent = true isCurrent = true
case let .unrestrict(unrestrictCount): 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 isCurrent = true
default: default:
if let remaining { if let remaining {
let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining)) let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining))
if myBoostCount > 0 { 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 { } else {
textString = strings.ChannelBoost_MoreBoostsNeeded_Text(peerName, boostsString).string textString = strings.ChannelBoost_MoreBoostsNeeded_Text(peerName, boostsString).string
} }
@ -1612,25 +1616,26 @@ public class PremiumBoostLevelsScreen: ViewController {
self.wrappingView.addSubview(self.containerView) self.wrappingView.addSubview(self.containerView)
self.containerView.addSubview(self.contentView) 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.containerView.addSubview(self.footerContainerView)
self.footerContainerView.addSubview(self.footerView) self.footerContainerView.addSubview(self.footerView)
}
if let status = controller.status, let myBoostStatus = controller.myBoostStatus {
var myBoostCount: Int32 = 0 var myBoostCount: Int32 = 0
var currentMyBoostCount: Int32 = 0 var currentMyBoostCount: Int32 = 0
var availableBoosts: [MyBoostStatus.Boost] = [] var availableBoosts: [MyBoostStatus.Boost] = []
var occupiedBoosts: [MyBoostStatus.Boost] = [] var occupiedBoosts: [MyBoostStatus.Boost] = []
if let myBoostStatus = controller.myBoostStatus {
for boost in myBoostStatus.boosts { for boost in myBoostStatus.boosts {
if let boostPeer = boost.peer { if let boostPeer = boost.peer {
if boostPeer.id == controller.peerId { if boostPeer.id == controller.peerId {
myBoostCount += 1 myBoostCount += 1
} else {
occupiedBoosts.append(boost)
}
} else { } else {
availableBoosts.append(boost) occupiedBoosts.append(boost)
} }
} else {
availableBoosts.append(boost)
} }
} }
@ -1868,23 +1873,10 @@ public class PremiumBoostLevelsScreen: ViewController {
status: controller.status, status: controller.status,
boostState: self.boostState, boostState: self.boostState,
boost: { [weak controller] in boost: { [weak controller] in
guard let controller, let navigationController = controller.navigationController else { guard let controller else {
return return
} }
controller.node.updateBoostState()
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)
}
}, },
copyLink: { [weak self, weak controller] link in copyLink: { [weak self, weak controller] link in
guard let self else { guard let self else {
@ -2217,6 +2209,13 @@ public class PremiumBoostLevelsScreen: ViewController {
let _ = (context.engine.peers.applyChannelBoost(peerId: peerId, slots: [availableBoost.slot]) let _ = (context.engine.peers.applyChannelBoost(peerId: peerId, slots: [availableBoost.slot])
|> deliverOnMainQueue).startStandalone(completed: { [weak self] in |> deliverOnMainQueue).startStandalone(completed: { [weak self] in
self?.updatedState.set(context.engine.peers.getChannelBoostStatus(peerId: peerId) 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 |> map { status in
if let status { if let status {
return InternalBoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: Int32(status.boosts + 1)) return InternalBoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: Int32(status.boosts + 1))
@ -2361,7 +2360,7 @@ public class PremiumBoostLevelsScreen: ViewController {
controller?.dismiss(animated: true, completion: nil) controller?.dismiss(animated: true, completion: nil)
Queue.mainQueue().after(0.4) { 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) navigationController.pushViewController(giftController, animated: true)
} }
} }
@ -2432,6 +2431,7 @@ public class PremiumBoostLevelsScreen: ViewController {
private var currentLayout: ContainerViewLayout? private var currentLayout: ContainerViewLayout?
public var boostStatusUpdated: (ChannelBoostStatus) -> Void = { _ in }
public var disposed: () -> Void = {} public var disposed: () -> Void = {}
public init( public init(
@ -2501,7 +2501,7 @@ public class PremiumBoostLevelsScreen: ViewController {
override open func viewDidDisappear(_ animated: Bool) { override open func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated) super.viewDidDisappear(animated)
self.node.updateIsVisible(isVisible: false) self.node.updateIsVisible(isVisible: false)
} }

View File

@ -239,7 +239,7 @@ public func PremiumBoostScreen(
dismissImpl?() dismissImpl?()
Queue.mainQueue().after(0.4) { 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) pushController(controller)
} }
}), }),

View File

@ -252,49 +252,53 @@ public class PremiumLimitDisplayComponent: Component {
rotationAngle = 0.26 rotationAngle = 0.26
} }
let to: CGFloat = self.badgeView.center.x
let positionAnimation = CABasicAnimation(keyPath: "position.x") let positionAnimation = CABasicAnimation(keyPath: "position.x")
positionAnimation.fromValue = NSValue(cgPoint: CGPoint(x: from ?? 0.0, y: 0.0)) 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.duration = 0.5
positionAnimation.fillMode = .forwards positionAnimation.fillMode = .forwards
positionAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) positionAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
self.badgeView.layer.add(positionAnimation, forKey: "appearance1") self.badgeView.layer.add(positionAnimation, forKey: "appearance1")
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z") if from != to {
rotateAnimation.fromValue = 0.0 as NSNumber let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotateAnimation.toValue = -rotationAngle as NSNumber rotateAnimation.fromValue = 0.0 as NSNumber
rotateAnimation.duration = 0.15 rotateAnimation.toValue = -rotationAngle as NSNumber
rotateAnimation.fillMode = .forwards rotateAnimation.duration = 0.15
rotateAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) rotateAnimation.fillMode = .forwards
rotateAnimation.isRemovedOnCompletion = false rotateAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
self.badgeView.layer.add(rotateAnimation, forKey: "appearance2") 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 !self.badgeView.isHidden { Queue.mainQueue().after(0.5, {
self.hapticFeedback.impact(.light) let bounceAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
} bounceAnimation.fromValue = -rotationAngle as NSNumber
bounceAnimation.toValue = 0.04 as NSNumber
Queue.mainQueue().after(0.2) { bounceAnimation.duration = 0.2
let returnAnimation = CABasicAnimation(keyPath: "transform.rotation.z") bounceAnimation.fillMode = .forwards
returnAnimation.fromValue = 0.04 as NSNumber bounceAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
returnAnimation.toValue = 0.0 as NSNumber bounceAnimation.isRemovedOnCompletion = false
returnAnimation.duration = 0.15 self.badgeView.layer.add(bounceAnimation, forKey: "appearance3")
returnAnimation.fillMode = .forwards self.badgeView.layer.removeAnimation(forKey: "appearance2")
returnAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn)
self.badgeView.layer.add(returnAnimation, forKey: "appearance4") if !self.badgeView.isHidden {
self.badgeView.layer.removeAnimation(forKey: "appearance3") 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 { if from == nil {
self.badgeView.alpha = 1.0 self.badgeView.alpha = 1.0

View File

@ -880,7 +880,7 @@ public class ReplaceBoostScreen: ViewController {
} }
let navigationController = self.navigationController let navigationController = self.navigationController
self.dismiss(animated: true, completion: { 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) navigationController?.pushViewController(giftController, animated: true)
}) })
} }

View File

@ -19,7 +19,7 @@ final class BoostHeaderItem: ItemListControllerHeaderItem {
let context: AccountContext let context: AccountContext
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings let strings: PresentationStrings
let status: ChannelBoostStatus let status: ChannelBoostStatus?
let title: String let title: String
let text: String let text: String
let openBoost: () -> Void let openBoost: () -> Void
@ -28,7 +28,7 @@ final class BoostHeaderItem: ItemListControllerHeaderItem {
let back: () -> Void let back: () -> Void
let updateStatusBar: (StatusBarStyle) -> 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.context = context
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
@ -44,7 +44,7 @@ final class BoostHeaderItem: ItemListControllerHeaderItem {
func isEqual(to: ItemListControllerHeaderItem) -> Bool { func isEqual(to: ItemListControllerHeaderItem) -> Bool {
if let item = to as? BoostHeaderItem { 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 { } else {
return false return false
} }
@ -78,7 +78,7 @@ final class BoostHeaderItemNode: ItemListControllerHeaderItemNode {
didSet { didSet {
self.updateItem() self.updateItem()
if let layout = self.validLayout { 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, strings: self.item.strings,
text: self.item.text, text: self.item.text,
status: self.item.status, status: self.item.status,
insets: layout.safeInsets,
openBoost: self.item.openBoost, openBoost: self.item.openBoost,
createGiveaway: self.item.createGiveaway, createGiveaway: self.item.createGiveaway,
openFeatures: self.item.openFeatures openFeatures: self.item.openFeatures
@ -205,7 +206,7 @@ final class BoostHeaderItemNode: ItemListControllerHeaderItemNode {
if let hostView = self.hostView { if let hostView = self.hostView {
let size = hostView.update( let size = hostView.update(
transition: .immediate, transition: Transition(transition),
component: component, component: component,
environment: {}, environment: {},
containerSize: containerSize containerSize: containerSize
@ -220,7 +221,7 @@ final class BoostHeaderItemNode: ItemListControllerHeaderItemNode {
self.whiteTitleNode.position = self.titleNode.position self.whiteTitleNode.position = self.titleNode.position
let backSize = self.backButton.update(key: .back, presentationData: self.item.context.sharedContext.currentPresentationData.with { $0 }, height: 44.0) 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.component = component
self.validLayout = layout self.validLayout = layout
@ -244,7 +245,8 @@ final class BoostHeaderItemNode: ItemListControllerHeaderItemNode {
private final class BoostHeaderComponent: CombinedComponent { private final class BoostHeaderComponent: CombinedComponent {
let strings: PresentationStrings let strings: PresentationStrings
let text: String let text: String
let status: ChannelBoostStatus let status: ChannelBoostStatus?
let insets: UIEdgeInsets
let openBoost: () -> Void let openBoost: () -> Void
let createGiveaway: () -> Void let createGiveaway: () -> Void
let openFeatures: () -> Void let openFeatures: () -> Void
@ -252,7 +254,8 @@ private final class BoostHeaderComponent: CombinedComponent {
public init( public init(
strings: PresentationStrings, strings: PresentationStrings,
text: String, text: String,
status: ChannelBoostStatus, status: ChannelBoostStatus?,
insets: UIEdgeInsets,
openBoost: @escaping () -> Void, openBoost: @escaping () -> Void,
createGiveaway: @escaping () -> Void, createGiveaway: @escaping () -> Void,
openFeatures: @escaping () -> Void openFeatures: @escaping () -> Void
@ -260,6 +263,7 @@ private final class BoostHeaderComponent: CombinedComponent {
self.strings = strings self.strings = strings
self.text = text self.text = text
self.status = status self.status = status
self.insets = insets
self.openBoost = openBoost self.openBoost = openBoost
self.createGiveaway = createGiveaway self.createGiveaway = createGiveaway
self.openFeatures = openFeatures self.openFeatures = openFeatures
@ -275,6 +279,9 @@ private final class BoostHeaderComponent: CombinedComponent {
if lhs.status != rhs.status { if lhs.status != rhs.status {
return false return false
} }
if lhs.insets != rhs.insets {
return false
}
return true return true
} }
@ -290,8 +297,9 @@ private final class BoostHeaderComponent: CombinedComponent {
return { context in return { context in
let size = context.availableSize let size = context.availableSize
let sideInset: CGFloat = 16.0
let component = context.component let component = context.component
let sideInset: CGFloat = 16.0 + component.insets.left
let background = background.update( let background = background.update(
component: PremiumGradientBackgroundComponent( 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)) .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 let position: CGFloat
if let nextLevelBoosts = component.status.nextLevelBoosts { if let status = component.status {
position = CGFloat(component.status.boosts - component.status.currentLevelBoosts) / CGFloat(nextLevelBoosts - component.status.currentLevelBoosts) if let nextLevelBoosts = status.nextLevelBoosts {
position = CGFloat(status.boosts - status.currentLevelBoosts) / CGFloat(nextLevelBoosts - status.currentLevelBoosts)
} else {
position = 1.0
}
boosts = status.boosts
} else { } else {
position = 1.0 boosts = 0
position = 0.0
} }
let inactiveText = component.strings.ChannelBoost_Level("\(level)").string let inactiveText = component.strings.ChannelBoost_Level("\(level)").string
@ -343,7 +358,7 @@ private final class BoostHeaderComponent: CombinedComponent {
activeValue: activeText, activeValue: activeText,
activeTitleColor: UIColor(rgb: 0x6f8fff), activeTitleColor: UIColor(rgb: 0x6f8fff),
badgeIconName: "Premium/Boost", badgeIconName: "Premium/Boost",
badgeText: "\(component.status.boosts)", badgeText: "\(boosts)",
badgePosition: position, badgePosition: position,
badgeGraphPosition: position, badgeGraphPosition: position,
invertProgress: true, invertProgress: true,

View File

@ -1046,7 +1046,7 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
return entries return entries
} }
public func channelStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, section: ChannelStatsSection = .stats, boostStatus: ChannelBoostStatus? = nil) -> ViewController { public func channelStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = 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 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 stateValue = Atomic(value: ChannelStatsControllerState(section: section, boostersExpanded: false, moreBoostersDisplayed: 0, giftsSelected: false))
let updateState: ((ChannelStatsControllerState) -> ChannelStatsControllerState) -> Void = { f in let updateState: ((ChannelStatsControllerState) -> ChannelStatsControllerState) -> Void = { f in
@ -1087,12 +1087,16 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
}) })
dataPromise.set(.single(nil) |> then(dataSignal)) dataPromise.set(.single(nil) |> then(dataSignal))
let boostData: Signal<ChannelBoostStatus?, NoError> let boostDataPromise = Promise<ChannelBoostStatus?>()
if let boostStatus { boostDataPromise.set(.single(boostStatus) |> then(context.engine.peers.getChannelBoostStatus(peerId: peerId)))
boostData = .single(boostStatus)
} else { actionsDisposable.add((boostDataPromise.get()
boostData = .single(nil) |> then(context.engine.peers.getChannelBoostStatus(peerId: peerId)) |> deliverOnMainQueue).start(next: { boostStatus in
} if let boostStatus, let boostStatusUpdated {
boostStatusUpdated(boostStatus)
}
}))
let boostsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: false) let boostsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: false)
let giftsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: true) let giftsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: true)
@ -1253,7 +1257,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
dataPromise.get(), dataPromise.get(),
messagesPromise.get(), messagesPromise.get(),
storiesPromise.get(), storiesPromise.get(),
boostData, boostDataPromise.get(),
boostsContext.state, boostsContext.state,
giftsContext.state, giftsContext.state,
longLoadingSignal longLoadingSignal
@ -1307,9 +1311,9 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
var headerItem: BoostHeaderItem? var headerItem: BoostHeaderItem?
var leftNavigationButton: ItemListNavigationButton? var leftNavigationButton: ItemListNavigationButton?
var boostsOnly = false var boostsOnly = false
if isGroup, section == .boosts, let boostStatus { if isGroup, section == .boosts {
title = .text("") 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) openBoostImpl?(false)
}, createGiveaway: { }, createGiveaway: {
arguments.openGifts() arguments.openGifts()
@ -1508,13 +1512,18 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
guard let boostStatus, let myBoostStatus else { guard let boostStatus, let myBoostStatus else {
return return
} }
boostDataPromise.set(.single(boostStatus))
let boostController = PremiumBoostLevelsScreen( let boostController = PremiumBoostLevelsScreen(
context: context, context: context,
peerId: peerId, peerId: peerId,
mode: .user(mode: .current), mode: .owner(subject: nil),
status: boostStatus, status: boostStatus,
myBoostStatus: myBoostStatus myBoostStatus: myBoostStatus
) )
boostController.boostStatusUpdated = { boostStatus in
boostDataPromise.set(.single(boostStatus))
}
controller?.push(boostController) controller?.push(boostController)
}) })
} }

View File

@ -566,7 +566,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1328256121] = { return Api.MessageReactions.parse_messageReactions($0) } dict[1328256121] = { return Api.MessageReactions.parse_messageReactions($0) }
dict[-2083123262] = { return Api.MessageReplies.parse_messageReplies($0) } dict[-2083123262] = { return Api.MessageReplies.parse_messageReplies($0) }
dict[-1346631205] = { return Api.MessageReplyHeader.parse_messageReplyHeader($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[1163625789] = { return Api.MessageViews.parse_messageViews($0) }
dict[975236280] = { return Api.MessagesFilter.parse_inputMessagesFilterChatPhotos($0) } dict[975236280] = { return Api.MessagesFilter.parse_inputMessagesFilterChatPhotos($0) }
dict[-530392189] = { return Api.MessagesFilter.parse_inputMessagesFilterContacts($0) } dict[-530392189] = { return Api.MessagesFilter.parse_inputMessagesFilterContacts($0) }
@ -833,7 +833,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[2008112412] = { return Api.StickerSetCovered.parse_stickerSetNoCovered($0) } dict[2008112412] = { return Api.StickerSetCovered.parse_stickerSetNoCovered($0) }
dict[1898850301] = { return Api.StoriesStealthMode.parse_storiesStealthMode($0) } dict[1898850301] = { return Api.StoriesStealthMode.parse_storiesStealthMode($0) }
dict[-1205411504] = { return Api.StoryFwdHeader.parse_storyFwdHeader($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[1374088783] = { return Api.StoryItem.parse_storyItemDeleted($0) }
dict[-5388013] = { return Api.StoryItem.parse_storyItemSkipped($0) } dict[-5388013] = { return Api.StoryItem.parse_storyItemSkipped($0) }
dict[1620104917] = { return Api.StoryReaction.parse_storyReaction($0) } dict[1620104917] = { return Api.StoryReaction.parse_storyReaction($0) }

View File

@ -323,7 +323,7 @@ public extension Api {
public extension Api { public extension Api {
indirect enum MessageReplyHeader: TypeConstructorDescription { 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 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) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -345,11 +345,11 @@ public extension Api {
}} }}
if Int(flags) & Int(1 << 10) != 0 {serializeInt32(quoteOffset!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(quoteOffset!, buffer: buffer, boxed: false)}
break break
case .messageReplyStoryHeader(let userId, let storyId): case .messageReplyStoryHeader(let peer, let storyId):
if boxed { if boxed {
buffer.appendInt32(-1667711039) buffer.appendInt32(240843065)
} }
serializeInt64(userId, buffer: buffer, boxed: false) peer.serialize(buffer, true)
serializeInt32(storyId, buffer: buffer, boxed: false) serializeInt32(storyId, buffer: buffer, boxed: false)
break break
} }
@ -359,8 +359,8 @@ public extension Api {
switch self { switch self {
case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyFrom, let replyMedia, let replyToTopId, let quoteText, let quoteEntities, let quoteOffset): 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)]) 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): case .messageReplyStoryHeader(let peer, let storyId):
return ("messageReplyStoryHeader", [("userId", userId as Any), ("storyId", storyId as Any)]) 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? { public static func parse_messageReplyStoryHeader(_ reader: BufferReader) -> MessageReplyHeader? {
var _1: Int64? var _1: Api.Peer?
_1 = reader.readInt64() if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: Int32? var _2: Int32?
_2 = reader.readInt32() _2 = reader.readInt32()
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
if _c1 && _c2 { if _c1 && _c2 {
return Api.MessageReplyHeader.messageReplyStoryHeader(userId: _1!, storyId: _2!) return Api.MessageReplyHeader.messageReplyStoryHeader(peer: _1!, storyId: _2!)
} }
else { else {
return nil return nil

View File

@ -1034,19 +1034,20 @@ public extension Api {
} }
public extension Api { public extension Api {
indirect enum StoryItem: TypeConstructorDescription { 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 storyItemDeleted(id: Int32)
case storyItemSkipped(flags: Int32, id: Int32, date: Int32, expireDate: Int32) case storyItemSkipped(flags: Int32, id: Int32, date: Int32, expireDate: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { 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 { if boxed {
buffer.appendInt32(-1352440415) buffer.appendInt32(2041735716)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false)
serializeInt32(date, 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)} if Int(flags) & Int(1 << 17) != 0 {fwdFrom!.serialize(buffer, true)}
serializeInt32(expireDate, buffer: buffer, boxed: false) serializeInt32(expireDate, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(caption!, 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)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { 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):
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)]) 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): case .storyItemDeleted(let id):
return ("storyItemDeleted", [("id", id as Any)]) return ("storyItemDeleted", [("id", id as Any)])
case .storyItemSkipped(let flags, let id, let date, let expireDate): case .storyItemSkipped(let flags, let id, let date, let expireDate):
@ -1105,52 +1106,57 @@ public extension Api {
_2 = reader.readInt32() _2 = reader.readInt32()
var _3: Int32? var _3: Int32?
_3 = reader.readInt32() _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() { 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? var _6: Int32?
_5 = reader.readInt32() _6 = reader.readInt32()
var _6: String? var _7: String?
if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } if Int(_1!) & Int(1 << 0) != 0 {_7 = parseString(reader) }
var _7: [Api.MessageEntity]? var _8: [Api.MessageEntity]?
if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { 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() { 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() { 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() { 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() { 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() { 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 _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 17) == 0) || _4 != nil let _c4 = (Int(_1!) & Int(1 << 18) == 0) || _4 != nil
let _c5 = _5 != nil let _c5 = (Int(_1!) & Int(1 << 17) == 0) || _5 != nil
let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil
let _c8 = _8 != nil let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 14) == 0) || _9 != nil let _c9 = _9 != nil
let _c10 = (Int(_1!) & Int(1 << 2) == 0) || _10 != nil let _c10 = (Int(_1!) & Int(1 << 14) == 0) || _10 != nil
let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil let _c11 = (Int(_1!) & Int(1 << 2) == 0) || _11 != nil
let _c12 = (Int(_1!) & Int(1 << 15) == 0) || _12 != nil let _c12 = (Int(_1!) & Int(1 << 3) == 0) || _12 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { let _c13 = (Int(_1!) & Int(1 << 15) == 0) || _13 != nil
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) 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 { else {
return nil return nil

View File

@ -200,8 +200,8 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
if let replyTo = replyTo { if let replyTo = replyTo {
switch replyTo { switch replyTo {
case let .messageReplyStoryHeader(userId, _): case let .messageReplyStoryHeader(peer, _):
let storyPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) let storyPeerId = peer.peerId
if !result.contains(storyPeerId) { if !result.contains(storyPeerId) {
result.append(storyPeerId) result.append(storyPeerId)
} }
@ -668,8 +668,8 @@ extension StoreMessage {
if let replyHeader = replyHeader { if let replyHeader = replyHeader {
attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote, isQuote: isQuote)) attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote, isQuote: isQuote))
} }
case let .messageReplyStoryHeader(userId, storyId): case let .messageReplyStoryHeader(peer, storyId):
attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: storyId))) attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: peer.peerId, id: storyId)))
} }
} }
@ -952,8 +952,8 @@ extension StoreMessage {
} else if let replyHeader = replyHeader { } else if let replyHeader = replyHeader {
attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote, isQuote: isQuote)) attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote, isQuote: isQuote))
} }
case let .messageReplyStoryHeader(userId, storyId): case let .messageReplyStoryHeader(peer, storyId):
attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: storyId))) attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: peer.peerId, id: storyId)))
} }
} else { } else {
switch action { switch action {

View File

@ -4712,7 +4712,8 @@ func replayFinalState(
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: updatedReaction, myReaction: updatedReaction,
forwardInfo: item.forwardInfo forwardInfo: item.forwardInfo,
authorId: item.authorId
)) ))
if let entry = CodableEntry(updatedItem) { if let entry = CodableEntry(updatedItem) {
updatedPeerEntries[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: item.expirationTimestamp, isCloseFriends: item.isCloseFriends) updatedPeerEntries[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: item.expirationTimestamp, isCloseFriends: item.isCloseFriends)
@ -4745,7 +4746,8 @@ func replayFinalState(
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: MessageReaction.Reaction(apiReaction: reaction), myReaction: MessageReaction.Reaction(apiReaction: reaction),
forwardInfo: item.forwardInfo forwardInfo: item.forwardInfo,
authorId: item.authorId
)) ))
if let entry = CodableEntry(updatedItem) { if let entry = CodableEntry(updatedItem) {
transaction.setStory(id: StoryId(peerId: peerId, id: id), value: entry) transaction.setStory(id: StoryId(peerId: peerId, id: id), value: entry)

View File

@ -349,7 +349,8 @@ private final class StoryStatsPublicForwardsContextImpl {
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, 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)) resultForwards.append(.story(EnginePeer(peer), mappedItem))
} }

View File

@ -556,7 +556,8 @@ public final class EngineStoryViewListContext {
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, 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) storyStats: transaction.getPeerStoryStats(peerId: peer.id)
))) )))
@ -595,7 +596,8 @@ public final class EngineStoryViewListContext {
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, myReaction: item.myReaction,
forwardInfo: item.forwardInfo forwardInfo: item.forwardInfo,
authorId: item.authorId
)) ))
if let entry = CodableEntry(updatedItem) { if let entry = CodableEntry(updatedItem) {
transaction.setStory(id: StoryId(peerId: account.peerId, id: storyId), value: entry) transaction.setStory(id: StoryId(peerId: account.peerId, id: storyId), value: entry)
@ -634,7 +636,8 @@ public final class EngineStoryViewListContext {
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, myReaction: item.myReaction,
forwardInfo: item.forwardInfo forwardInfo: item.forwardInfo,
authorId: item.authorId
)) ))
if let entry = CodableEntry(updatedItem) { if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends) 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, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, 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) storyStats: transaction.getPeerStoryStats(peerId: peer.id)
))) )))

View File

@ -261,6 +261,7 @@ public enum Stories {
case isMy case isMy
case myReaction case myReaction
case forwardInfo case forwardInfo
case authorId
} }
public let id: Int32 public let id: Int32
@ -284,6 +285,7 @@ public enum Stories {
public let isMy: Bool public let isMy: Bool
public let myReaction: MessageReaction.Reaction? public let myReaction: MessageReaction.Reaction?
public let forwardInfo: ForwardInfo? public let forwardInfo: ForwardInfo?
public let authorId: PeerId?
public init( public init(
id: Int32, id: Int32,
@ -306,7 +308,8 @@ public enum Stories {
isEdited: Bool, isEdited: Bool,
isMy: Bool, isMy: Bool,
myReaction: MessageReaction.Reaction?, myReaction: MessageReaction.Reaction?,
forwardInfo: ForwardInfo? forwardInfo: ForwardInfo?,
authorId: PeerId?
) { ) {
self.id = id self.id = id
self.timestamp = timestamp self.timestamp = timestamp
@ -329,6 +332,7 @@ public enum Stories {
self.isMy = isMy self.isMy = isMy
self.myReaction = myReaction self.myReaction = myReaction
self.forwardInfo = forwardInfo self.forwardInfo = forwardInfo
self.authorId = authorId
} }
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
@ -367,6 +371,7 @@ public enum Stories {
self.isMy = try container.decodeIfPresent(Bool.self, forKey: .isMy) ?? false self.isMy = try container.decodeIfPresent(Bool.self, forKey: .isMy) ?? false
self.myReaction = try container.decodeIfPresent(MessageReaction.Reaction.self, forKey: .myReaction) self.myReaction = try container.decodeIfPresent(MessageReaction.Reaction.self, forKey: .myReaction)
self.forwardInfo = try container.decodeIfPresent(ForwardInfo.self, forKey: .forwardInfo) 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 { public func encode(to encoder: Encoder) throws {
@ -407,6 +412,7 @@ public enum Stories {
try container.encode(self.isMy, forKey: .isMy) try container.encode(self.isMy, forKey: .isMy)
try container.encodeIfPresent(self.myReaction, forKey: .myReaction) try container.encodeIfPresent(self.myReaction, forKey: .myReaction)
try container.encodeIfPresent(self.forwardInfo, forKey: .forwardInfo) try container.encodeIfPresent(self.forwardInfo, forKey: .forwardInfo)
try container.encodeIfPresent(self.authorId?.toInt64(), forKey: .authorId)
} }
public static func ==(lhs: Item, rhs: Item) -> Bool { public static func ==(lhs: Item, rhs: Item) -> Bool {
@ -485,6 +491,9 @@ public enum Stories {
if lhs.forwardInfo != rhs.forwardInfo { if lhs.forwardInfo != rhs.forwardInfo {
return false return false
} }
if lhs.authorId != rhs.authorId {
return false
}
return true return true
} }
} }
@ -1173,7 +1182,7 @@ func _internal_uploadStoryImpl(
for update in updates.allUpdates { for update in updates.allUpdates {
if case let .updateStory(_, story) = update { if case let .updateStory(_, story) = update {
switch story { switch story {
case let .storyItem(_, idValue, _, _, _, _, _, media, _, _, _, _): case let .storyItem(_, idValue, _, fromId, _, _, _, _, media, _, _, _, _):
if let parsedStory = Stories.StoredItem(apiStoryItem: story, peerId: toPeerId, transaction: transaction) { if let parsedStory = Stories.StoredItem(apiStoryItem: story, peerId: toPeerId, transaction: transaction) {
var items = transaction.getStoryItems(peerId: toPeerId) var items = transaction.getStoryItems(peerId: toPeerId)
var updatedItems: [Stories.Item] = [] var updatedItems: [Stories.Item] = []
@ -1199,7 +1208,8 @@ func _internal_uploadStoryImpl(
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, myReaction: item.myReaction,
forwardInfo: item.forwardInfo forwardInfo: item.forwardInfo,
authorId: fromId?.peerId
) )
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items.append(StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)) 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 { for update in updates.allUpdates {
if case let .updateStory(_, story) = update { if case let .updateStory(_, story) = update {
switch story { switch story {
case let .storyItem(_, _, _, _, _, _, _, media, _, _, _, _): case let .storyItem(_, _, _, _, _, _, _, _, media, _, _, _, _):
let (parsedMedia, _, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, account.peerId) let (parsedMedia, _, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, account.peerId)
if let parsedMedia = parsedMedia, let originalMedia = originalMedia { if let parsedMedia = parsedMedia, let originalMedia = originalMedia {
applyMediaResourceChanges(from: originalMedia, to: parsedMedia, postbox: account.postbox, force: false) 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, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, myReaction: item.myReaction,
forwardInfo: item.forwardInfo forwardInfo: item.forwardInfo,
authorId: item.authorId
) )
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
transaction.setStory(id: storyId, value: entry) transaction.setStory(id: storyId, value: entry)
@ -1412,7 +1423,8 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, myReaction: item.myReaction,
forwardInfo: item.forwardInfo forwardInfo: item.forwardInfo,
authorId: item.authorId
) )
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends) 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, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, myReaction: item.myReaction,
forwardInfo: item.forwardInfo forwardInfo: item.forwardInfo,
authorId: item.authorId
) )
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends) 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, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, myReaction: item.myReaction,
forwardInfo: item.forwardInfo forwardInfo: item.forwardInfo,
authorId: item.authorId
) )
updatedItems.append(updatedItem) updatedItems.append(updatedItem)
} }
@ -1668,7 +1682,7 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In
extension Api.StoryItem { extension Api.StoryItem {
var id: Int32 { var id: Int32 {
switch self { switch self {
case let .storyItem(_, id, _, _, _, _, _, _, _, _, _, _): case let .storyItem(_, id, _, _, _, _, _, _, _, _, _, _, _):
return id return id
case let .storyItemDeleted(id): case let .storyItemDeleted(id):
return id return id
@ -1731,7 +1745,7 @@ extension Stories.Item.ForwardInfo {
extension Stories.StoredItem { extension Stories.StoredItem {
init?(apiStoryItem: Api.StoryItem, existingItem: Stories.Item? = nil, peerId: PeerId, transaction: Transaction) { init?(apiStoryItem: Api.StoryItem, existingItem: Stories.Item? = nil, peerId: PeerId, transaction: Transaction) {
switch apiStoryItem { 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) let (parsedMedia, _, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
if let parsedMedia = parsedMedia { if let parsedMedia = parsedMedia {
var parsedPrivacy: Stories.Item.Privacy? var parsedPrivacy: Stories.Item.Privacy?
@ -1840,7 +1854,8 @@ extension Stories.StoredItem {
isEdited: isEdited, isEdited: isEdited,
isMy: mergedIsMy, isMy: mergedIsMy,
myReaction: mergedMyReaction, myReaction: mergedMyReaction,
forwardInfo: mergedForwardInfo forwardInfo: mergedForwardInfo,
authorId: fromId?.peerId
) )
self = .item(item) self = .item(item)
} else { } else {
@ -1916,7 +1931,8 @@ func _internal_getStoryById(accountPeerId: PeerId, postbox: Postbox, network: Ne
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, 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, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: reaction, myReaction: reaction,
forwardInfo: item.forwardInfo forwardInfo: item.forwardInfo,
authorId: item.authorId
)) ))
updatedItemValue = updatedItem updatedItemValue = updatedItem
if let entry = CodableEntry(updatedItem) { if let entry = CodableEntry(updatedItem) {
@ -2419,7 +2436,8 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: reaction, myReaction: reaction,
forwardInfo: item.forwardInfo forwardInfo: item.forwardInfo,
authorId: item.authorId
)) ))
updatedItemValue = updatedItem updatedItemValue = updatedItem
if let entry = CodableEntry(updatedItem) { if let entry = CodableEntry(updatedItem) {

View File

@ -78,8 +78,9 @@ public final class EngineStoryItem: Equatable {
public let isMy: Bool public let isMy: Bool
public let myReaction: MessageReaction.Reaction? public let myReaction: MessageReaction.Reaction?
public let forwardInfo: ForwardInfo? 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.id = id
self.timestamp = timestamp self.timestamp = timestamp
self.expirationTimestamp = expirationTimestamp self.expirationTimestamp = expirationTimestamp
@ -102,6 +103,7 @@ public final class EngineStoryItem: Equatable {
self.isMy = isMy self.isMy = isMy
self.myReaction = myReaction self.myReaction = myReaction
self.forwardInfo = forwardInfo self.forwardInfo = forwardInfo
self.author = author
} }
public static func ==(lhs: EngineStoryItem, rhs: EngineStoryItem) -> Bool { public static func ==(lhs: EngineStoryItem, rhs: EngineStoryItem) -> Bool {
@ -171,6 +173,9 @@ public final class EngineStoryItem: Equatable {
if lhs.forwardInfo != rhs.forwardInfo { if lhs.forwardInfo != rhs.forwardInfo {
return false return false
} }
if lhs.author != rhs.author {
return false
}
return true return true
} }
} }
@ -223,7 +228,8 @@ public extension EngineStoryItem {
isEdited: self.isEdited, isEdited: self.isEdited,
isMy: self.isMy, isMy: self.isMy,
myReaction: self.myReaction, 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, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, 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) items.append(mappedItem)
@ -745,7 +752,8 @@ public final class PeerStoryListContext {
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, 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) storyItems.append(mappedItem)
} }
@ -839,6 +847,11 @@ public final class PeerStoryListContext {
peers[peer.id] = peer peers[peer.id] = peer
} }
} }
if let peerId = item.authorId {
if let peer = transaction.getPeer(peerId) {
peers[peer.id] = peer
}
}
} }
} }
default: default:
@ -907,7 +920,8 @@ public final class PeerStoryListContext {
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, 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 finalUpdatedState = updatedState
} }
@ -955,7 +969,8 @@ public final class PeerStoryListContext {
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, 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 finalUpdatedState = updatedState
} else { } else {
@ -1005,7 +1020,8 @@ public final class PeerStoryListContext {
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, 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 updatedState.items.sort(by: { lhs, rhs in
return lhs.timestamp > rhs.timestamp return lhs.timestamp > rhs.timestamp
@ -1051,7 +1067,8 @@ public final class PeerStoryListContext {
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, 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 updatedState.items.sort(by: { lhs, rhs in
return lhs.timestamp > rhs.timestamp return lhs.timestamp > rhs.timestamp
@ -1221,7 +1238,8 @@ public final class PeerExpiringStoryListContext {
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, 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)) items.append(.item(mappedItem))
} }

View File

@ -1249,7 +1249,8 @@ public extension TelegramEngine {
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, myReaction: item.myReaction,
forwardInfo: item.forwardInfo forwardInfo: item.forwardInfo,
authorId: item.authorId
)) ))
if let entry = CodableEntry(updatedItem) { if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends) currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)

View File

@ -232,6 +232,7 @@ private struct ApplicationSpecificNoticeKeys {
private static let botGameNoticeNamespace: Int32 = 7 private static let botGameNoticeNamespace: Int32 = 7
private static let peerInviteRequestsNamespace: Int32 = 8 private static let peerInviteRequestsNamespace: Int32 = 8
private static let dismissedPremiumGiftNamespace: Int32 = 9 private static let dismissedPremiumGiftNamespace: Int32 = 9
private static let groupEmojiPackNamespace: Int32 = 9
static func inlineBotLocationRequestNotice(peerId: PeerId) -> NoticeEntryKey { static func inlineBotLocationRequestNotice(peerId: PeerId) -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: inlineBotLocationRequestNamespace), key: noticeKey(peerId: peerId, key: 0)) 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)) 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 { static func forcedPasswordSetup() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.secretChatInlineBotUsage.key) return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.secretChatInlineBotUsage.key)
} }
@ -1582,6 +1587,33 @@ public struct ApplicationSpecificNotice {
} }
} }
public static func groupEmojiPackSuggestion(accountManager: AccountManager<TelegramAccountManagerTypes>, peerId: PeerId) -> Signal<Int32, NoError> {
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<TelegramAccountManagerTypes>, peerId: PeerId, count: Int32 = 1) -> Signal<Int32, NoError> {
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<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> { public static func getSendWhenOnlineTip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
return accountManager.transaction { transaction -> Int32 in return accountManager.transaction { transaction -> Int32 in
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.sendWhenOnlineTip())?.get(ApplicationSpecificCounterNotice.self) { if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.sendWhenOnlineTip())?.get(ApplicationSpecificCounterNotice.self) {

View File

@ -2274,7 +2274,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
} }
nameNodeOriginY = headerSize.height 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 headerSize.height += nameNodeSizeApply.0.height
} }

View File

@ -2129,14 +2129,14 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
var entities: [MessageTextEntity] = [] var entities: [MessageTextEntity] = []
if new != nil { 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 { if index == 0, let author = author {
return [.TextMention(peerId: author.id)] return [.TextMention(peerId: author.id)]
} }
return [] return []
}, to: &text, entities: &entities) }, to: &text, entities: &entities)
} else { } 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 { if index == 0, let author = author {
return [.TextMention(peerId: author.id)] return [.TextMention(peerId: author.id)]
} }

View File

@ -390,7 +390,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
} }
return true return true
} }
public var useExternalSearchContainer: Bool = false public var useExternalSearchContainer: Bool = false
private var gifContext: GifContext? { private var gifContext: GifContext? {
@ -2133,6 +2133,12 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
strongSelf.interaction?.presentGlobalOverlayController(contextController, nil) 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 { private final class ContextControllerContentSourceImpl: ContextControllerContentSource {

View File

@ -23,6 +23,7 @@ swift_library(
"//submodules/SearchBarNode:SearchBarNode", "//submodules/SearchBarNode:SearchBarNode",
"//submodules/SearchUI:SearchUI", "//submodules/SearchUI:SearchUI",
"//submodules/MergeLists:MergeLists", "//submodules/MergeLists:MergeLists",
"//submodules/UndoUI:UndoUI",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -344,6 +344,7 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode {
if case .searching = item.content { if case .searching = item.content {
strongSelf.activityIndicator.isHidden = false strongSelf.activityIndicator.isHidden = false
strongSelf.imageNode.isHidden = true
} else { } else {
strongSelf.activityIndicator.isHidden = true strongSelf.activityIndicator.isHidden = true
} }
@ -351,6 +352,7 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode {
if case .found = item.content { if case .found = item.content {
strongSelf.removeButtonIcon.isHidden = false strongSelf.removeButtonIcon.isHidden = false
strongSelf.removeButton.isHidden = false strongSelf.removeButton.isHidden = false
strongSelf.imageNode.isHidden = false
} else { } else {
strongSelf.removeButtonIcon.isHidden = true strongSelf.removeButtonIcon.isHidden = true
strongSelf.removeButton.isHidden = true strongSelf.removeButton.isHidden = true

View File

@ -11,6 +11,7 @@ import PresentationDataUtils
import AccountContext import AccountContext
import StickerPackPreviewUI import StickerPackPreviewUI
import ItemListStickerPackItem import ItemListStickerPackItem
import UndoUI
private final class GroupStickerPackSetupControllerArguments { private final class GroupStickerPackSetupControllerArguments {
let context: AccountContext let context: AccountContext
@ -343,6 +344,20 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres
initialData.set(.single(.noData)) 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<CombinedView>() let stickerPacks = Promise<CombinedView>()
stickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [isEmoji ? Namespaces.ItemCollection.CloudEmojiPacks : Namespaces.ItemCollection.CloudStickerPacks])])) 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 { if searchText.isEmpty {
return .single((searchText, .none)) return .single((searchText, .none))
} else if case let .data(data) = initialData, searchText.lowercased() == data.info.shortName { } 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)))) return .single((searchText, .found(StickerPackData(info: data.info, item: data.item))))
} else { } else {
let namespace = isEmoji ? Namespaces.ItemCollection.CloudEmojiPacks : Namespaces.ItemCollection.CloudStickerPacks let namespace = isEmoji ? Namespaces.ItemCollection.CloudEmojiPacks : Namespaces.ItemCollection.CloudStickerPacks
@ -378,6 +396,13 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres
case let .result(info, items, _): case let .result(info, items, _):
return .single((searchText, .found(StickerPackData(info: info, item: items.first)))) 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 { } else {
@ -385,7 +410,6 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres
} }
}) })
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var navigateToChatControllerImpl: ((PeerId) -> Void)? var navigateToChatControllerImpl: ((PeerId) -> Void)?
var dismissInputImpl: (() -> Void)? var dismissInputImpl: (() -> Void)?
var dismissImpl: (() -> Void)? var dismissImpl: (() -> Void)?
@ -402,15 +426,13 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres
let arguments = GroupStickerPackSetupControllerArguments(context: context, selectStickerPack: { info in let arguments = GroupStickerPackSetupControllerArguments(context: context, selectStickerPack: { info in
searchText.set(info.shortName) searchText.set(info.shortName)
if let completion { completionImpl?(info)
completion(info)
}
}, openStickerPack: { info in }, openStickerPack: { info in
presentStickerPackController?(info) presentStickerPackController?(info)
}, updateSearchText: { text in }, updateSearchText: { text in
searchText.set(text) searchText.set(text)
if text == "", let completion { if text == "" {
completion(nil) completionImpl?(nil)
} }
}, openStickersBot: { }, openStickersBot: {
resolveDisposable.set((context.engine.peers.resolvePeerByName(name: "stickers") resolveDisposable.set((context.engine.peers.resolvePeerByName(name: "stickers")
@ -473,29 +495,24 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres
info = data.info info = data.info
} }
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: enabled, action: { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: enabled, action: {
if let completion { if info?.id == currentPackInfo?.id {
completion(info)
dismissImpl?() dismissImpl?()
} else { } else {
if info?.id == currentPackInfo?.id { updateState { state in
dismissImpl?() var state = state
} else { state.isSaving = true
return state
}
saveDisposable.set((context.engine.peers.updateGroupSpecificStickerset(peerId: peerId, info: info)
|> deliverOnMainQueue).start(error: { _ in
updateState { state in updateState { state in
var state = state var state = state
state.isSaving = true state.isSaving = false
return state return state
} }
saveDisposable.set((context.engine.peers.updateGroupSpecificStickerset(peerId: peerId, info: info) }, completed: {
|> deliverOnMainQueue).start(error: { _ in dismissImpl?()
updateState { state in }))
var state = state
state.isSaving = false
return state
}
}, completed: {
dismissImpl?()
}))
}
} }
}) })
} }
@ -537,6 +554,14 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres
presentControllerImpl = { [weak controller] c, p in presentControllerImpl = { [weak controller] c, p in
if let controller = controller { 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) controller.present(c, in: .window(.root), with: p)
} }
} }

View File

@ -208,6 +208,7 @@ final class PeerInfoScreenData {
let isPowerSavingEnabled: Bool? let isPowerSavingEnabled: Bool?
let accountIsPremium: Bool let accountIsPremium: Bool
let hasSavedMessageTags: Bool let hasSavedMessageTags: Bool
let isPremiumRequiredForStoryPosting: Bool
let _isContact: Bool let _isContact: Bool
var forceIsContact: Bool = false var forceIsContact: Bool = false
@ -244,7 +245,8 @@ final class PeerInfoScreenData {
appConfiguration: AppConfiguration?, appConfiguration: AppConfiguration?,
isPowerSavingEnabled: Bool?, isPowerSavingEnabled: Bool?,
accountIsPremium: Bool, accountIsPremium: Bool,
hasSavedMessageTags: Bool hasSavedMessageTags: Bool,
isPremiumRequiredForStoryPosting: Bool
) { ) {
self.peer = peer self.peer = peer
self.chatPeer = chatPeer self.chatPeer = chatPeer
@ -270,6 +272,7 @@ final class PeerInfoScreenData {
self.isPowerSavingEnabled = isPowerSavingEnabled self.isPowerSavingEnabled = isPowerSavingEnabled
self.accountIsPremium = accountIsPremium self.accountIsPremium = accountIsPremium
self.hasSavedMessageTags = hasSavedMessageTags self.hasSavedMessageTags = hasSavedMessageTags
self.isPremiumRequiredForStoryPosting = isPremiumRequiredForStoryPosting
} }
} }
@ -666,7 +669,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
appConfiguration: appConfiguration, appConfiguration: appConfiguration,
isPowerSavingEnabled: isPowerSavingEnabled, isPowerSavingEnabled: isPowerSavingEnabled,
accountIsPremium: peer?.isPremium ?? false, accountIsPremium: peer?.isPremium ?? false,
hasSavedMessageTags: false hasSavedMessageTags: false,
isPremiumRequiredForStoryPosting: true
) )
} }
} }
@ -702,7 +706,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
appConfiguration: nil, appConfiguration: nil,
isPowerSavingEnabled: nil, isPowerSavingEnabled: nil,
accountIsPremium: false, accountIsPremium: false,
hasSavedMessageTags: false hasSavedMessageTags: false,
isPremiumRequiredForStoryPosting: true
)) ))
case let .user(userPeerId, secretChatId, kind): case let .user(userPeerId, secretChatId, kind):
let groupsInCommon: GroupsInCommonContext? let groupsInCommon: GroupsInCommonContext?
@ -973,7 +978,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
appConfiguration: nil, appConfiguration: nil,
isPowerSavingEnabled: nil, isPowerSavingEnabled: nil,
accountIsPremium: accountIsPremium, accountIsPremium: accountIsPremium,
hasSavedMessageTags: hasSavedMessageTags hasSavedMessageTags: hasSavedMessageTags,
isPremiumRequiredForStoryPosting: false
) )
} }
case .channel: case .channel:
@ -1047,6 +1053,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
hasSavedMessageTags = .single(false) hasSavedMessageTags = .single(false)
} }
let isPremiumRequiredForStoryPosting: Signal<Bool, NoError> = isPremiumRequiredForStoryPosting(context: context)
return combineLatest( return combineLatest(
context.account.viewTracker.peerView(peerId, updateData: true), context.account.viewTracker.peerView(peerId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), 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), context.engine.peers.recommendedChannels(peerId: peerId),
hasSavedMessages, hasSavedMessages,
hasSavedMessagesChats, 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 var availablePanes = availablePanes
if let hasStories { if let hasStories {
if hasStories { if hasStories {
@ -1138,7 +1147,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
appConfiguration: nil, appConfiguration: nil,
isPowerSavingEnabled: nil, isPowerSavingEnabled: nil,
accountIsPremium: accountIsPremium, accountIsPremium: accountIsPremium,
hasSavedMessageTags: hasSavedMessageTags hasSavedMessageTags: hasSavedMessageTags,
isPremiumRequiredForStoryPosting: isPremiumRequiredForStoryPosting
) )
} }
case let .group(groupId): case let .group(groupId):
@ -1315,6 +1325,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
hasSavedMessageTags = .single(false) hasSavedMessageTags = .single(false)
} }
let isPremiumRequiredForStoryPosting: Signal<Bool, NoError> = isPremiumRequiredForStoryPosting(context: context)
return combineLatest(queue: .mainQueue(), return combineLatest(queue: .mainQueue(),
context.account.viewTracker.peerView(groupId, updateData: true), context.account.viewTracker.peerView(groupId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: groupId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), peerInfoAvailableMediaPanes(context: context, peerId: groupId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder),
@ -1331,9 +1343,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
accountIsPremium, accountIsPremium,
hasSavedMessages, hasSavedMessages,
hasSavedMessagesChats, hasSavedMessagesChats,
hasSavedMessageTags hasSavedMessageTags,
isPremiumRequiredForStoryPosting
) )
|> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags -> Signal<PeerInfoScreenData, NoError> in |> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags, isPremiumRequiredForStoryPosting -> Signal<PeerInfoScreenData, NoError> in
var discussionPeer: Peer? var discussionPeer: Peer?
if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] {
discussionPeer = peer discussionPeer = peer
@ -1426,7 +1439,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
appConfiguration: appConfiguration, appConfiguration: appConfiguration,
isPowerSavingEnabled: nil, isPowerSavingEnabled: nil,
accountIsPremium: accountIsPremium, accountIsPremium: accountIsPremium,
hasSavedMessageTags: hasSavedMessageTags hasSavedMessageTags: hasSavedMessageTags,
isPremiumRequiredForStoryPosting: isPremiumRequiredForStoryPosting
)) ))
} }
} }
@ -1812,3 +1826,25 @@ func peerInfoIsChatMuted(peer: Peer?, peerNotificationSettings: TelegramPeerNoti
} }
return chatIsMuted return chatIsMuted
} }
private var isPremiumRequired: Bool?
private func isPremiumRequiredForStoryPosting(context: AccountContext) -> Signal<Bool, NoError> {
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
}
)
}

View File

@ -5718,7 +5718,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 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) generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in }, action: { [weak self] _, f in
@ -6943,7 +6943,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} }
if channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) { 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) controller.push(boostsController)
} else { } else {
let _ = combineLatest( let _ = combineLatest(
@ -8800,7 +8804,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .settings, forceDark: false, dismissed: nil) let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .settings, forceDark: false, dismissed: nil)
self.controller?.push(controller) self.controller?.push(controller)
case .premiumGift: 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) self.controller?.push(controller)
case .stickers: case .stickers:
if let settings = self.data?.globalSettings { if let settings = self.data?.globalSettings {
@ -10339,7 +10343,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) { } 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)) 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) rightNavigationButtons.insert(PeerInfoHeaderNavigationButtonSpec(key: .postStory, isForExpandedView: false), at: 0)
} }

View File

@ -348,50 +348,61 @@ final class PeerInfoStoryGridScreenComponent: Component {
return return
} }
switch component.scope { let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId))
case .saved: |> deliverOnMainQueue).start(next: { [weak self] peer in
let selectedCount = paneNode.selectedItems.count guard let self, let peer else {
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: false).start() return
}
paneNode.setIsSelectionModeActive(false) var isGroup = false
(self.environment?.controller() as? PeerInfoStoryGridScreen)?.updateTitle() if case let .channel(channel) = peer, case .group = channel.info {
isGroup = true
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
} }
environment.controller()?.present(UndoOverlayController( switch component.scope {
presentationData: presentationData, case .saved:
content: .info(title: title, text: text, timeout: nil, customUndoText: nil), let selectedCount = paneNode.selectedItems.count
elevatedLayout: false, let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: false).start()
animateInAsReplacement: false,
action: { _ in return false } paneNode.setIsSelectionModeActive(false)
), in: .current) (self.environment?.controller() as? PeerInfoStoryGridScreen)?.updateTitle()
paneNode.clearSelection() 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: {}, environment: {},

View File

@ -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.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 self.backButton.view.superview == nil {
if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar { if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar {
navigationBar.view.addSubview(self.backButton.view) navigationBar.view.addSubview(self.backButton.view)

View File

@ -19,15 +19,17 @@ final class StoryAuthorInfoComponent: Component {
let strings: PresentationStrings let strings: PresentationStrings
let peer: EnginePeer? let peer: EnginePeer?
let forwardInfo: EngineStoryItem.ForwardInfo? let forwardInfo: EngineStoryItem.ForwardInfo?
let author: EnginePeer?
let timestamp: Int32 let timestamp: Int32
let counters: Counters? let counters: Counters?
let isEdited: Bool 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.context = context
self.strings = strings self.strings = strings
self.peer = peer self.peer = peer
self.forwardInfo = forwardInfo self.forwardInfo = forwardInfo
self.author = author
self.timestamp = timestamp self.timestamp = timestamp
self.counters = counters self.counters = counters
self.isEdited = isEdited self.isEdited = isEdited
@ -46,6 +48,9 @@ final class StoryAuthorInfoComponent: Component {
if lhs.forwardInfo != rhs.forwardInfo { if lhs.forwardInfo != rhs.forwardInfo {
return false return false
} }
if lhs.author != rhs.author {
return false
}
if lhs.timestamp != rhs.timestamp { if lhs.timestamp != rhs.timestamp {
return false return false
} }
@ -121,6 +126,16 @@ final class StoryAuthorInfoComponent: Component {
} }
subtitle = combinedString subtitle = combinedString
subtitleTruncationType = .middle 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 { } else {
var subtitleString = stringForStoryActivityTimestamp(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, preciseTime: true, relativeTimestamp: component.timestamp, relativeTo: timestamp) var subtitleString = stringForStoryActivityTimestamp(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, preciseTime: true, relativeTimestamp: component.timestamp, relativeTo: timestamp)
if component.isEdited { if component.isEdited {
@ -176,7 +191,16 @@ final class StoryAuthorInfoComponent: Component {
self.repostIconView = nil self.repostIconView = nil
repostIconView.removeFromSuperview() repostIconView.removeFromSuperview()
} }
var authorPeer: EnginePeer?
if let forwardInfo = component.forwardInfo, case let .known(peer, _, _) = forwardInfo { 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 let avatarNode: AvatarNode
if let current = self.avatarNode { if let current = self.avatarNode {
avatarNode = current avatarNode = current

View File

@ -114,6 +114,11 @@ public final class StoryContentContextImpl: StoryContentContext {
forwardInfoStories.updateValue(nil, forKey: storyId) 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 { for entity in itemValue.entities {
if case let .CustomEmoji(_, fileId) = entity.type { if case let .CustomEmoji(_, fileId) = entity.type {
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId) let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
@ -296,7 +301,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, myReaction: item.myReaction,
forwardInfo: forwardInfo forwardInfo: forwardInfo,
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
) )
} }
var totalCount = peerStoryItemsView.items.count var totalCount = peerStoryItemsView.items.count
@ -332,7 +338,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isEdited: false, isEdited: false,
isMy: true, isMy: true,
myReaction: nil, myReaction: nil,
forwardInfo: pendingForwardsInfo[item.randomId] forwardInfo: pendingForwardsInfo[item.randomId],
author: nil
)) ))
totalCount += 1 totalCount += 1
} }
@ -1187,6 +1194,11 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
stories.updateValue(nil, forKey: storyId) 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 { for entity in item.entities {
if case let .CustomEmoji(_, fileId) = entity.type { if case let .CustomEmoji(_, fileId) = entity.type {
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId) let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
@ -1312,7 +1324,8 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
isEdited: itemValue.isEdited, isEdited: itemValue.isEdited,
isMy: itemValue.isMy, isMy: itemValue.isMy,
myReaction: itemValue.myReaction, myReaction: itemValue.myReaction,
forwardInfo: forwardInfo forwardInfo: forwardInfo,
author: itemValue.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
) )
let mainItem = StoryContentItem( let mainItem = StoryContentItem(
@ -2191,7 +2204,8 @@ private func getCachedStory(storyId: StoryId, transaction: Transaction) -> Engin
isEdited: item.isEdited, isEdited: item.isEdited,
isMy: item.isMy, isMy: item.isMy,
myReaction: item.myReaction, 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 { } else {
return nil return nil

View File

@ -1660,11 +1660,18 @@ public final class StoryItemSetContainerComponent: Component {
var canShare = true var canShare = true
var displayFooter = false var displayFooter = false
if case let .channel(channel) = component.slice.peer { if case let .channel(channel) = component.slice.peer {
displayFooter = true
isChannel = true isChannel = true
if channel.addressName == nil { if channel.addressName == nil {
canShare = false 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 { } else if component.slice.peer.id == component.context.account.peerId {
displayFooter = true displayFooter = true
} else if component.slice.item.storyItem.isPending { } else if component.slice.item.storyItem.isPending {
@ -2752,6 +2759,24 @@ public final class StoryItemSetContainerComponent: Component {
disabledPlaceholder = .text(component.strings.Story_FooterReplyUnavailable) 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 let inputPlaceholder: MessageInputPanelComponent.Placeholder
if let stealthModeTimeout = component.stealthModeTimeout { if let stealthModeTimeout = component.stealthModeTimeout {
let minutes = Int(stealthModeTimeout / 60) let minutes = Int(stealthModeTimeout / 60)
@ -2787,7 +2812,7 @@ public final class StoryItemSetContainerComponent: Component {
inputPlaceholder = .counter(items) inputPlaceholder = .counter(items)
} else { } else {
inputPlaceholder = .plain(component.strings.Story_InputPlaceholderReplyPrivately) inputPlaceholder = .plain(isGroup ? component.strings.Story_InputPlaceholderReplyInGroup : component.strings.Story_InputPlaceholderReplyPrivately)
} }
let startTime22 = CFAbsoluteTimeGetCurrent() let startTime22 = CFAbsoluteTimeGetCurrent()
@ -2811,13 +2836,8 @@ public final class StoryItemSetContainerComponent: Component {
var inputPanelSize: CGSize? var inputPanelSize: CGSize?
let startTime23 = CFAbsoluteTimeGetCurrent() let startTime23 = CFAbsoluteTimeGetCurrent()
var isChannel = false if showMessageInputPanel {
if case .channel = component.slice.peer {
isChannel = true
}
if component.slice.peer.id != component.context.account.peerId && !isChannel {
var haveLikeOptions = false var haveLikeOptions = false
if case .user = component.slice.peer { if case .user = component.slice.peer {
haveLikeOptions = true haveLikeOptions = true
@ -3998,6 +4018,7 @@ public final class StoryItemSetContainerComponent: Component {
strings: component.strings, strings: component.strings,
peer: component.slice.peer, peer: component.slice.peer,
forwardInfo: component.slice.item.storyItem.forwardInfo, forwardInfo: component.slice.item.storyItem.forwardInfo,
author: component.slice.item.storyItem.author,
timestamp: component.slice.item.storyItem.timestamp, timestamp: component.slice.item.storyItem.timestamp,
counters: counters, counters: counters,
isEdited: component.slice.item.storyItem.isEdited isEdited: component.slice.item.storyItem.isEdited
@ -4031,6 +4052,12 @@ public final class StoryItemSetContainerComponent: Component {
} else { } else {
self.navigateToPeer(peer: peer, chat: false) 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 { } else {
if component.slice.peer.id == component.context.account.peerId { if component.slice.peer.id == component.context.account.peerId {
self.navigateToMyStories() self.navigateToMyStories()
@ -4381,7 +4408,7 @@ public final class StoryItemSetContainerComponent: Component {
presentationData: component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme), presentationData: component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme),
items: reactionItems.map(ReactionContextItem.reaction), items: reactionItems.map(ReactionContextItem.reaction),
selectedItems: component.slice.item.storyItem.myReaction.flatMap { Set([$0]) } ?? Set(), 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, reactionsLocked: false,
alwaysAllowPremiumReactions: false, alwaysAllowPremiumReactions: false,
allPresetReactionsAreAvailable: false, allPresetReactionsAreAvailable: false,
@ -5356,6 +5383,7 @@ public final class StoryItemSetContainerComponent: Component {
let externalState = MediaEditorTransitionOutExternalState( let externalState = MediaEditorTransitionOutExternalState(
storyTarget: nil, storyTarget: nil,
isForcedTarget: false,
isPeerArchived: false, isPeerArchived: false,
transitionOut: nil transitionOut: nil
) )
@ -6394,11 +6422,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() 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) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
if component.slice.item.storyItem.isPinned { if component.slice.item.storyItem.isPinned {
self.scheduledStoryUnpinnedUndoOverlay = UndoOverlayController( self.scheduledStoryUnpinnedUndoOverlay = UndoOverlayController(
presentationData: presentationData, 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, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, blurred: true,
@ -6407,7 +6440,7 @@ public final class StoryItemSetContainerComponent: Component {
} else { } else {
self.component?.presentController(UndoOverlayController( self.component?.presentController(UndoOverlayController(
presentationData: presentationData, 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, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, blurred: true,

View File

@ -1443,9 +1443,6 @@ public class VideoMessageCameraScreen: ViewController {
deinit { deinit {
self.audioSessionDisposable?.dispose() self.audioSessionDisposable?.dispose()
if #available(iOS 13.0, *) {
try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(false)
}
} }
override public func loadDisplayNode() { 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 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 { if let self {
Queue.mainQueue().async { Queue.mainQueue().async {
self.node.setupCamera() self.node.setupCamera()

View File

@ -450,6 +450,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var mediaRestrictedTooltipControllerMode = true var mediaRestrictedTooltipControllerMode = true
weak var checksTooltipController: TooltipController? weak var checksTooltipController: TooltipController?
weak var copyProtectionTooltipController: TooltipController? weak var copyProtectionTooltipController: TooltipController?
weak var emojiPackTooltipController: TooltipScreen?
var currentMessageTooltipScreens: [(TooltipScreen, ListViewItemNode)] = [] var currentMessageTooltipScreens: [(TooltipScreen, ListViewItemNode)] = []
@ -965,14 +966,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
return true return true
} }
if let peer = peer as? TelegramChannel, peer.hasPermission(.changeInfo) { if let peer = peer as? TelegramChannel {
let _ = (context.engine.peers.getChannelBoostStatus(peerId: peer.id) if peer.flags.contains(.isCreator) || peer.adminRights?.rights.contains(.canChangeInfo) == true {
|> deliverOnMainQueue).start(next: { [weak self] boostStatus in let _ = (context.engine.peers.getChannelBoostStatus(peerId: peer.id)
guard let self else { |> deliverOnMainQueue).start(next: { [weak self] boostStatus in
return guard let self else {
} return
self.push(ChannelAppearanceScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peerId: peer.id, boostStatus: boostStatus)) }
}) self.push(ChannelAppearanceScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peerId: peer.id, boostStatus: boostStatus))
})
}
return true return true
} }
guard message.effectivelyIncoming(strongSelf.context.account.peerId), let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { guard message.effectivelyIncoming(strongSelf.context.account.peerId), let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
@ -15714,42 +15717,53 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let rect = self.chatDisplayNode.frameForEmojiButton(), self.effectiveNavigationController?.topViewController === self else { guard let rect = self.chatDisplayNode.frameForEmojiButton(), self.effectiveNavigationController?.topViewController === self else {
return 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 return
} }
let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [thumbnailFileId])
|> deliverOnMainQueue).start(next: { [weak self] files in let _ = (ApplicationSpecificNotice.groupEmojiPackSuggestion(accountManager: self.context.sharedContext.accountManager, peerId: peerId)
guard let self, let emojiFile = files.values.first else { |> deliverOnMainQueue).start(next: { [weak self] counter in
guard let self, counter == 0 else {
return return
} }
let textFont = Font.regular(self.presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0) let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [thumbnailFileId])
let boldTextFont = Font.bold(self.presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0) |> deliverOnMainQueue).start(next: { [weak self] files in
let textColor = UIColor.white guard let self, let emojiFile = files.values.first else {
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in return
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 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)
self.emojiPackTooltipController = tooltipScreen
let _ = ApplicationSpecificNotice.incrementGroupEmojiPackSuggestion(accountManager: self.context.sharedContext.accountManager, peerId: peerId).startStandalone()
})
}) })
} }
@ -16694,6 +16708,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let externalState = MediaEditorTransitionOutExternalState( let externalState = MediaEditorTransitionOutExternalState(
storyTarget: nil, storyTarget: nil,
isForcedTarget: false,
isPeerArchived: false, isPeerArchived: false,
transitionOut: nil transitionOut: nil
) )

View File

@ -3705,6 +3705,17 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
strongSelf.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in strongSelf.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in
return (.media(mode: .other, expanded: nil, focused: false), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId) 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()
}
}
}
}) })
} }
} }

View File

@ -1070,7 +1070,25 @@ extension ChatControllerImpl {
if case .scheduledMessages = self.presentationInterfaceState.subject { if case .scheduledMessages = self.presentationInterfaceState.subject {
isScheduledMessages = true 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 let mediaPickerContext = controller.mediaPickerContext
controller.openCamera = { [weak self] cameraView in controller.openCamera = { [weak self] cameraView in
self?.openCamera(cameraView: cameraView) self?.openCamera(cameraView: cameraView)

View File

@ -587,7 +587,7 @@ func openResolvedUrlImpl(
} }
case let .premiumMultiGift(reference): case let .premiumMultiGift(reference):
dismissInput() 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 { if let navigationController = navigationController {
navigationController.pushViewController(controller, animated: true) navigationController.pushViewController(controller, animated: true)
} }

View File

@ -2049,7 +2049,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return PremiumLimitScreen(context: context, subject: mappedSubject, count: count, forceDark: forceDark, cancel: cancel, action: action) 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]>() let options = Promise<[PremiumGiftCodeOption]>()
options.set(context.engine.payments.premiumGiftCodeOptions(peerId: nil)) options.set(context.engine.payments.premiumGiftCodeOptions(peerId: nil))
@ -2101,6 +2101,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
pushImpl?(c) pushImpl?(c)
}, completion: { }, completion: {
filterImpl?() filterImpl?()
completion?()
}) })
pushImpl = { [weak giftController] c in pushImpl = { [weak giftController] c in
giftController?.push(c) giftController?.push(c)
@ -2108,7 +2109,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
filterImpl = { [weak giftController] in filterImpl = { [weak giftController] in
if let navigationController = giftController?.navigationController as? NavigationController { if let navigationController = giftController?.navigationController as? NavigationController {
var controllers = navigationController.viewControllers var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is ContactMultiselectionController) } controllers = controllers.filter { !($0 is ContactMultiselectionController) && !($0 is PremiumGiftScreen) }
navigationController.setViewControllers(controllers, animated: true) navigationController.setViewControllers(controllers, animated: true)
} }
} }

View File

@ -277,6 +277,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
let externalState = MediaEditorTransitionOutExternalState( let externalState = MediaEditorTransitionOutExternalState(
storyTarget: nil, storyTarget: nil,
isForcedTarget: customTarget != nil,
isPeerArchived: false, isPeerArchived: false,
transitionOut: nil transitionOut: nil
) )
@ -522,13 +523,17 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
var viewControllers = self.viewControllers var viewControllers = self.viewControllers
let archiveController = ChatListControllerImpl(context: context, location: .chatList(groupId: .archive), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false) 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 chatListController = archiveController
viewControllers.insert(archiveController, at: 1) viewControllers.insert(archiveController, at: 1)
self.setViewControllers(viewControllers, animated: false) self.setViewControllers(viewControllers, animated: false)
} else { } else {
chatListController = self.chatListController as? ChatListControllerImpl chatListController = self.chatListController as? ChatListControllerImpl
externalState.transitionOut = chatListController?.storyCameraTransitionOut() if !externalState.isForcedTarget {
externalState.transitionOut = chatListController?.storyCameraTransitionOut()
}
} }
if let chatListController { if let chatListController {

View File

@ -291,6 +291,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
private var paymentDisposable: Disposable? private var paymentDisposable: Disposable?
private var lastExpansionTimestamp: Double?
private var didTransitionIn = false private var didTransitionIn = false
private var dismissed = false private var dismissed = false
@ -739,7 +741,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
guard let eventName = body["eventName"] as? String else { guard let eventName = body["eventName"] as? String else {
return return
} }
let currentTimestamp = CACurrentMediaTime()
let eventData = (body["eventData"] as? String)?.data(using: .utf8) let eventData = (body["eventData"] as? String)?.data(using: .utf8)
let json = try? JSONSerialization.jsonObject(with: eventData ?? Data(), options: []) as? [String: Any] 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": case "web_app_request_theme":
self.sendThemeChangedEvent() self.sendThemeChangedEvent()
case "web_app_expand": 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": case "web_app_close":
controller.dismiss() controller.dismiss()
case "web_app_open_tg_link": case "web_app_open_tg_link":
@ -846,7 +853,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
case "web_app_open_link": case "web_app_open_link":
if let json = json, let url = json["url"] as? String { if let json = json, let url = json["url"] as? String {
let tryInstantView = json["try_instant_view"] as? Bool ?? false let tryInstantView = json["try_instant_view"] as? Bool ?? false
let currentTimestamp = CACurrentMediaTime()
if let lastTouchTimestamp = self.webView?.lastTouchTimestamp, currentTimestamp < lastTouchTimestamp + 10.0 { if let lastTouchTimestamp = self.webView?.lastTouchTimestamp, currentTimestamp < lastTouchTimestamp + 10.0 {
self.webView?.lastTouchTimestamp = nil self.webView?.lastTouchTimestamp = nil
if tryInstantView { if tryInstantView {