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.MoreBoostsNeeded.Boosted.Text" = "%@ needed to unlock new features.";
"ChannelBoost.MoreBoostsNeeded.Boosted.Level.Text" = "This channel reached **%@** and unlocked new features.";
"GroupBoost.MoreBoostsNeeded.Boosted.Level.Text" = "This group reached **%@** and unlocked new features.";
"ContactList.Context.Delete" = "Delete Contact";
"ContactList.Context.Select" = "Select";
@ -11013,7 +11015,7 @@ Sorry for the inconvenience.";
"GroupBoost.EnableStoriesText" = "Your group needs %1$@ to enable posting stories.";
"GroupBoost.IncreaseLimitText" = "Your group needs %1$@ to post %2$@.";
"BoostGift.Group.Description" = "Get more boosts for your group by gifting\nPremium to your subscribers.";
"BoostGift.Group.Description" = "Get more boosts for your group by gifting\nPremium to your members.";
"BoostGift.Group.AllMembers" = "All members";
"BoostGift.Group.OnlyNewMembers" = "Only new members";
@ -11276,3 +11278,31 @@ Sorry for the inconvenience.";
"Chat.Giveaway.Info.OtherChannelsAndGroups_1" = "**%@** other listed channel and group";
"Chat.Giveaway.Info.OtherChannelsAndGroups_any" = "**%@** other listed channels and groups";
"GroupBoost.MemberBoosted.Times_1" = "**%@** time";
"GroupBoost.MemberBoosted.Times_any" = "**%@** times";
"GroupBoost.MemberBoosted" = "**%1$@** boosted the group %2$@.";
"GroupBoost.MemberBoosted.BoostForBadge" = "Boost **%1$@** to help it unlock new features and get a booster **badge** for your messages.";
"GroupBoost.BoostToUnrestrict.Times_1" = "**%@** time";
"GroupBoost.BoostToUnrestrict.Times_any" = "**%@** times";
"GroupBoost.BoostToUnrestrict" = "Boost the group %1$@ to remove messaging restrictions. Your boosts will help **%2$@** to unlock new features.";
"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 var storyTarget: Stories.PendingTarget?
public var isForcedTarget: Bool
public var isPeerArchived: Bool
public var transitionOut: ((Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?)?
public init(storyTarget: Stories.PendingTarget?, isPeerArchived: Bool, transitionOut: ((Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?)?) {
public init(storyTarget: Stories.PendingTarget?, isForcedTarget: Bool, isPeerArchived: Bool, transitionOut: ((Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?)?) {
self.storyTarget = storyTarget
self.isForcedTarget = isForcedTarget
self.isPeerArchived = isPeerArchived
self.transitionOut = transitionOut
}
@ -959,7 +961,7 @@ public protocol SharedAccountContext: AnyObject {
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> Void) -> ViewController
func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Bool) -> ViewController
func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource) -> ViewController
func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (() -> Void)?) -> ViewController
func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController
func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController

View File

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

View File

@ -309,7 +309,11 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
if let forwardInfo = message.forwardInfo, let author = forwardInfo.author {
messageText = strings.Message_GiveawayStartedOther(EnginePeer(author).compactDisplayTitle).string
} else {
messageText = isPeerGroup ? strings.Message_GiveawayStartedGroup : strings.Message_GiveawayStarted
if let author = message.author, case let .channel(channel) = author, case .group = channel.info {
messageText = strings.Message_GiveawayStartedGroup
} else {
messageText = strings.Message_GiveawayStarted
}
}
case let results as TelegramMediaGiveawayResults:
if results.winnersCount == 0 {

View File

@ -1683,7 +1683,7 @@ public final class ChatListNode: ListView {
guard let self else {
return
}
let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .chatList)
let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .chatList, completion: nil)
self.push?(controller)
}, openActiveSessions: { [weak self] in
guard let self else {

View File

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

View File

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

View File

@ -1210,7 +1210,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
navigationController.setViewControllers(controllers, animated: true)
let title = presentationData.strings.BoostGift_GiveawayCreated_Title
let text = presentationData.strings.BoostGift_GiveawayCreated_Text
let text = isGroup ? presentationData.strings.BoostGift_Group_GiveawayCreated_Text : presentationData.strings.BoostGift_GiveawayCreated_Text
let tooltipController = UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: title, text: text, customUndoText: nil, timeout: nil, linkAction: { [weak navigationController] _ in
let statsController = context.sharedContext.makeChannelStatsController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, boosts: true, boostStatus: nil)

View File

@ -647,33 +647,37 @@ private final class SheetContent: CombinedComponent {
}
} else {
textString = strings.ChannelBoost_MaxLevelReached_Text(peerName, "\(level)").string
// let storiesString = strings.ChannelBoost_StoriesPerDay(Int32(level))
// textString = strings.ChannelBoost_MaxLevelReachedTextAuthor("\(level)", storiesString).string
}
case let .user(mode):
switch mode {
case let .groupPeer(_, peerBoostCount):
let memberName = state.memberPeer?.compactDisplayTitle ?? ""
//TODO:localize
let timesString = strings.GroupBoost_MemberBoosted_Times(Int32(peerBoostCount))
let memberString = strings.GroupBoost_MemberBoosted(memberName, timesString).string
if myBoostCount > 0 {
if let remaining {
if let remaining, remaining != 0 {
let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining))
textString = "**\(memberName)** boosted the group **\(peerBoostCount)** times. \(strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string)"
textString = "\(memberString) \(strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string)"
} else {
textString = "**\(memberName)** boosted the group **\(peerBoostCount)** times."
textString = memberString
}
} else {
textString = "**\(memberName)** boosted the group **\(peerBoostCount)** times. Boost **\(peerName)** to help it unlock new features and get a booster **badge** for your messages."
textString = "\(memberString) \(strings.GroupBoost_MemberBoosted_BoostForBadge(peerName).string)"
}
isCurrent = true
case let .unrestrict(unrestrictCount):
textString = "Boost the group **\(unrestrictCount)** times to remove messaging restrictions. Your boosts will help **\(peerName)** to unlock new features."
let timesString = strings.GroupBoost_BoostToUnrestrict_Times(Int32(unrestrictCount))
textString = strings.GroupBoost_BoostToUnrestrict(timesString, peerName).string
isCurrent = true
default:
if let remaining {
let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining))
if myBoostCount > 0 {
textString = strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string
if remaining == 0 {
textString = isGroup ? strings.GroupBoost_MoreBoostsNeeded_Boosted_Level_Text("\(level)").string : strings.ChannelBoost_MoreBoostsNeeded_Boosted_Level_Text("\(level)").string
} else {
textString = strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string
}
} else {
textString = strings.ChannelBoost_MoreBoostsNeeded_Text(peerName, boostsString).string
}
@ -1612,25 +1616,26 @@ public class PremiumBoostLevelsScreen: ViewController {
self.wrappingView.addSubview(self.containerView)
self.containerView.addSubview(self.contentView)
if case .user = controller.mode, let status = controller.status {
if case .user = controller.mode {
self.containerView.addSubview(self.footerContainerView)
self.footerContainerView.addSubview(self.footerView)
}
if let status = controller.status, let myBoostStatus = controller.myBoostStatus {
var myBoostCount: Int32 = 0
var currentMyBoostCount: Int32 = 0
var availableBoosts: [MyBoostStatus.Boost] = []
var occupiedBoosts: [MyBoostStatus.Boost] = []
if let myBoostStatus = controller.myBoostStatus {
for boost in myBoostStatus.boosts {
if let boostPeer = boost.peer {
if boostPeer.id == controller.peerId {
myBoostCount += 1
} else {
occupiedBoosts.append(boost)
}
for boost in myBoostStatus.boosts {
if let boostPeer = boost.peer {
if boostPeer.id == controller.peerId {
myBoostCount += 1
} else {
availableBoosts.append(boost)
occupiedBoosts.append(boost)
}
} else {
availableBoosts.append(boost)
}
}
@ -1868,23 +1873,10 @@ public class PremiumBoostLevelsScreen: ViewController {
status: controller.status,
boostState: self.boostState,
boost: { [weak controller] in
guard let controller, let navigationController = controller.navigationController else {
guard let controller else {
return
}
controller.dismiss(animated: true)
Queue.mainQueue().justDispatch {
let boostController = PremiumBoostLevelsScreen(
context: controller.context,
peerId: controller.peerId,
mode: .user(mode: .current),
status: controller.status,
myBoostStatus: nil,
forceDark: controller.forceDark
)
navigationController.pushViewController(boostController, animated: true)
}
controller.node.updateBoostState()
},
copyLink: { [weak self, weak controller] link in
guard let self else {
@ -2217,6 +2209,13 @@ public class PremiumBoostLevelsScreen: ViewController {
let _ = (context.engine.peers.applyChannelBoost(peerId: peerId, slots: [availableBoost.slot])
|> deliverOnMainQueue).startStandalone(completed: { [weak self] in
self?.updatedState.set(context.engine.peers.getChannelBoostStatus(peerId: peerId)
|> beforeNext { [weak self] status in
if let self, let status {
Queue.mainQueue().async {
self.controller?.boostStatusUpdated(status)
}
}
}
|> map { status in
if let status {
return InternalBoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: Int32(status.boosts + 1))
@ -2361,7 +2360,7 @@ public class PremiumBoostLevelsScreen: ViewController {
controller?.dismiss(animated: true, completion: nil)
Queue.mainQueue().after(0.4) {
let giftController = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost)
let giftController = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost, completion: nil)
navigationController.pushViewController(giftController, animated: true)
}
}
@ -2432,6 +2431,7 @@ public class PremiumBoostLevelsScreen: ViewController {
private var currentLayout: ContainerViewLayout?
public var boostStatusUpdated: (ChannelBoostStatus) -> Void = { _ in }
public var disposed: () -> Void = {}
public init(
@ -2501,7 +2501,7 @@ public class PremiumBoostLevelsScreen: ViewController {
override open func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.node.updateIsVisible(isVisible: false)
}

View File

@ -239,7 +239,7 @@ public func PremiumBoostScreen(
dismissImpl?()
Queue.mainQueue().after(0.4) {
let controller = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost)
let controller = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost, completion: nil)
pushController(controller)
}
}),

View File

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

View File

@ -880,7 +880,7 @@ public class ReplaceBoostScreen: ViewController {
}
let navigationController = self.navigationController
self.dismiss(animated: true, completion: {
let giftController = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost)
let giftController = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost, completion: nil)
navigationController?.pushViewController(giftController, animated: true)
})
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -349,7 +349,8 @@ private final class StoryStatsPublicForwardsContextImpl {
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
)
resultForwards.append(.story(EnginePeer(peer), mappedItem))
}

View File

@ -556,7 +556,8 @@ public final class EngineStoryViewListContext {
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
),
storyStats: transaction.getPeerStoryStats(peerId: peer.id)
)))
@ -595,7 +596,8 @@ public final class EngineStoryViewListContext {
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo
forwardInfo: item.forwardInfo,
authorId: item.authorId
))
if let entry = CodableEntry(updatedItem) {
transaction.setStory(id: StoryId(peerId: account.peerId, id: storyId), value: entry)
@ -634,7 +636,8 @@ public final class EngineStoryViewListContext {
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo
forwardInfo: item.forwardInfo,
authorId: item.authorId
))
if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
@ -748,7 +751,8 @@ public final class EngineStoryViewListContext {
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
),
storyStats: transaction.getPeerStoryStats(peerId: peer.id)
)))

View File

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

View File

@ -78,8 +78,9 @@ public final class EngineStoryItem: Equatable {
public let isMy: Bool
public let myReaction: MessageReaction.Reaction?
public let forwardInfo: ForwardInfo?
public let author: EnginePeer?
public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, alternativeMedia: EngineMedia?, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool, isContacts: Bool, isSelectedContacts: Bool, isForwardingDisabled: Bool, isEdited: Bool, isMy: Bool, myReaction: MessageReaction.Reaction?, forwardInfo: ForwardInfo?) {
public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, alternativeMedia: EngineMedia?, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool, isContacts: Bool, isSelectedContacts: Bool, isForwardingDisabled: Bool, isEdited: Bool, isMy: Bool, myReaction: MessageReaction.Reaction?, forwardInfo: ForwardInfo?, author: EnginePeer?) {
self.id = id
self.timestamp = timestamp
self.expirationTimestamp = expirationTimestamp
@ -102,6 +103,7 @@ public final class EngineStoryItem: Equatable {
self.isMy = isMy
self.myReaction = myReaction
self.forwardInfo = forwardInfo
self.author = author
}
public static func ==(lhs: EngineStoryItem, rhs: EngineStoryItem) -> Bool {
@ -171,6 +173,9 @@ public final class EngineStoryItem: Equatable {
if lhs.forwardInfo != rhs.forwardInfo {
return false
}
if lhs.author != rhs.author {
return false
}
return true
}
}
@ -223,7 +228,8 @@ public extension EngineStoryItem {
isEdited: self.isEdited,
isMy: self.isMy,
myReaction: self.myReaction,
forwardInfo: self.forwardInfo?.storedForwardInfo
forwardInfo: self.forwardInfo?.storedForwardInfo,
authorId: self.author?.id
)
}
}
@ -600,7 +606,8 @@ public final class PeerStoryListContext {
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
)
items.append(mappedItem)
@ -745,7 +752,8 @@ public final class PeerStoryListContext {
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
)
storyItems.append(mappedItem)
}
@ -839,6 +847,11 @@ public final class PeerStoryListContext {
peers[peer.id] = peer
}
}
if let peerId = item.authorId {
if let peer = transaction.getPeer(peerId) {
peers[peer.id] = peer
}
}
}
}
default:
@ -907,7 +920,8 @@ public final class PeerStoryListContext {
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) }
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) },
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
)
finalUpdatedState = updatedState
}
@ -955,7 +969,8 @@ public final class PeerStoryListContext {
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) }
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) },
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
)
finalUpdatedState = updatedState
} else {
@ -1005,7 +1020,8 @@ public final class PeerStoryListContext {
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) }
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) },
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
))
updatedState.items.sort(by: { lhs, rhs in
return lhs.timestamp > rhs.timestamp
@ -1051,7 +1067,8 @@ public final class PeerStoryListContext {
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) }
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) },
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
))
updatedState.items.sort(by: { lhs, rhs in
return lhs.timestamp > rhs.timestamp
@ -1221,7 +1238,8 @@ public final class PeerExpiringStoryListContext {
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }
)
items.append(.item(mappedItem))
}

View File

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

View File

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

View File

@ -2274,7 +2274,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
nameNodeOriginY = headerSize.height
headerSize.width = max(headerSize.width, nameNodeSizeApply.0.width + adminBadgeSizeAndApply.0.size.width + credibilityIconWidth + boostBadgeWidth + closeButtonWidth + bubbleWidthInsets)
headerSize.width = max(headerSize.width, nameNodeSizeApply.0.width + 8.0 + adminBadgeSizeAndApply.0.size.width + credibilityIconWidth + boostBadgeWidth + closeButtonWidth + bubbleWidthInsets)
headerSize.height += nameNodeSizeApply.0.height
}

View File

@ -2129,14 +2129,14 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
var entities: [MessageTextEntity] = []
if new != nil {
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageChangedGroupStickerPack(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""), generateEntities: { index in
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageChangedGroupEmojiPack(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""), generateEntities: { index in
if index == 0, let author = author {
return [.TextMention(peerId: author.id)]
}
return []
}, to: &text, entities: &entities)
} else {
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageRemovedGroupStickerPack(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""), generateEntities: { index in
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageRemovedGroupEmojiPack(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""), generateEntities: { index in
if index == 0, let author = author {
return [.TextMention(peerId: author.id)]
}

View File

@ -390,7 +390,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
}
return true
}
public var useExternalSearchContainer: Bool = false
private var gifContext: GifContext? {
@ -2133,6 +2133,12 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
strongSelf.interaction?.presentGlobalOverlayController(contextController, nil)
})
}
public func scrollToGroupEmoji() {
if let pagerView = self.entityKeyboardView.componentView as? EntityKeyboardComponent.View {
pagerView.scrollToItemGroup(contentId: "emoji", groupId: "peerSpecific", subgroupId: nil)
}
}
}
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {

View File

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

View File

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

View File

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

View File

@ -208,6 +208,7 @@ final class PeerInfoScreenData {
let isPowerSavingEnabled: Bool?
let accountIsPremium: Bool
let hasSavedMessageTags: Bool
let isPremiumRequiredForStoryPosting: Bool
let _isContact: Bool
var forceIsContact: Bool = false
@ -244,7 +245,8 @@ final class PeerInfoScreenData {
appConfiguration: AppConfiguration?,
isPowerSavingEnabled: Bool?,
accountIsPremium: Bool,
hasSavedMessageTags: Bool
hasSavedMessageTags: Bool,
isPremiumRequiredForStoryPosting: Bool
) {
self.peer = peer
self.chatPeer = chatPeer
@ -270,6 +272,7 @@ final class PeerInfoScreenData {
self.isPowerSavingEnabled = isPowerSavingEnabled
self.accountIsPremium = accountIsPremium
self.hasSavedMessageTags = hasSavedMessageTags
self.isPremiumRequiredForStoryPosting = isPremiumRequiredForStoryPosting
}
}
@ -666,7 +669,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
appConfiguration: appConfiguration,
isPowerSavingEnabled: isPowerSavingEnabled,
accountIsPremium: peer?.isPremium ?? false,
hasSavedMessageTags: false
hasSavedMessageTags: false,
isPremiumRequiredForStoryPosting: true
)
}
}
@ -702,7 +706,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
appConfiguration: nil,
isPowerSavingEnabled: nil,
accountIsPremium: false,
hasSavedMessageTags: false
hasSavedMessageTags: false,
isPremiumRequiredForStoryPosting: true
))
case let .user(userPeerId, secretChatId, kind):
let groupsInCommon: GroupsInCommonContext?
@ -973,7 +978,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
appConfiguration: nil,
isPowerSavingEnabled: nil,
accountIsPremium: accountIsPremium,
hasSavedMessageTags: hasSavedMessageTags
hasSavedMessageTags: hasSavedMessageTags,
isPremiumRequiredForStoryPosting: false
)
}
case .channel:
@ -1047,6 +1053,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
hasSavedMessageTags = .single(false)
}
let isPremiumRequiredForStoryPosting: Signal<Bool, NoError> = isPremiumRequiredForStoryPosting(context: context)
return combineLatest(
context.account.viewTracker.peerView(peerId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder),
@ -1061,9 +1069,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
context.engine.peers.recommendedChannels(peerId: peerId),
hasSavedMessages,
hasSavedMessagesChats,
hasSavedMessageTags
hasSavedMessageTags,
isPremiumRequiredForStoryPosting
)
|> map { peerView, availablePanes, globalNotificationSettings, status, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, accountIsPremium, recommendedChannels, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags -> PeerInfoScreenData in
|> map { peerView, availablePanes, globalNotificationSettings, status, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, accountIsPremium, recommendedChannels, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags, isPremiumRequiredForStoryPosting -> PeerInfoScreenData in
var availablePanes = availablePanes
if let hasStories {
if hasStories {
@ -1138,7 +1147,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
appConfiguration: nil,
isPowerSavingEnabled: nil,
accountIsPremium: accountIsPremium,
hasSavedMessageTags: hasSavedMessageTags
hasSavedMessageTags: hasSavedMessageTags,
isPremiumRequiredForStoryPosting: isPremiumRequiredForStoryPosting
)
}
case let .group(groupId):
@ -1315,6 +1325,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
hasSavedMessageTags = .single(false)
}
let isPremiumRequiredForStoryPosting: Signal<Bool, NoError> = isPremiumRequiredForStoryPosting(context: context)
return combineLatest(queue: .mainQueue(),
context.account.viewTracker.peerView(groupId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: groupId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder),
@ -1331,9 +1343,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
accountIsPremium,
hasSavedMessages,
hasSavedMessagesChats,
hasSavedMessageTags
hasSavedMessageTags,
isPremiumRequiredForStoryPosting
)
|> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags -> Signal<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?
if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] {
discussionPeer = peer
@ -1426,7 +1439,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
appConfiguration: appConfiguration,
isPowerSavingEnabled: nil,
accountIsPremium: accountIsPremium,
hasSavedMessageTags: hasSavedMessageTags
hasSavedMessageTags: hasSavedMessageTags,
isPremiumRequiredForStoryPosting: isPremiumRequiredForStoryPosting
))
}
}
@ -1812,3 +1826,25 @@ func peerInfoIsChatMuted(peer: Peer?, peerNotificationSettings: TelegramPeerNoti
}
return chatIsMuted
}
private var isPremiumRequired: Bool?
private func isPremiumRequiredForStoryPosting(context: AccountContext) -> Signal<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
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
}, 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) {
let boostsController = channelStatsController(context: self.context, updatedPresentationData: controller.updatedPresentationData, peerId: self.peerId, section: .boosts, boostStatus: self.boostStatus)
let boostsController = channelStatsController(context: self.context, updatedPresentationData: controller.updatedPresentationData, peerId: self.peerId, section: .boosts, boostStatus: self.boostStatus, boostStatusUpdated: { [weak self] boostStatus in
if let self {
self.boostStatus = boostStatus
}
})
controller.push(boostsController)
} else {
let _ = combineLatest(
@ -8800,7 +8804,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .settings, forceDark: false, dismissed: nil)
self.controller?.push(controller)
case .premiumGift:
let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .settings)
let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .settings, completion: nil)
self.controller?.push(controller)
case .stickers:
if let settings = self.data?.globalSettings {
@ -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) {
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)
}

View File

@ -348,50 +348,61 @@ final class PeerInfoStoryGridScreenComponent: Component {
return
}
switch component.scope {
case .saved:
let selectedCount = paneNode.selectedItems.count
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: false).start()
paneNode.setIsSelectionModeActive(false)
(self.environment?.controller() as? PeerInfoStoryGridScreen)?.updateTitle()
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
let title: String = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(selectedCount))
environment.controller()?.present(UndoOverlayController(
presentationData: presentationData,
content: .info(title: nil, text: title, timeout: nil, customUndoText: nil),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
), in: .current)
case .archive:
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: true).start()
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
let title: String
let text: String
if component.peerId == component.context.account.peerId {
title = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(paneNode.selectedIds.count))
text = presentationData.strings.StoryList_TooltipStoriesSavedToProfileText
} else {
title = presentationData.strings.StoryList_TooltipStoriesSavedToChannel(Int32(paneNode.selectedIds.count))
text = presentationData.strings.Story_ToastSavedToChannelText
let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let peer else {
return
}
var isGroup = false
if case let .channel(channel) = peer, case .group = channel.info {
isGroup = true
}
environment.controller()?.present(UndoOverlayController(
presentationData: presentationData,
content: .info(title: title, text: text, timeout: nil, customUndoText: nil),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
), in: .current)
paneNode.clearSelection()
}
switch component.scope {
case .saved:
let selectedCount = paneNode.selectedItems.count
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: false).start()
paneNode.setIsSelectionModeActive(false)
(self.environment?.controller() as? PeerInfoStoryGridScreen)?.updateTitle()
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
let title: String = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(selectedCount))
environment.controller()?.present(UndoOverlayController(
presentationData: presentationData,
content: .info(title: nil, text: title, timeout: nil, customUndoText: nil),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
), in: .current)
case .archive:
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: true).start()
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
let title: String
let text: String
if component.peerId == component.context.account.peerId {
title = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(paneNode.selectedIds.count))
text = presentationData.strings.StoryList_TooltipStoriesSavedToProfileText
} else {
title = isGroup ? presentationData.strings.StoryList_TooltipStoriesSavedToGroup(Int32(paneNode.selectedIds.count)) : presentationData.strings.StoryList_TooltipStoriesSavedToChannel(Int32(paneNode.selectedIds.count))
text = isGroup ? presentationData.strings.Story_ToastSavedToGroupText : presentationData.strings.Story_ToastSavedToChannelText
}
environment.controller()?.present(UndoOverlayController(
presentationData: presentationData,
content: .info(title: title, text: text, timeout: nil, customUndoText: nil),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
), in: .current)
paneNode.clearSelection()
}
})
}
)),
environment: {},

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.frame = CGRect(origin: CGPoint(x: 16.0, y: 54.0), size: backSize)
self.backButton.frame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: environment.navigationHeight - 44.0), size: backSize)
if self.backButton.view.superview == nil {
if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar {
navigationBar.view.addSubview(self.backButton.view)

View File

@ -19,15 +19,17 @@ final class StoryAuthorInfoComponent: Component {
let strings: PresentationStrings
let peer: EnginePeer?
let forwardInfo: EngineStoryItem.ForwardInfo?
let author: EnginePeer?
let timestamp: Int32
let counters: Counters?
let isEdited: Bool
init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer?, forwardInfo: EngineStoryItem.ForwardInfo?, timestamp: Int32, counters: Counters?, isEdited: Bool) {
init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer?, forwardInfo: EngineStoryItem.ForwardInfo?, author: EnginePeer?, timestamp: Int32, counters: Counters?, isEdited: Bool) {
self.context = context
self.strings = strings
self.peer = peer
self.forwardInfo = forwardInfo
self.author = author
self.timestamp = timestamp
self.counters = counters
self.isEdited = isEdited
@ -46,6 +48,9 @@ final class StoryAuthorInfoComponent: Component {
if lhs.forwardInfo != rhs.forwardInfo {
return false
}
if lhs.author != rhs.author {
return false
}
if lhs.timestamp != rhs.timestamp {
return false
}
@ -121,6 +126,16 @@ final class StoryAuthorInfoComponent: Component {
}
subtitle = combinedString
subtitleTruncationType = .middle
} else if let author = component.author {
let authorName = author.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let timeString = stringForStoryActivityTimestamp(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, preciseTime: true, relativeTimestamp: component.timestamp, relativeTo: timestamp, short: true)
let combinedString = NSMutableAttributedString()
combinedString.append(NSAttributedString(string: authorName, font: Font.medium(11.0), textColor: titleColor))
if timeString.count < 6 {
combinedString.append(NSAttributedString(string: "\(timeString)", font: Font.regular(11.0), textColor: subtitleColor))
}
subtitle = combinedString
subtitleTruncationType = .middle
} else {
var subtitleString = stringForStoryActivityTimestamp(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, preciseTime: true, relativeTimestamp: component.timestamp, relativeTo: timestamp)
if component.isEdited {
@ -176,7 +191,16 @@ final class StoryAuthorInfoComponent: Component {
self.repostIconView = nil
repostIconView.removeFromSuperview()
}
var authorPeer: EnginePeer?
if let forwardInfo = component.forwardInfo, case let .known(peer, _, _) = forwardInfo {
authorPeer = peer
} else if let author = component.author {
authorPeer = author
}
if let peer = authorPeer {
let avatarNode: AvatarNode
if let current = self.avatarNode {
avatarNode = current

View File

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

View File

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

View File

@ -1443,9 +1443,6 @@ public class VideoMessageCameraScreen: ViewController {
deinit {
self.audioSessionDisposable?.dispose()
if #available(iOS 13.0, *) {
try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(false)
}
}
override public func loadDisplayNode() {
@ -1688,9 +1685,6 @@ public class VideoMessageCameraScreen: ViewController {
}
self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: audioSessionType, activate: { [weak self] _ in
if #available(iOS 13.0, *) {
try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(true)
}
if let self {
Queue.mainQueue().async {
self.node.setupCamera()

View File

@ -450,6 +450,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var mediaRestrictedTooltipControllerMode = true
weak var checksTooltipController: TooltipController?
weak var copyProtectionTooltipController: TooltipController?
weak var emojiPackTooltipController: TooltipScreen?
var currentMessageTooltipScreens: [(TooltipScreen, ListViewItemNode)] = []
@ -965,14 +966,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
return true
}
if let peer = peer as? TelegramChannel, peer.hasPermission(.changeInfo) {
let _ = (context.engine.peers.getChannelBoostStatus(peerId: peer.id)
|> deliverOnMainQueue).start(next: { [weak self] boostStatus in
guard let self else {
return
}
self.push(ChannelAppearanceScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peerId: peer.id, boostStatus: boostStatus))
})
if let peer = peer as? TelegramChannel {
if peer.flags.contains(.isCreator) || peer.adminRights?.rights.contains(.canChangeInfo) == true {
let _ = (context.engine.peers.getChannelBoostStatus(peerId: peer.id)
|> deliverOnMainQueue).start(next: { [weak self] boostStatus in
guard let self else {
return
}
self.push(ChannelAppearanceScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peerId: peer.id, boostStatus: boostStatus))
})
}
return true
}
guard message.effectivelyIncoming(strongSelf.context.account.peerId), let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
@ -15714,42 +15717,53 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let rect = self.chatDisplayNode.frameForEmojiButton(), self.effectiveNavigationController?.topViewController === self else {
return
}
guard let emojiPack = (self.peerView?.cachedData as? CachedChannelData)?.emojiPack, let thumbnailFileId = emojiPack.thumbnailFileId else {
guard let peerId = self.chatLocation.peerId, let emojiPack = (self.peerView?.cachedData as? CachedChannelData)?.emojiPack, let thumbnailFileId = emojiPack.thumbnailFileId else {
return
}
let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [thumbnailFileId])
|> deliverOnMainQueue).start(next: { [weak self] files in
guard let self, let emojiFile = files.values.first else {
let _ = (ApplicationSpecificNotice.groupEmojiPackSuggestion(accountManager: self.context.sharedContext.accountManager, peerId: peerId)
|> deliverOnMainQueue).start(next: { [weak self] counter in
guard let self, counter == 0 else {
return
}
let textFont = Font.regular(self.presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)
let boldTextFont = Font.bold(self.presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)
let textColor = UIColor.white
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in
return nil
})
let text = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(self.presentationData.strings.Chat_GroupEmojiTooltip(emojiPack.title).string, attributes: markdownAttributes))
let range = (text.string as NSString).range(of: "#")
if range.location != NSNotFound {
text.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: emojiFile.fileId.id, file: emojiFile), range: range)
}
let tooltipScreen = TooltipScreen(
context: self.context,
account: self.context.account,
sharedContext: self.context.sharedContext,
text: .attributedString(text: text),
location: .point(rect.offsetBy(dx: 0.0, dy: -3.0), .bottom),
displayDuration: .default,
cornerRadius: 10.0,
shouldDismissOnTouch: { _, _ in
return .ignore
let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [thumbnailFileId])
|> deliverOnMainQueue).start(next: { [weak self] files in
guard let self, let emojiFile = files.values.first else {
return
}
)
self.present(tooltipScreen, in: .current)
let textFont = Font.regular(self.presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)
let boldTextFont = Font.bold(self.presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)
let textColor = UIColor.white
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in
return nil
})
let text = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(self.presentationData.strings.Chat_GroupEmojiTooltip(emojiPack.title).string, attributes: markdownAttributes))
let range = (text.string as NSString).range(of: "#")
if range.location != NSNotFound {
text.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: emojiFile.fileId.id, file: emojiFile), range: range)
}
let tooltipScreen = TooltipScreen(
context: self.context,
account: self.context.account,
sharedContext: self.context.sharedContext,
text: .attributedString(text: text),
location: .point(rect.offsetBy(dx: 0.0, dy: -3.0), .bottom),
displayDuration: .default,
cornerRadius: 10.0,
shouldDismissOnTouch: { _, _ in
return .ignore
}
)
self.present(tooltipScreen, in: .current)
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(
storyTarget: nil,
isForcedTarget: false,
isPeerArchived: false,
transitionOut: nil
)

View File

@ -3705,6 +3705,17 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
strongSelf.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in
return (.media(mode: .other, expanded: nil, focused: false), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId)
})
if let emojiPackTooltipController = strongSelf.controller?.emojiPackTooltipController {
strongSelf.controller?.emojiPackTooltipController = nil
emojiPackTooltipController.dismiss()
Queue.mainQueue().after(0.1) {
if let inputNode = strongSelf.inputNode as? ChatEntityKeyboardInputNode {
inputNode.scrollToGroupEmoji()
}
}
}
})
}
}

View File

@ -1070,7 +1070,25 @@ extension ChatControllerImpl {
if case .scheduledMessages = self.presentationInterfaceState.subject {
isScheduledMessages = true
}
let controller = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), threadTitle: self.threadInfo?.title, chatLocation: self.chatLocation, isScheduledMessages: isScheduledMessages, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, subject: subject, saveEditedPhotos: saveEditedPhotos)
let controller = MediaPickerScreen(
context: self.context,
updatedPresentationData: self.updatedPresentationData,
peer: EnginePeer(peer),
threadTitle: self.threadInfo?.title,
chatLocation: self.chatLocation,
isScheduledMessages: isScheduledMessages,
bannedSendPhotos: bannedSendPhotos,
bannedSendVideos: bannedSendVideos,
canBoostToUnrestrict: (self.presentationInterfaceState.boostsToUnrestrict ?? 0) > 0 && bannedSendPhotos?.1 != true && bannedSendVideos?.1 != true,
subject: subject,
saveEditedPhotos: saveEditedPhotos
)
controller.openBoost = { [weak self, weak controller] in
if let self {
controller?.dismiss()
self.interfaceInteraction?.openBoostToUnrestrict()
}
}
let mediaPickerContext = controller.mediaPickerContext
controller.openCamera = { [weak self] cameraView in
self?.openCamera(cameraView: cameraView)

View File

@ -587,7 +587,7 @@ func openResolvedUrlImpl(
}
case let .premiumMultiGift(reference):
dismissInput()
let controller = context.sharedContext.makePremiumGiftController(context: context, source: .deeplink(reference))
let controller = context.sharedContext.makePremiumGiftController(context: context, source: .deeplink(reference), completion: nil)
if let navigationController = navigationController {
navigationController.pushViewController(controller, animated: true)
}

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

View File

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

View File

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