Various improvements

This commit is contained in:
Isaac 2025-10-11 00:56:21 +08:00
parent e3ee0126b2
commit b9b38e5fbf
87 changed files with 1337 additions and 498 deletions

View file

@ -1134,6 +1134,51 @@ public enum StarsWithdrawalScreenSubject {
case postSuggestionModification(current: CurrencyAmount, timestamp: Int32?, completion: (CurrencyAmount, Int32?) -> Void) case postSuggestionModification(current: CurrencyAmount, timestamp: Int32?, completion: (CurrencyAmount, Int32?) -> Void)
} }
public enum ChannelMembersSearchControllerMode {
case promote
case ban
case inviteToCall
}
public enum ChannelMembersSearchFilter {
case exclude([EnginePeer.Id])
case disable([EnginePeer.Id])
case excludeNonMembers
case excludeBots
}
public final class ChannelMembersSearchControllerParams {
public let context: AccountContext
public let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
public let peerId: EnginePeer.Id
public let forceTheme: PresentationTheme?
public let mode: ChannelMembersSearchControllerMode
public let filters: [ChannelMembersSearchFilter]
public let openPeer: (EnginePeer, RenderedChannelParticipant?) -> Void
public init(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
peerId: EnginePeer.Id,
forceTheme: PresentationTheme? = nil,
mode: ChannelMembersSearchControllerMode,
filters: [ChannelMembersSearchFilter] = [],
openPeer: @escaping (EnginePeer, RenderedChannelParticipant?) -> Void
) {
self.context = context
self.updatedPresentationData = updatedPresentationData
self.peerId = peerId
self.forceTheme = forceTheme
self.mode = mode
self.filters = filters
self.openPeer = openPeer
}
}
public protocol ChannelMembersSearchController: ViewController {
var copyInviteLink: (() -> Void)? { get set }
}
public protocol SharedAccountContext: AnyObject { public protocol SharedAccountContext: AnyObject {
var sharedContainerPath: String { get } var sharedContainerPath: String { get }
var basePath: String { get } var basePath: String { get }
@ -1365,6 +1410,8 @@ public protocol SharedAccountContext: AnyObject {
func makeBirthdaySuggestionScreen(context: AccountContext, peerId: EnginePeer.Id, completion: @escaping (TelegramBirthday) -> Void) -> ViewController func makeBirthdaySuggestionScreen(context: AccountContext, peerId: EnginePeer.Id, completion: @escaping (TelegramBirthday) -> Void) -> ViewController
func makeBirthdayAcceptSuggestionScreen(context: AccountContext, birthday: TelegramBirthday, settings: Promise<AccountPrivacySettings?>, openSettings: @escaping () -> Void, completion: @escaping (TelegramBirthday) -> Void) -> ViewController func makeBirthdayAcceptSuggestionScreen(context: AccountContext, birthday: TelegramBirthday, settings: Promise<AccountPrivacySettings?>, openSettings: @escaping () -> Void, completion: @escaping (TelegramBirthday) -> Void) -> ViewController
func makeChannelMembersSearchController(params: ChannelMembersSearchControllerParams) -> ChannelMembersSearchController
func makeDebugSettingsController(context: AccountContext?) -> ViewController? func makeDebugSettingsController(context: AccountContext?) -> ViewController?
func openCreateGroupCallUI(context: AccountContext, peerIds: [EnginePeer.Id], parentController: ViewController) func openCreateGroupCallUI(context: AccountContext, peerIds: [EnginePeer.Id], parentController: ViewController)

View file

@ -1156,6 +1156,11 @@ public final class AvatarNode: ASDisplayNode {
public let contentNode: ContentNode public let contentNode: ContentNode
private var storyIndicator: ComponentView<Empty>? private var storyIndicator: ComponentView<Empty>?
private var contentMaskView: UIView?
private var storyIndicatorMaskView: UIView?
private var liveBadgeMaskView: UIImageView?
private var liveBadgeStoryIndicatorMaskView: UIImageView?
private var liveBadgeView: UIImageView?
public private(set) var storyPresentationParams: StoryPresentationParams? public private(set) var storyPresentationParams: StoryPresentationParams?
private var loadingStatuses = Bag<Disposable>() private var loadingStatuses = Bag<Disposable>()
@ -1164,22 +1169,26 @@ public final class AvatarNode: ASDisplayNode {
public var totalCount: Int public var totalCount: Int
public var unseenCount: Int public var unseenCount: Int
public var hasUnseenCloseFriendsItems: Bool public var hasUnseenCloseFriendsItems: Bool
public var hasLiveItems: Bool
public var progress: Float? public var progress: Float?
public init( public init(
totalCount: Int, totalCount: Int,
unseenCount: Int, unseenCount: Int,
hasUnseenCloseFriendsItems: Bool, hasUnseenCloseFriendsItems: Bool,
hasLiveItems: Bool,
progress: Float? = nil progress: Float? = nil
) { ) {
self.totalCount = totalCount self.totalCount = totalCount
self.unseenCount = unseenCount self.unseenCount = unseenCount
self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems
self.hasLiveItems = hasLiveItems
self.progress = progress self.progress = progress
} }
} }
public private(set) var storyStats: StoryStats? public private(set) var storyStats: StoryStats?
public var displayLiveBadge: Bool = false
public var font: UIFont { public var font: UIFont {
get { get {
@ -1459,6 +1468,7 @@ public final class AvatarNode: ASDisplayNode {
component: AnyComponent(AvatarStoryIndicatorComponent( component: AnyComponent(AvatarStoryIndicatorComponent(
hasUnseen: storyStats.unseenCount != 0, hasUnseen: storyStats.unseenCount != 0,
hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriendsItems, hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriendsItems,
hasLiveItems: storyStats.hasLiveItems,
colors: AvatarStoryIndicatorComponent.Colors( colors: AvatarStoryIndicatorComponent.Colors(
unseenColors: storyPresentationParams.colors.unseenColors, unseenColors: storyPresentationParams.colors.unseenColors,
unseenCloseFriendsColors: storyPresentationParams.colors.unseenCloseFriendsColors, unseenCloseFriendsColors: storyPresentationParams.colors.unseenCloseFriendsColors,
@ -1494,6 +1504,124 @@ public final class AvatarNode: ASDisplayNode {
} }
} }
} }
if self.displayLiveBadge, let storyStats = self.storyStats, storyStats.hasLiveItems {
let contentMaskView: UIView
let storyIndicatorMaskView: UIView
let liveBadgeMaskView: UIImageView
let liveBadgeStoryIndicatorMaskView: UIImageView
let liveBadgeView: UIImageView
var liveBadgeTransition = transition
if let current = self.contentMaskView {
contentMaskView = current
} else {
liveBadgeTransition = liveBadgeTransition.withAnimation(.none)
contentMaskView = UIView()
contentMaskView.backgroundColor = .white
if let filter = CALayer.luminanceToAlpha() {
contentMaskView.layer.filters = [filter]
}
self.contentMaskView = contentMaskView
self.contentNode.view.mask = contentMaskView
}
if let current = self.storyIndicatorMaskView {
storyIndicatorMaskView = current
} else {
storyIndicatorMaskView = UIView()
storyIndicatorMaskView.backgroundColor = .white
if let filter = CALayer.luminanceToAlpha() {
storyIndicatorMaskView.layer.filters = [filter]
}
self.storyIndicatorMaskView = storyIndicatorMaskView
self.storyIndicator?.view?.mask = storyIndicatorMaskView
}
if let current = self.liveBadgeView {
liveBadgeView = current
} else {
liveBadgeView = UIImageView()
//TODO:localize
let liveString = NSAttributedString(string: "LIVE", font: Font.semibold(10.0), textColor: .white)
let liveStringBounds = liveString.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
let liveBadgeSize = CGSize(width: ceil(liveStringBounds.width) + 4.0 * 2.0, height: ceil(liveStringBounds.height) + 2.0 * 2.0)
liveBadgeView.image = generateImage(liveBadgeSize, rotatedContext: { size, context in
UIGraphicsPushContext(context)
defer {
UIGraphicsPopContext()
}
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor(rgb: 0xFF2D55).cgColor)
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: size.height * 0.5).cgPath)
context.fillPath()
liveString.draw(at: CGPoint(x: floorToScreenPixels((size.width - liveStringBounds.width) * 0.5), y: floorToScreenPixels((size.height - liveStringBounds.height) * 0.5)))
})
self.view.addSubview(liveBadgeView)
self.liveBadgeView = liveBadgeView
}
if let current = self.liveBadgeMaskView {
liveBadgeMaskView = current
} else {
liveBadgeMaskView = UIImageView()
self.liveBadgeMaskView = liveBadgeMaskView
contentMaskView.addSubview(liveBadgeMaskView)
if let image = liveBadgeView.image {
liveBadgeMaskView.image = generateStretchableFilledCircleImage(diameter: image.size.height + 2.0 * 2.0, color: .black)
}
}
if let current = self.liveBadgeStoryIndicatorMaskView {
liveBadgeStoryIndicatorMaskView = current
} else {
liveBadgeStoryIndicatorMaskView = UIImageView()
self.liveBadgeStoryIndicatorMaskView = liveBadgeStoryIndicatorMaskView
storyIndicatorMaskView.addSubview(liveBadgeStoryIndicatorMaskView)
if let image = liveBadgeView.image {
liveBadgeStoryIndicatorMaskView.image = generateStretchableFilledCircleImage(diameter: image.size.height + 2.0 * 2.0, color: .black)
}
}
liveBadgeTransition.setFrame(view: contentMaskView, frame: CGRect(origin: CGPoint(), size: size))
liveBadgeTransition.setFrame(view: storyIndicatorMaskView, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -6.0, dy: -6.0))
if let image = liveBadgeView.image {
let badgeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - image.size.width) * 0.5), y: size.height + 5.0 - image.size.height), size: image.size)
liveBadgeTransition.setFrame(view: liveBadgeView, frame: badgeFrame)
liveBadgeTransition.setFrame(view: liveBadgeMaskView, frame: self.contentNode.view.convert(badgeFrame.insetBy(dx: -2.0, dy: -2.0), from: self.view))
liveBadgeTransition.setFrame(view: liveBadgeStoryIndicatorMaskView, frame: badgeFrame.insetBy(dx: -2.0, dy: -2.0).offsetBy(dx: 1.0, dy: 0.0))
}
} else {
if let contentMaskView = self.contentMaskView {
self.contentMaskView = nil
contentMaskView.removeFromSuperview()
self.contentNode.view.mask = nil
}
if let storyIndicatorMaskView = self.storyIndicatorMaskView {
self.storyIndicatorMaskView = nil
storyIndicatorMaskView.removeFromSuperview()
self.storyIndicator?.view?.mask = nil
}
if let liveBadgeMaskView = self.liveBadgeMaskView {
self.liveBadgeMaskView = nil
liveBadgeMaskView.removeFromSuperview()
}
if let liveBadgeStoryIndicatorMaskView = self.liveBadgeStoryIndicatorMaskView {
self.liveBadgeStoryIndicatorMaskView = nil
liveBadgeStoryIndicatorMaskView.removeFromSuperview()
}
if let liveBadgeView = self.liveBadgeView {
self.liveBadgeView = nil
liveBadgeView.removeFromSuperview()
}
}
} }
public func cancelLoading() { public func cancelLoading() {

View file

@ -2251,7 +2251,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
stats: EngineChatList.StoryStats( stats: EngineChatList.StoryStats(
totalCount: rawStoryArchiveSubscriptions.items.count, totalCount: rawStoryArchiveSubscriptions.items.count,
unseenCount: unseenCount, unseenCount: unseenCount,
hasUnseenCloseFriends: hasUnseenCloseFriends hasUnseenCloseFriends: hasUnseenCloseFriends,
hasLiveItems: false
), ),
hasUnseenCloseFriends: hasUnseenCloseFriends hasUnseenCloseFriends: hasUnseenCloseFriends
) )

View file

@ -367,7 +367,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
animationCache: animationCache, animationCache: animationCache,
animationRenderer: animationRenderer, animationRenderer: animationRenderer,
storyStats: storyStats.flatMap { stats in storyStats: storyStats.flatMap { stats in
return (stats.totalCount, unseen: stats.unseenCount, stats.hasUnseenCloseFriends) return (stats.totalCount, unseen: stats.unseenCount, stats.hasUnseenCloseFriends, hasLiveItems: stats.hasLiveItems)
}, },
openStories: { itemPeer, sourceNode in openStories: { itemPeer, sourceNode in
guard case let .peer(_, chatPeer) = itemPeer, let peer = chatPeer else { guard case let .peer(_, chatPeer) = itemPeer, let peer = chatPeer else {
@ -829,7 +829,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
} }
} }
}, arrowAction: nil, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer, storyStats: storyStats.flatMap { stats in }, arrowAction: nil, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer, storyStats: storyStats.flatMap { stats in
return (stats.totalCount, stats.unseenCount, stats.hasUnseenCloseFriends) return (stats.totalCount, stats.unseenCount, stats.hasUnseenCloseFriends, stats.hasLiveItems)
}, openStories: { itemPeer, sourceNode in }, openStories: { itemPeer, sourceNode in
guard case let .peer(_, chatPeer) = itemPeer, let peer = chatPeer else { guard case let .peer(_, chatPeer) = itemPeer, let peer = chatPeer else {
return return
@ -1002,7 +1002,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
} }
} }
}, arrowAction: nil, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer, storyStats: storyStats.flatMap { stats in }, arrowAction: nil, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer, storyStats: storyStats.flatMap { stats in
return (stats.totalCount, stats.unseenCount, stats.hasUnseenCloseFriends) return (stats.totalCount, stats.unseenCount, stats.hasUnseenCloseFriends, stats.hasLiveItems)
}, openStories: { itemPeer, sourceNode in }, openStories: { itemPeer, sourceNode in
guard case let .peer(_, chatPeer) = itemPeer, let peer = chatPeer else { guard case let .peer(_, chatPeer) = itemPeer, let peer = chatPeer else {
return return
@ -1080,7 +1080,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
peerContextAction(EnginePeer(peer.peer), .search(nil), node, gesture, location) peerContextAction(EnginePeer(peer.peer), .search(nil), node, gesture, location)
} }
}, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer, storyStats: storyStats.flatMap { stats in }, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer, storyStats: storyStats.flatMap { stats in
return (stats.totalCount, stats.unseenCount, stats.hasUnseenCloseFriends) return (stats.totalCount, stats.unseenCount, stats.hasUnseenCloseFriends, stats.hasLiveItems)
}, openStories: { itemPeer, sourceNode in }, openStories: { itemPeer, sourceNode in
guard case let .peer(_, chatPeer) = itemPeer, let peer = chatPeer else { guard case let .peer(_, chatPeer) = itemPeer, let peer = chatPeer else {
return return
@ -1205,7 +1205,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
stats: EngineChatList.StoryStats( stats: EngineChatList.StoryStats(
totalCount: stats.totalCount, totalCount: stats.totalCount,
unseenCount: stats.unseenCount, unseenCount: stats.unseenCount,
hasUnseenCloseFriends: stats.hasUnseenCloseFriends hasUnseenCloseFriends: stats.hasUnseenCloseFriends,
hasLiveItems: stats.hasLiveItems
), ),
hasUnseenCloseFriends: stats.hasUnseenCloseFriends hasUnseenCloseFriends: stats.hasUnseenCloseFriends
) )

View file

@ -1601,6 +1601,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
self.avatarContainerNode = ASDisplayNode() self.avatarContainerNode = ASDisplayNode()
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
self.avatarNode.displayLiveBadge = true
self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true self.highlightedBackgroundNode.isLayerBacked = true
@ -1791,7 +1792,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
return AvatarNode.StoryStats( return AvatarNode.StoryStats(
totalCount: storyState.stats.totalCount, totalCount: storyState.stats.totalCount,
unseenCount: storyState.stats.unseenCount, unseenCount: storyState.stats.unseenCount,
hasUnseenCloseFriendsItems: storyState.hasUnseenCloseFriends hasUnseenCloseFriendsItems: storyState.hasUnseenCloseFriends,
hasLiveItems: storyState.stats.hasLiveItems
) )
}, presentationParams: AvatarNode.StoryPresentationParams( }, presentationParams: AvatarNode.StoryPresentationParams(
colors: AvatarNode.Colors(theme: item.presentationData.theme), colors: AvatarNode.Colors(theme: item.presentationData.theme),

View file

@ -207,7 +207,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
let arrowAction: (() -> Void)? let arrowAction: (() -> Void)?
let animationCache: AnimationCache? let animationCache: AnimationCache?
let animationRenderer: MultiAnimationRenderer? let animationRenderer: MultiAnimationRenderer?
let storyStats: (total: Int, unseen: Int, hasUnseenCloseFriends: Bool)? let storyStats: (total: Int, unseen: Int, hasUnseenCloseFriends: Bool, hasLiveItems: Bool)?
let openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)? let openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)?
let adButtonAction: ((ASDisplayNode) -> Void)? let adButtonAction: ((ASDisplayNode) -> Void)?
let visibilityUpdated: ((Bool) -> Void)? let visibilityUpdated: ((Bool) -> Void)?
@ -253,7 +253,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, arrowAction: (() -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, arrowAction: (() -> Void)? = nil,
animationCache: AnimationCache? = nil, animationCache: AnimationCache? = nil,
animationRenderer: MultiAnimationRenderer? = nil, animationRenderer: MultiAnimationRenderer? = nil,
storyStats: (total: Int, unseen: Int, hasUnseenCloseFriends: Bool)? = nil, storyStats: (total: Int, unseen: Int, hasUnseenCloseFriends: Bool, hasLiveItems: Bool)? = nil,
openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)? = nil, openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)? = nil,
adButtonAction: ((ASDisplayNode) -> Void)? = nil, adButtonAction: ((ASDisplayNode) -> Void)? = nil,
visibilityUpdated: ((Bool) -> Void)? = nil visibilityUpdated: ((Bool) -> Void)? = nil
@ -1286,7 +1286,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
return AvatarNode.StoryStats( return AvatarNode.StoryStats(
totalCount: stats.total, totalCount: stats.total,
unseenCount: stats.unseen, unseenCount: stats.unseen,
hasUnseenCloseFriendsItems: stats.hasUnseenCloseFriends hasUnseenCloseFriendsItems: stats.hasUnseenCloseFriends,
hasLiveItems: stats.hasLiveItems
) )
}, },
presentationParams: AvatarNode.StoryPresentationParams( presentationParams: AvatarNode.StoryPresentationParams(

View file

@ -1233,6 +1233,9 @@ public class Window1 {
let updatedInputOffset = inputHeightOffsetForLayout(self.windowLayout) let updatedInputOffset = inputHeightOffsetForLayout(self.windowLayout)
if !previousInputOffset.isEqual(to: updatedInputOffset) { if !previousInputOffset.isEqual(to: updatedInputOffset) {
let hide = updatingLayout.transition.isAnimated && updatingLayout.layout.upperKeyboardInputPositionBound == updatingLayout.layout.size.height let hide = updatingLayout.transition.isAnimated && updatingLayout.layout.upperKeyboardInputPositionBound == updatingLayout.layout.size.height
if hide {
print("hide with \(updatingLayout.transition)")
}
self.keyboardManager?.updateInteractiveInputOffset(updatedInputOffset, transition: updatingLayout.transition, completion: { [weak self] in self.keyboardManager?.updateInteractiveInputOffset(updatedInputOffset, transition: updatingLayout.transition, completion: { [weak self] in
if let strongSelf = self, hide { if let strongSelf = self, hide {
strongSelf.updateLayout { strongSelf.updateLayout {
@ -1370,8 +1373,14 @@ public class Window1 {
} }
if canDismiss, let inputHeight = self.windowLayout.inputHeight, currentLocation.y + (self.keyboardGestureAccessoryHeight ?? 0.0) > self.windowLayout.size.height - inputHeight { if canDismiss, let inputHeight = self.windowLayout.inputHeight, currentLocation.y + (self.keyboardGestureAccessoryHeight ?? 0.0) > self.windowLayout.size.height - inputHeight {
let springDuration: CGFloat
if #available(iOS 26.0, *) {
springDuration = 0.3832
} else {
springDuration = 0.25
}
self.updateLayout { self.updateLayout {
$0.update(upperKeyboardInputPositionBound: self.windowLayout.size.height, transition: .animated(duration: 0.25, curve: .spring), overrideTransition: false) $0.update(upperKeyboardInputPositionBound: self.windowLayout.size.height, transition: .animated(duration: springDuration, curve: .spring), overrideTransition: false)
} }
} else { } else {
self.updateLayout { self.updateLayout {

View file

@ -1749,7 +1749,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
return AvatarNode.StoryStats( return AvatarNode.StoryStats(
totalCount: storyStats.totalCount, totalCount: storyStats.totalCount,
unseenCount: storyStats.unseenCount, unseenCount: storyStats.unseenCount,
hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends,
hasLiveItems: storyStats.hasLiveItems
) )
}, presentationParams: AvatarNode.StoryPresentationParams( }, presentationParams: AvatarNode.StoryPresentationParams(
colors: AvatarNode.Colors(theme: item.presentationData.theme), colors: AvatarNode.Colors(theme: item.presentationData.theme),

View file

@ -65,7 +65,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent
if let credentialsPromise = credentialsPromise { if let credentialsPromise = credentialsPromise {
credentialsSignal = credentialsPromise.get() credentialsSignal = credentialsPromise.get()
} else { } else {
credentialsSignal = context.engine.calls.getGroupCallStreamCredentials(peerId: peerId, revokePreviousCredentials: false) credentialsSignal = context.engine.calls.getGroupCallStreamCredentials(peerId: peerId, isLiveStream: false, revokePreviousCredentials: false)
|> `catch` { _ -> Signal<GroupCallStreamCredentials, NoError> in |> `catch` { _ -> Signal<GroupCallStreamCredentials, NoError> in
return .never() return .never()
} }

View file

@ -754,7 +754,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
|> deliverOnMainQueue).start(next: { peerView in |> deliverOnMainQueue).start(next: { peerView in
updateState { current in updateState { current in
var dismissController: (() -> Void)? var dismissController: (() -> Void)?
let controller = ChannelMembersSearchController(context: context, peerId: peerId, mode: .promote, filters: [], openPeer: { peer, participant in let controller = ChannelMembersSearchControllerImpl(params: ChannelMembersSearchControllerParams(context: context, peerId: peerId, mode: .promote, filters: [], openPeer: { peer, participant in
dismissController?() dismissController?()
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
if peer.id == context.account.peerId { if peer.id == context.account.peerId {
@ -784,7 +784,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
} }
pushControllerImpl?(channelAdminController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, adminId: peer.id, initialParticipant: participant?.participant, updated: { _ in pushControllerImpl?(channelAdminController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, adminId: peer.id, initialParticipant: participant?.participant, updated: { _ in
}, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership)) }, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership))
}) }))
dismissController = { [weak controller] in dismissController = { [weak controller] in
controller?.dismiss() controller?.dismiss()
} }

View file

@ -300,7 +300,7 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat
} }
}, addPeer: { }, addPeer: {
var dismissController: (() -> Void)? var dismissController: (() -> Void)?
let controller = ChannelMembersSearchController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, mode: .ban, openPeer: { peer, participant in let controller = ChannelMembersSearchControllerImpl(params: ChannelMembersSearchControllerParams(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, mode: .ban, openPeer: { peer, participant in
if let participant = participant { if let participant = participant {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
switch participant.participant { switch participant.participant {
@ -329,7 +329,7 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat
dismissController?() dismissController?()
})) }))
}) })
}) }))
dismissController = { [weak controller] in dismissController = { [weak controller] in
controller?.dismiss() controller?.dismiss()
} }

View file

@ -7,20 +7,7 @@ import TelegramPresentationData
import AccountContext import AccountContext
import SearchUI import SearchUI
public enum ChannelMembersSearchControllerMode { public final class ChannelMembersSearchControllerImpl: ViewController, ChannelMembersSearchController {
case promote
case ban
case inviteToCall
}
public enum ChannelMembersSearchFilter {
case exclude([EnginePeer.Id])
case disable([EnginePeer.Id])
case excludeNonMembers
case excludeBots
}
public final class ChannelMembersSearchController: ViewController {
private let queue = Queue() private let queue = Queue()
private let context: AccountContext private let context: AccountContext
@ -43,15 +30,15 @@ public final class ChannelMembersSearchController: ViewController {
private var searchContentNode: NavigationBarSearchContentNode? private var searchContentNode: NavigationBarSearchContentNode?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id, forceTheme: PresentationTheme? = nil, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter] = [], openPeer: @escaping (EnginePeer, RenderedChannelParticipant?) -> Void) { public init(params: ChannelMembersSearchControllerParams) {
self.context = context self.context = params.context
self.peerId = peerId self.peerId = params.peerId
self.mode = mode self.mode = params.mode
self.openPeer = openPeer self.openPeer = params.openPeer
self.filters = filters self.filters = params.filters
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.presentationData = params.updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
self.forceTheme = forceTheme self.forceTheme = params.forceTheme
if let forceTheme = forceTheme { if let forceTheme = params.forceTheme {
self.presentationData = self.presentationData.withUpdated(theme: forceTheme) self.presentationData = self.presentationData.withUpdated(theme: forceTheme)
} }
@ -79,7 +66,7 @@ public final class ChannelMembersSearchController: ViewController {
}) })
self.navigationBar?.setContentNode(self.searchContentNode, animated: false) self.navigationBar?.setContentNode(self.searchContentNode, animated: false)
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) self.presentationDataDisposable = ((params.updatedPresentationData?.signal ?? params.context.sharedContext.presentationData)
|> deliverOnMainQueue).start(next: { [weak self] presentationData in |> deliverOnMainQueue).start(next: { [weak self] presentationData in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -88,7 +75,7 @@ public final class ChannelMembersSearchController: ViewController {
strongSelf.controllerNode.updatePresentationData(presentationData) strongSelf.controllerNode.updatePresentationData(presentationData)
}) })
let _ = (context.account.postbox.loadedPeerWithId(peerId) let _ = (params.context.account.postbox.loadedPeerWithId(peerId)
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { [weak self] peer in |> deliverOnMainQueue).start(next: { [weak self] peer in
if let strongSelf = self { if let strongSelf = self {

View file

@ -1027,7 +1027,7 @@ public func channelPermissionsController(context: AccountContext, updatedPresent
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { peerId, _ in |> deliverOnMainQueue).start(next: { peerId, _ in
var dismissController: (() -> Void)? var dismissController: (() -> Void)?
let controller = ChannelMembersSearchController(context: context, peerId: peerId, mode: .ban, filters: [.disable([context.account.peerId])], openPeer: { peer, participant in let controller = ChannelMembersSearchControllerImpl(params: ChannelMembersSearchControllerParams(context: context, peerId: peerId, mode: .ban, filters: [.disable([context.account.peerId])], openPeer: { peer, participant in
if let participant = participant { if let participant = participant {
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
switch participant.participant { switch participant.participant {
@ -1048,7 +1048,7 @@ public func channelPermissionsController(context: AccountContext, updatedPresent
upgradedToSupergroupImpl?(upgradedPeerId, f) upgradedToSupergroupImpl?(upgradedPeerId, f)
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}) })
}) }))
dismissController = { [weak controller] in dismissController = { [weak controller] in
controller?.dismiss() controller?.dismiss()
} }

View file

@ -282,11 +282,13 @@ public struct PeerStoryStats: Equatable {
public var totalCount: Int public var totalCount: Int
public var unseenCount: Int public var unseenCount: Int
public var hasUnseenCloseFriends: Bool public var hasUnseenCloseFriends: Bool
public var hasLiveItems: Bool
public init(totalCount: Int, unseenCount: Int, hasUnseenCloseFriends: Bool) { public init(totalCount: Int, unseenCount: Int, hasUnseenCloseFriends: Bool, hasLiveItems: Bool) {
self.totalCount = totalCount self.totalCount = totalCount
self.unseenCount = unseenCount self.unseenCount = unseenCount
self.hasUnseenCloseFriends = hasUnseenCloseFriends self.hasUnseenCloseFriends = hasUnseenCloseFriends
self.hasLiveItems = hasLiveItems
} }
} }
@ -305,9 +307,9 @@ func fetchPeerStoryStats(postbox: PostboxImpl, peerId: PeerId) -> PeerStoryStats
if topItems.isExact { if topItems.isExact {
let stats = postbox.storyItemsTable.getStats(peerId: peerId, maxSeenId: maxSeenId) let stats = postbox.storyItemsTable.getStats(peerId: peerId, maxSeenId: maxSeenId)
return PeerStoryStats(totalCount: stats.total, unseenCount: stats.unseen, hasUnseenCloseFriends: stats.hasUnseenCloseFriends) return PeerStoryStats(totalCount: stats.total, unseenCount: stats.unseen, hasUnseenCloseFriends: stats.hasUnseenCloseFriends, hasLiveItems: stats.hasLiveItems)
} else { } else {
return PeerStoryStats(totalCount: 1, unseenCount: topItems.id > maxSeenId ? 1 : 0, hasUnseenCloseFriends: false) return PeerStoryStats(totalCount: 1, unseenCount: topItems.id > maxSeenId ? 1 : 0, hasUnseenCloseFriends: false, hasLiveItems: false)
} }
} }

View file

@ -5,17 +5,20 @@ public final class StoryItemsTableEntry: Equatable {
public let id: Int32 public let id: Int32
public let expirationTimestamp: Int32? public let expirationTimestamp: Int32?
public let isCloseFriends: Bool public let isCloseFriends: Bool
public let isLiveStream: Bool
public init( public init(
value: CodableEntry, value: CodableEntry,
id: Int32, id: Int32,
expirationTimestamp: Int32?, expirationTimestamp: Int32?,
isCloseFriends: Bool isCloseFriends: Bool,
isLiveStream: Bool
) { ) {
self.value = value self.value = value
self.id = id self.id = id
self.expirationTimestamp = expirationTimestamp self.expirationTimestamp = expirationTimestamp
self.isCloseFriends = isCloseFriends self.isCloseFriends = isCloseFriends
self.isLiveStream = isLiveStream
} }
public static func ==(lhs: StoryItemsTableEntry, rhs: StoryItemsTableEntry) -> Bool { public static func ==(lhs: StoryItemsTableEntry, rhs: StoryItemsTableEntry) -> Bool {
@ -34,6 +37,9 @@ public final class StoryItemsTableEntry: Equatable {
if lhs.isCloseFriends != rhs.isCloseFriends { if lhs.isCloseFriends != rhs.isCloseFriends {
return false return false
} }
if lhs.isLiveStream != rhs.isLiveStream {
return false
}
return true return true
} }
} }
@ -139,10 +145,11 @@ final class StoryItemsTable: Table {
return key.successor return key.successor
} }
public func getStats(peerId: PeerId, maxSeenId: Int32) -> (total: Int, unseen: Int, hasUnseenCloseFriends: Bool) { public func getStats(peerId: PeerId, maxSeenId: Int32) -> (total: Int, unseen: Int, hasUnseenCloseFriends: Bool, hasLiveItems: Bool) {
var total = 0 var total = 0
var unseen = 0 var unseen = 0
var hasUnseenCloseFriends = false var hasUnseenCloseFriends = false
var hasLiveItems = false
self.valueBox.range(self.table, start: self.lowerBound(peerId: peerId), end: self.upperBound(peerId: peerId), values: { key, value in self.valueBox.range(self.table, start: self.lowerBound(peerId: peerId), end: self.upperBound(peerId: peerId), values: { key, value in
let id = key.getInt32(8) let id = key.getInt32(8)
@ -152,6 +159,7 @@ final class StoryItemsTable: Table {
unseen += 1 unseen += 1
var isCloseFriends = false var isCloseFriends = false
var isLiveStream = false
let readBuffer = ReadBuffer(data: value.makeData()) let readBuffer = ReadBuffer(data: value.makeData())
var magic: UInt32 = 0 var magic: UInt32 = 0
readBuffer.read(&magic, offset: 0, length: 4) readBuffer.read(&magic, offset: 0, length: 4)
@ -168,6 +176,7 @@ final class StoryItemsTable: Table {
var flags: UInt8 = 0 var flags: UInt8 = 0
readBuffer.read(&flags, offset: 0, length: 1) readBuffer.read(&flags, offset: 0, length: 1)
isCloseFriends = (flags & (1 << 0)) != 0 isCloseFriends = (flags & (1 << 0)) != 0
isLiveStream = (flags & (1 << 1)) != 0
} }
} }
} else { } else {
@ -180,12 +189,15 @@ final class StoryItemsTable: Table {
if isCloseFriends { if isCloseFriends {
hasUnseenCloseFriends = true hasUnseenCloseFriends = true
} }
if isLiveStream {
hasLiveItems = true
}
} }
return true return true
}, limit: 10000) }, limit: 10000)
return (total, unseen, hasUnseenCloseFriends) return (total, unseen, hasUnseenCloseFriends, hasLiveItems)
} }
public func get(peerId: PeerId) -> [StoryItemsTableEntry] { public func get(peerId: PeerId) -> [StoryItemsTableEntry] {
@ -199,6 +211,7 @@ final class StoryItemsTable: Table {
let entry: CodableEntry let entry: CodableEntry
var expirationTimestamp: Int32? var expirationTimestamp: Int32?
var isCloseFriends = false var isCloseFriends = false
var isLiveStream = false
let readBuffer = ReadBuffer(data: value.makeData()) let readBuffer = ReadBuffer(data: value.makeData())
var magic: UInt32 = 0 var magic: UInt32 = 0
@ -234,6 +247,7 @@ final class StoryItemsTable: Table {
var flags: UInt8 = 0 var flags: UInt8 = 0
readBuffer.read(&flags, offset: 0, length: 1) readBuffer.read(&flags, offset: 0, length: 1)
isCloseFriends = (flags & (1 << 0)) != 0 isCloseFriends = (flags & (1 << 0)) != 0
isLiveStream = (flags & (1 << 1)) != 0
} }
} }
} else { } else {
@ -245,7 +259,7 @@ final class StoryItemsTable: Table {
entry = CodableEntry(data: value.makeData()) entry = CodableEntry(data: value.makeData())
} }
result.append(StoryItemsTableEntry(value: entry, id: id, expirationTimestamp: expirationTimestamp, isCloseFriends: isCloseFriends)) result.append(StoryItemsTableEntry(value: entry, id: id, expirationTimestamp: expirationTimestamp, isCloseFriends: isCloseFriends, isLiveStream: isLiveStream))
return true return true
}, limit: 10000) }, limit: 10000)
@ -386,6 +400,9 @@ final class StoryItemsTable: Table {
if entry.isCloseFriends { if entry.isCloseFriends {
flags |= (1 << 0) flags |= (1 << 0)
} }
if entry.isLiveStream {
flags |= (1 << 1)
}
buffer.write(&flags, length: 1) buffer.write(&flags, length: 1)
self.valueBox.set(self.table, key: self.key(Key(peerId: peerId, id: entry.id)), value: buffer.readBufferNoCopy()) self.valueBox.set(self.table, key: self.key(Key(peerId: peerId, id: entry.id)), value: buffer.readBufferNoCopy())

View file

@ -82,6 +82,7 @@ private final class AvatarComponent: Component {
AvatarStoryIndicatorComponent( AvatarStoryIndicatorComponent(
hasUnseen: true, hasUnseen: true,
hasUnseenCloseFriendsItems: false, hasUnseenCloseFriendsItems: false,
hasLiveItems: false,
colors: AvatarStoryIndicatorComponent.Colors(unseenColors: colors, unseenCloseFriendsColors: colors, seenColors: colors), colors: AvatarStoryIndicatorComponent.Colors(unseenColors: colors, unseenCloseFriendsColors: colors, seenColors: colors),
activeLineWidth: 3.0, activeLineWidth: 3.0,
inactiveLineWidth: 3.0, inactiveLineWidth: 3.0,

View file

@ -39,6 +39,14 @@ private func generateBubbleShadowImage(shadow: UIColor, diameter: CGFloat, shado
final class ReactionContextBackgroundNode: ASDisplayNode { final class ReactionContextBackgroundNode: ASDisplayNode {
struct GlassParams {
var isTinted: Bool
init(isTinted: Bool) {
self.isTinted = isTinted
}
}
private let largeCircleSize: CGFloat private let largeCircleSize: CGFloat
private let smallCircleSize: CGFloat private let smallCircleSize: CGFloat
@ -59,14 +67,16 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
private let smallCircleLayer: SimpleLayer private let smallCircleLayer: SimpleLayer
private let smallCircleShadowLayer: SimpleLayer private let smallCircleShadowLayer: SimpleLayer
private let glass: GlassParams?
private var theme: PresentationTheme? private var theme: PresentationTheme?
init(glass: Bool, largeCircleSize: CGFloat, smallCircleSize: CGFloat, maskNode: ASDisplayNode) { init(glass: GlassParams?, largeCircleSize: CGFloat, smallCircleSize: CGFloat, maskNode: ASDisplayNode) {
self.glass = glass
self.largeCircleSize = largeCircleSize self.largeCircleSize = largeCircleSize
self.smallCircleSize = smallCircleSize self.smallCircleSize = smallCircleSize
self.backgroundView = BlurredBackgroundView(color: nil, enableBlur: true) self.backgroundView = BlurredBackgroundView(color: nil, enableBlur: true)
if glass { if glass != nil {
self.glassBackgroundView = GlassBackgroundView() self.glassBackgroundView = GlassBackgroundView()
} else { } else {
self.glassBackgroundView = nil self.glassBackgroundView = nil
@ -237,7 +247,13 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
var glassBackgroundFrame = contentBounds.insetBy(dx: 10.0, dy: 10.0) var glassBackgroundFrame = contentBounds.insetBy(dx: 10.0, dy: 10.0)
glassBackgroundFrame.size.height -= 8.0 glassBackgroundFrame.size.height -= 8.0
transition.updateFrame(view: glassBackgroundView, frame: glassBackgroundFrame, beginWithCurrentState: true) transition.updateFrame(view: glassBackgroundView, frame: glassBackgroundFrame, beginWithCurrentState: true)
glassBackgroundView.update(size: glassBackgroundFrame.size, cornerRadius: 23.0, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0x25272e, alpha: 0.72)), transition: ComponentTransition(transition)) let glassTintColor: GlassBackgroundView.TintColor
if let glass = self.glass, glass.isTinted {
glassTintColor = .init(kind: .custom, color: UIColor(rgb: 0x25272e, alpha: 0.72))
} else {
glassTintColor = .init(kind: .panel, color: defaultDarkPresentationTheme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7))
}
glassBackgroundView.update(size: glassBackgroundFrame.size, cornerRadius: 23.0, isDark: true, tintColor: glassTintColor, transition: ComponentTransition(transition))
transition.updateFrame(view: self.backgroundTintView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentBounds.width, height: contentBounds.height)).insetBy(dx: -10.0, dy: -10.0)) transition.updateFrame(view: self.backgroundTintView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentBounds.width, height: contentBounds.height)).insetBy(dx: -10.0, dy: -10.0))
} else { } else {

View file

@ -489,7 +489,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
public enum Style { public enum Style {
case legacy case legacy
case glass case glass(isTinted: Bool)
} }
public init(context: AccountContext, animationCache: AnimationCache, presentationData: PresentationData, style: Style = .legacy, items: [ReactionContextItem], selectedItems: Set<AnyHashable>, title: String? = nil, reactionsLocked: Bool, alwaysAllowPremiumReactions: Bool, allPresetReactionsAreAvailable: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, requestUpdateOverlayWantsToBeBelowKeyboard: @escaping (ContainedViewLayoutTransition) -> Void) { public init(context: AccountContext, animationCache: AnimationCache, presentationData: PresentationData, style: Style = .legacy, items: [ReactionContextItem], selectedItems: Set<AnyHashable>, title: String? = nil, reactionsLocked: Bool, alwaysAllowPremiumReactions: Bool, allPresetReactionsAreAvailable: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, requestUpdateOverlayWantsToBeBelowKeyboard: @escaping (ContainedViewLayoutTransition) -> Void) {
@ -508,7 +508,11 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
(self.animationRenderer as? MultiAnimationRendererImpl)?.useYuvA = context.sharedContext.immediateExperimentalUISettings.compressedEmojiCache (self.animationRenderer as? MultiAnimationRendererImpl)?.useYuvA = context.sharedContext.immediateExperimentalUISettings.compressedEmojiCache
self.backgroundMaskNode = ASDisplayNode() self.backgroundMaskNode = ASDisplayNode()
self.backgroundNode = ReactionContextBackgroundNode(glass: style == .glass, largeCircleSize: largeCircleSize, smallCircleSize: smallCircleSize, maskNode: self.backgroundMaskNode) var backgroundGlassParams: ReactionContextBackgroundNode.GlassParams?
if case let .glass(isTinted) = style {
backgroundGlassParams = ReactionContextBackgroundNode.GlassParams(isTinted: isTinted)
}
self.backgroundNode = ReactionContextBackgroundNode(glass: backgroundGlassParams, largeCircleSize: largeCircleSize, smallCircleSize: smallCircleSize, maskNode: self.backgroundMaskNode)
self.leftBackgroundMaskNode = ASDisplayNode() self.leftBackgroundMaskNode = ASDisplayNode()
self.leftBackgroundMaskNode.backgroundColor = .black self.leftBackgroundMaskNode.backgroundColor = .black
self.rightBackgroundMaskNode = ASDisplayNode() self.rightBackgroundMaskNode = ASDisplayNode()

View file

@ -661,6 +661,7 @@ final class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
component: AnyComponent(AvatarStoryIndicatorComponent( component: AnyComponent(AvatarStoryIndicatorComponent(
hasUnseen: true, hasUnseen: true,
hasUnseenCloseFriendsItems: false, hasUnseenCloseFriendsItems: false,
hasLiveItems: false,
colors: AvatarStoryIndicatorComponent.Colors( colors: AvatarStoryIndicatorComponent.Colors(
unseenColors: item.presentationData.theme.chatList.storyUnseenColors.array, unseenColors: item.presentationData.theme.chatList.storyUnseenColors.array,
unseenCloseFriendsColors: item.presentationData.theme.chatList.storyUnseenPrivateColors.array, unseenCloseFriendsColors: item.presentationData.theme.chatList.storyUnseenPrivateColors.array,

View file

@ -70,6 +70,7 @@ final class StoryIconNode: ASDisplayNode {
component: AnyComponent(AvatarStoryIndicatorComponent( component: AnyComponent(AvatarStoryIndicatorComponent(
hasUnseen: true, hasUnseen: true,
hasUnseenCloseFriendsItems: false, hasUnseenCloseFriendsItems: false,
hasLiveItems: false,
colors: AvatarStoryIndicatorComponent.Colors( colors: AvatarStoryIndicatorComponent.Colors(
unseenColors: theme.chatList.storyUnseenColors.array, unseenColors: theme.chatList.storyUnseenColors.array,
unseenCloseFriendsColors: theme.chatList.storyUnseenPrivateColors.array, unseenCloseFriendsColors: theme.chatList.storyUnseenPrivateColors.array,

View file

@ -303,7 +303,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-29248689] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) } dict[-29248689] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) }
dict[1429932961] = { return Api.GroupCall.parse_groupCall($0) } dict[1429932961] = { return Api.GroupCall.parse_groupCall($0) }
dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) } dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) }
dict[-341428482] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) } dict[708691884] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) }
dict[1735736008] = { return Api.GroupCallParticipantVideo.parse_groupCallParticipantVideo($0) } dict[1735736008] = { return Api.GroupCallParticipantVideo.parse_groupCallParticipantVideo($0) }
dict[-592373577] = { return Api.GroupCallParticipantVideoSourceGroup.parse_groupCallParticipantVideoSourceGroup($0) } dict[-592373577] = { return Api.GroupCallParticipantVideoSourceGroup.parse_groupCallParticipantVideoSourceGroup($0) }
dict[-2132064081] = { return Api.GroupCallStreamChannel.parse_groupCallStreamChannel($0) } dict[-2132064081] = { return Api.GroupCallStreamChannel.parse_groupCallStreamChannel($0) }
@ -355,7 +355,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1968737087] = { return Api.InputClientProxy.parse_inputClientProxy($0) } dict[1968737087] = { return Api.InputClientProxy.parse_inputClientProxy($0) }
dict[-1562241884] = { return Api.InputCollectible.parse_inputCollectiblePhone($0) } dict[-1562241884] = { return Api.InputCollectible.parse_inputCollectiblePhone($0) }
dict[-476815191] = { return Api.InputCollectible.parse_inputCollectibleUsername($0) } dict[-476815191] = { return Api.InputCollectible.parse_inputCollectibleUsername($0) }
dict[-208488460] = { return Api.InputContact.parse_inputPhoneContact($0) } dict[1780335806] = { return Api.InputContact.parse_inputPhoneContact($0) }
dict[-55902537] = { return Api.InputDialogPeer.parse_inputDialogPeer($0) } dict[-55902537] = { return Api.InputDialogPeer.parse_inputDialogPeer($0) }
dict[1684014375] = { return Api.InputDialogPeer.parse_inputDialogPeerFolder($0) } dict[1684014375] = { return Api.InputDialogPeer.parse_inputDialogPeerFolder($0) }
dict[448771445] = { return Api.InputDocument.parse_inputDocument($0) } dict[448771445] = { return Api.InputDocument.parse_inputDocument($0) }
@ -562,7 +562,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) } dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) }
dict[1235637404] = { return Api.MediaArea.parse_mediaAreaWeather($0) } dict[1235637404] = { return Api.MediaArea.parse_mediaAreaWeather($0) }
dict[-808853502] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) } dict[-808853502] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) }
dict[-1743401272] = { return Api.Message.parse_message($0) } dict[-1188071729] = { return Api.Message.parse_message($0) }
dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) } dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) }
dict[2055212554] = { return Api.Message.parse_messageService($0) } dict[2055212554] = { return Api.Message.parse_messageService($0) }
dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) } dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) }
@ -664,6 +664,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1974226924] = { return Api.MessageMedia.parse_messageMediaToDo($0) } dict[-1974226924] = { return Api.MessageMedia.parse_messageMediaToDo($0) }
dict[-1618676578] = { return Api.MessageMedia.parse_messageMediaUnsupported($0) } dict[-1618676578] = { return Api.MessageMedia.parse_messageMediaUnsupported($0) }
dict[784356159] = { return Api.MessageMedia.parse_messageMediaVenue($0) } dict[784356159] = { return Api.MessageMedia.parse_messageMediaVenue($0) }
dict[1059290001] = { return Api.MessageMedia.parse_messageMediaVideoStream($0) }
dict[-571405253] = { return Api.MessageMedia.parse_messageMediaWebPage($0) } dict[-571405253] = { return Api.MessageMedia.parse_messageMediaWebPage($0) }
dict[-1938180548] = { return Api.MessagePeerReaction.parse_messagePeerReaction($0) } dict[-1938180548] = { return Api.MessagePeerReaction.parse_messagePeerReaction($0) }
dict[-1228133028] = { return Api.MessagePeerVote.parse_messagePeerVote($0) } dict[-1228133028] = { return Api.MessagePeerVote.parse_messagePeerVote($0) }
@ -1107,11 +1108,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-451831443] = { return Api.Update.parse_updateFavedStickers($0) } dict[-451831443] = { return Api.Update.parse_updateFavedStickers($0) }
dict[422972864] = { return Api.Update.parse_updateFolderPeers($0) } dict[422972864] = { return Api.Update.parse_updateFolderPeers($0) }
dict[-2027964103] = { return Api.Update.parse_updateGeoLiveViewed($0) } dict[-2027964103] = { return Api.Update.parse_updateGeoLiveViewed($0) }
dict[-1747565759] = { return Api.Update.parse_updateGroupCall($0) } dict[-1658710304] = { return Api.Update.parse_updateGroupCall($0) }
dict[-1535694705] = { return Api.Update.parse_updateGroupCallChainBlocks($0) } dict[-1535694705] = { return Api.Update.parse_updateGroupCallChainBlocks($0) }
dict[192428418] = { return Api.Update.parse_updateGroupCallConnection($0) } dict[192428418] = { return Api.Update.parse_updateGroupCallConnection($0) }
dict[-917002394] = { return Api.Update.parse_updateGroupCallEncryptedMessage($0) } dict[-917002394] = { return Api.Update.parse_updateGroupCallEncryptedMessage($0) }
dict[2026050784] = { return Api.Update.parse_updateGroupCallMessage($0) } dict[-964095818] = { return Api.Update.parse_updateGroupCallMessage($0) }
dict[-219423922] = { return Api.Update.parse_updateGroupCallParticipants($0) } dict[-219423922] = { return Api.Update.parse_updateGroupCallParticipants($0) }
dict[1763610706] = { return Api.Update.parse_updateInlineBotCallbackQuery($0) } dict[1763610706] = { return Api.Update.parse_updateInlineBotCallbackQuery($0) }
dict[1442983757] = { return Api.Update.parse_updateLangPack($0) } dict[1442983757] = { return Api.Update.parse_updateLangPack($0) }

View file

@ -60,15 +60,15 @@ public extension Api {
} }
public extension Api { public extension Api {
indirect enum Message: TypeConstructorDescription { indirect enum Message: TypeConstructorDescription {
case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?, reportDeliveryUntilDate: Int32?, paidMessageStars: Int64?, suggestedPost: Api.SuggestedPost?) case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?, reportDeliveryUntilDate: Int32?, paidMessageStars: Int64?, suggestedPost: Api.SuggestedPost?, scheduleRepeatPeriod: Int32?)
case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?) case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?)
case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, savedPeerId: Api.Peer?, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, reactions: Api.MessageReactions?, ttlPeriod: Int32?) case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, savedPeerId: Api.Peer?, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, reactions: Api.MessageReactions?, ttlPeriod: Int32?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars, let suggestedPost): case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars, let suggestedPost, let scheduleRepeatPeriod):
if boxed { if boxed {
buffer.appendInt32(-1743401272) buffer.appendInt32(-1188071729)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(flags2, buffer: buffer, boxed: false) serializeInt32(flags2, buffer: buffer, boxed: false)
@ -109,6 +109,7 @@ public extension Api {
if Int(flags2) & Int(1 << 5) != 0 {serializeInt32(reportDeliveryUntilDate!, buffer: buffer, boxed: false)} if Int(flags2) & Int(1 << 5) != 0 {serializeInt32(reportDeliveryUntilDate!, buffer: buffer, boxed: false)}
if Int(flags2) & Int(1 << 6) != 0 {serializeInt64(paidMessageStars!, buffer: buffer, boxed: false)} if Int(flags2) & Int(1 << 6) != 0 {serializeInt64(paidMessageStars!, buffer: buffer, boxed: false)}
if Int(flags2) & Int(1 << 7) != 0 {suggestedPost!.serialize(buffer, true)} if Int(flags2) & Int(1 << 7) != 0 {suggestedPost!.serialize(buffer, true)}
if Int(flags2) & Int(1 << 10) != 0 {serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false)}
break break
case .messageEmpty(let flags, let id, let peerId): case .messageEmpty(let flags, let id, let peerId):
if boxed { if boxed {
@ -138,8 +139,8 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars, let suggestedPost): case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars, let suggestedPost, let scheduleRepeatPeriod):
return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any), ("reportDeliveryUntilDate", reportDeliveryUntilDate as Any), ("paidMessageStars", paidMessageStars as Any), ("suggestedPost", suggestedPost as Any)]) return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any), ("reportDeliveryUntilDate", reportDeliveryUntilDate as Any), ("paidMessageStars", paidMessageStars as Any), ("suggestedPost", suggestedPost as Any), ("scheduleRepeatPeriod", scheduleRepeatPeriod as Any)])
case .messageEmpty(let flags, let id, let peerId): case .messageEmpty(let flags, let id, let peerId):
return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)]) return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)])
case .messageService(let flags, let id, let fromId, let peerId, let savedPeerId, let replyTo, let date, let action, let reactions, let ttlPeriod): case .messageService(let flags, let id, let fromId, let peerId, let savedPeerId, let replyTo, let date, let action, let reactions, let ttlPeriod):
@ -236,6 +237,8 @@ public extension Api {
if Int(_2!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() { if Int(_2!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() {
_31 = Api.parse(reader, signature: signature) as? Api.SuggestedPost _31 = Api.parse(reader, signature: signature) as? Api.SuggestedPost
} } } }
var _32: Int32?
if Int(_2!) & Int(1 << 10) != 0 {_32 = reader.readInt32() }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
@ -267,8 +270,9 @@ public extension Api {
let _c29 = (Int(_2!) & Int(1 << 5) == 0) || _29 != nil let _c29 = (Int(_2!) & Int(1 << 5) == 0) || _29 != nil
let _c30 = (Int(_2!) & Int(1 << 6) == 0) || _30 != nil let _c30 = (Int(_2!) & Int(1 << 6) == 0) || _30 != nil
let _c31 = (Int(_2!) & Int(1 << 7) == 0) || _31 != nil let _c31 = (Int(_2!) & Int(1 << 7) == 0) || _31 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 { let _c32 = (Int(_2!) & Int(1 << 10) == 0) || _32 != nil
return Api.Message.message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, peerId: _6!, savedPeerId: _7, fwdFrom: _8, viaBotId: _9, viaBusinessBotId: _10, replyTo: _11, date: _12!, message: _13!, media: _14, replyMarkup: _15, entities: _16, views: _17, forwards: _18, replies: _19, editDate: _20, postAuthor: _21, groupedId: _22, reactions: _23, restrictionReason: _24, ttlPeriod: _25, quickReplyShortcutId: _26, effect: _27, factcheck: _28, reportDeliveryUntilDate: _29, paidMessageStars: _30, suggestedPost: _31) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 {
return Api.Message.message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, peerId: _6!, savedPeerId: _7, fwdFrom: _8, viaBotId: _9, viaBusinessBotId: _10, replyTo: _11, date: _12!, message: _13!, media: _14, replyMarkup: _15, entities: _16, views: _17, forwards: _18, replies: _19, editDate: _20, postAuthor: _21, groupedId: _22, reactions: _23, restrictionReason: _24, ttlPeriod: _25, quickReplyShortcutId: _26, effect: _27, factcheck: _28, reportDeliveryUntilDate: _29, paidMessageStars: _30, suggestedPost: _31, scheduleRepeatPeriod: _32)
} }
else { else {
return nil return nil

View file

@ -725,6 +725,7 @@ public extension Api {
case messageMediaToDo(flags: Int32, todo: Api.TodoList, completions: [Api.TodoCompletion]?) case messageMediaToDo(flags: Int32, todo: Api.TodoList, completions: [Api.TodoCompletion]?)
case messageMediaUnsupported case messageMediaUnsupported
case messageMediaVenue(geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String) case messageMediaVenue(geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String)
case messageMediaVideoStream(call: Api.InputGroupCall)
case messageMediaWebPage(flags: Int32, webpage: Api.WebPage) case messageMediaWebPage(flags: Int32, webpage: Api.WebPage)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
@ -908,6 +909,12 @@ public extension Api {
serializeString(venueId, buffer: buffer, boxed: false) serializeString(venueId, buffer: buffer, boxed: false)
serializeString(venueType, buffer: buffer, boxed: false) serializeString(venueType, buffer: buffer, boxed: false)
break break
case .messageMediaVideoStream(let call):
if boxed {
buffer.appendInt32(1059290001)
}
call.serialize(buffer, true)
break
case .messageMediaWebPage(let flags, let webpage): case .messageMediaWebPage(let flags, let webpage):
if boxed { if boxed {
buffer.appendInt32(-571405253) buffer.appendInt32(-571405253)
@ -954,6 +961,8 @@ public extension Api {
return ("messageMediaUnsupported", []) return ("messageMediaUnsupported", [])
case .messageMediaVenue(let geo, let title, let address, let provider, let venueId, let venueType): case .messageMediaVenue(let geo, let title, let address, let provider, let venueId, let venueType):
return ("messageMediaVenue", [("geo", geo as Any), ("title", title as Any), ("address", address as Any), ("provider", provider as Any), ("venueId", venueId as Any), ("venueType", venueType as Any)]) return ("messageMediaVenue", [("geo", geo as Any), ("title", title as Any), ("address", address as Any), ("provider", provider as Any), ("venueId", venueId as Any), ("venueType", venueType as Any)])
case .messageMediaVideoStream(let call):
return ("messageMediaVideoStream", [("call", call as Any)])
case .messageMediaWebPage(let flags, let webpage): case .messageMediaWebPage(let flags, let webpage):
return ("messageMediaWebPage", [("flags", flags as Any), ("webpage", webpage as Any)]) return ("messageMediaWebPage", [("flags", flags as Any), ("webpage", webpage as Any)])
} }
@ -1329,6 +1338,19 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_messageMediaVideoStream(_ reader: BufferReader) -> MessageMedia? {
var _1: Api.InputGroupCall?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
}
let _c1 = _1 != nil
if _c1 {
return Api.MessageMedia.messageMediaVideoStream(call: _1!)
}
else {
return nil
}
}
public static func parse_messageMediaWebPage(_ reader: BufferReader) -> MessageMedia? { public static func parse_messageMediaWebPage(_ reader: BufferReader) -> MessageMedia? {
var _1: Int32? var _1: Int32?
_1 = reader.readInt32() _1 = reader.readInt32()

View file

@ -604,11 +604,11 @@ public extension Api {
case updateFavedStickers case updateFavedStickers
case updateFolderPeers(folderPeers: [Api.FolderPeer], pts: Int32, ptsCount: Int32) case updateFolderPeers(folderPeers: [Api.FolderPeer], pts: Int32, ptsCount: Int32)
case updateGeoLiveViewed(peer: Api.Peer, msgId: Int32) case updateGeoLiveViewed(peer: Api.Peer, msgId: Int32)
case updateGroupCall(flags: Int32, chatId: Int64?, call: Api.GroupCall) case updateGroupCall(flags: Int32, peer: Api.Peer?, call: Api.GroupCall)
case updateGroupCallChainBlocks(call: Api.InputGroupCall, subChainId: Int32, blocks: [Buffer], nextOffset: Int32) case updateGroupCallChainBlocks(call: Api.InputGroupCall, subChainId: Int32, blocks: [Buffer], nextOffset: Int32)
case updateGroupCallConnection(flags: Int32, params: Api.DataJSON) case updateGroupCallConnection(flags: Int32, params: Api.DataJSON)
case updateGroupCallEncryptedMessage(call: Api.InputGroupCall, fromId: Api.Peer, encryptedMessage: Buffer) case updateGroupCallEncryptedMessage(call: Api.InputGroupCall, fromId: Api.Peer, encryptedMessage: Buffer)
case updateGroupCallMessage(call: Api.InputGroupCall, fromId: Api.Peer, randomId: Int64, message: Api.TextWithEntities) case updateGroupCallMessage(flags: Int32, call: Api.InputGroupCall, fromId: Api.Peer, randomId: Int64, message: Api.TextWithEntities, paidMessageStars: Int64?)
case updateGroupCallParticipants(call: Api.InputGroupCall, participants: [Api.GroupCallParticipant], version: Int32) case updateGroupCallParticipants(call: Api.InputGroupCall, participants: [Api.GroupCallParticipant], version: Int32)
case updateInlineBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int64, msgId: Api.InputBotInlineMessageID, chatInstance: Int64, data: Buffer?, gameShortName: String?) case updateInlineBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int64, msgId: Api.InputBotInlineMessageID, chatInstance: Int64, data: Buffer?, gameShortName: String?)
case updateLangPack(difference: Api.LangPackDifference) case updateLangPack(difference: Api.LangPackDifference)
@ -1271,12 +1271,12 @@ public extension Api {
peer.serialize(buffer, true) peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false) serializeInt32(msgId, buffer: buffer, boxed: false)
break break
case .updateGroupCall(let flags, let chatId, let call): case .updateGroupCall(let flags, let peer, let call):
if boxed { if boxed {
buffer.appendInt32(-1747565759) buffer.appendInt32(-1658710304)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(chatId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {peer!.serialize(buffer, true)}
call.serialize(buffer, true) call.serialize(buffer, true)
break break
case .updateGroupCallChainBlocks(let call, let subChainId, let blocks, let nextOffset): case .updateGroupCallChainBlocks(let call, let subChainId, let blocks, let nextOffset):
@ -1307,14 +1307,16 @@ public extension Api {
fromId.serialize(buffer, true) fromId.serialize(buffer, true)
serializeBytes(encryptedMessage, buffer: buffer, boxed: false) serializeBytes(encryptedMessage, buffer: buffer, boxed: false)
break break
case .updateGroupCallMessage(let call, let fromId, let randomId, let message): case .updateGroupCallMessage(let flags, let call, let fromId, let randomId, let message, let paidMessageStars):
if boxed { if boxed {
buffer.appendInt32(2026050784) buffer.appendInt32(-964095818)
} }
serializeInt32(flags, buffer: buffer, boxed: false)
call.serialize(buffer, true) call.serialize(buffer, true)
fromId.serialize(buffer, true) fromId.serialize(buffer, true)
serializeInt64(randomId, buffer: buffer, boxed: false) serializeInt64(randomId, buffer: buffer, boxed: false)
message.serialize(buffer, true) message.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(paidMessageStars!, buffer: buffer, boxed: false)}
break break
case .updateGroupCallParticipants(let call, let participants, let version): case .updateGroupCallParticipants(let call, let participants, let version):
if boxed { if boxed {
@ -2110,16 +2112,16 @@ public extension Api {
return ("updateFolderPeers", [("folderPeers", folderPeers as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) return ("updateFolderPeers", [("folderPeers", folderPeers as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)])
case .updateGeoLiveViewed(let peer, let msgId): case .updateGeoLiveViewed(let peer, let msgId):
return ("updateGeoLiveViewed", [("peer", peer as Any), ("msgId", msgId as Any)]) return ("updateGeoLiveViewed", [("peer", peer as Any), ("msgId", msgId as Any)])
case .updateGroupCall(let flags, let chatId, let call): case .updateGroupCall(let flags, let peer, let call):
return ("updateGroupCall", [("flags", flags as Any), ("chatId", chatId as Any), ("call", call as Any)]) return ("updateGroupCall", [("flags", flags as Any), ("peer", peer as Any), ("call", call as Any)])
case .updateGroupCallChainBlocks(let call, let subChainId, let blocks, let nextOffset): case .updateGroupCallChainBlocks(let call, let subChainId, let blocks, let nextOffset):
return ("updateGroupCallChainBlocks", [("call", call as Any), ("subChainId", subChainId as Any), ("blocks", blocks as Any), ("nextOffset", nextOffset as Any)]) return ("updateGroupCallChainBlocks", [("call", call as Any), ("subChainId", subChainId as Any), ("blocks", blocks as Any), ("nextOffset", nextOffset as Any)])
case .updateGroupCallConnection(let flags, let params): case .updateGroupCallConnection(let flags, let params):
return ("updateGroupCallConnection", [("flags", flags as Any), ("params", params as Any)]) return ("updateGroupCallConnection", [("flags", flags as Any), ("params", params as Any)])
case .updateGroupCallEncryptedMessage(let call, let fromId, let encryptedMessage): case .updateGroupCallEncryptedMessage(let call, let fromId, let encryptedMessage):
return ("updateGroupCallEncryptedMessage", [("call", call as Any), ("fromId", fromId as Any), ("encryptedMessage", encryptedMessage as Any)]) return ("updateGroupCallEncryptedMessage", [("call", call as Any), ("fromId", fromId as Any), ("encryptedMessage", encryptedMessage as Any)])
case .updateGroupCallMessage(let call, let fromId, let randomId, let message): case .updateGroupCallMessage(let flags, let call, let fromId, let randomId, let message, let paidMessageStars):
return ("updateGroupCallMessage", [("call", call as Any), ("fromId", fromId as Any), ("randomId", randomId as Any), ("message", message as Any)]) return ("updateGroupCallMessage", [("flags", flags as Any), ("call", call as Any), ("fromId", fromId as Any), ("randomId", randomId as Any), ("message", message as Any), ("paidMessageStars", paidMessageStars as Any)])
case .updateGroupCallParticipants(let call, let participants, let version): case .updateGroupCallParticipants(let call, let participants, let version):
return ("updateGroupCallParticipants", [("call", call as Any), ("participants", participants as Any), ("version", version as Any)]) return ("updateGroupCallParticipants", [("call", call as Any), ("participants", participants as Any), ("version", version as Any)])
case .updateInlineBotCallbackQuery(let flags, let queryId, let userId, let msgId, let chatInstance, let data, let gameShortName): case .updateInlineBotCallbackQuery(let flags, let queryId, let userId, let msgId, let chatInstance, let data, let gameShortName):
@ -3513,17 +3515,19 @@ public extension Api {
public static func parse_updateGroupCall(_ reader: BufferReader) -> Update? { public static func parse_updateGroupCall(_ reader: BufferReader) -> Update? {
var _1: Int32? var _1: Int32?
_1 = reader.readInt32() _1 = reader.readInt32()
var _2: Int64? var _2: Api.Peer?
if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt64() } if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Peer
} }
var _3: Api.GroupCall? var _3: Api.GroupCall?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.GroupCall _3 = Api.parse(reader, signature: signature) as? Api.GroupCall
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
if _c1 && _c2 && _c3 { if _c1 && _c2 && _c3 {
return Api.Update.updateGroupCall(flags: _1!, chatId: _2, call: _3!) return Api.Update.updateGroupCall(flags: _1!, peer: _2, call: _3!)
} }
else { else {
return nil return nil
@ -3591,26 +3595,32 @@ public extension Api {
} }
} }
public static func parse_updateGroupCallMessage(_ reader: BufferReader) -> Update? { public static func parse_updateGroupCallMessage(_ reader: BufferReader) -> Update? {
var _1: Api.InputGroupCall? var _1: Int32?
_1 = reader.readInt32()
var _2: Api.InputGroupCall?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall _2 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
} }
var _2: Api.Peer? var _3: Api.Peer?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Peer _3 = Api.parse(reader, signature: signature) as? Api.Peer
} }
var _3: Int64? var _4: Int64?
_3 = reader.readInt64() _4 = reader.readInt64()
var _4: Api.TextWithEntities? var _5: Api.TextWithEntities?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.TextWithEntities _5 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
} }
var _6: Int64?
if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt64() }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
let _c4 = _4 != nil let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 { let _c5 = _5 != nil
return Api.Update.updateGroupCallMessage(call: _1!, fromId: _2!, randomId: _3!, message: _4!) let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.Update.updateGroupCallMessage(flags: _1!, call: _2!, fromId: _3!, randomId: _4!, message: _5!, paidMessageStars: _6)
} }
else { else {
return nil return nil

View file

@ -5656,9 +5656,9 @@ public extension Api.functions.messages {
} }
} }
public extension Api.functions.messages { public extension Api.functions.messages {
static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, quickReplyShortcutId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, quickReplyShortcutId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-539934715) buffer.appendInt32(1374175969)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true) peer.serialize(buffer, true)
serializeInt32(id, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false)
@ -5671,8 +5671,9 @@ public extension Api.functions.messages {
item.serialize(buffer, true) item.serialize(buffer, true)
}} }}
if Int(flags) & Int(1 << 15) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 15) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 18) != 0 {serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 17) != 0 {serializeInt32(quickReplyShortcutId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 17) != 0 {serializeInt32(quickReplyShortcutId!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("message", String(describing: message)), ("media", String(describing: media)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("quickReplyShortcutId", String(describing: quickReplyShortcutId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("message", String(describing: message)), ("media", String(describing: media)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("scheduleRepeatPeriod", String(describing: scheduleRepeatPeriod)), ("quickReplyShortcutId", String(describing: quickReplyShortcutId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Updates? var result: Api.Updates?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -5735,9 +5736,9 @@ public extension Api.functions.messages {
} }
} }
public extension Api.functions.messages { public extension Api.functions.messages {
static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, topMsgId: Int32?, replyTo: Api.InputReplyTo?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, videoTimestamp: Int32?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, topMsgId: Int32?, replyTo: Api.InputReplyTo?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, videoTimestamp: Int32?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-1752618806) buffer.appendInt32(1104419550)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
fromPeer.serialize(buffer, true) fromPeer.serialize(buffer, true)
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
@ -5754,12 +5755,13 @@ public extension Api.functions.messages {
if Int(flags) & Int(1 << 9) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 9) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 22) != 0 {replyTo!.serialize(buffer, true)} if Int(flags) & Int(1 << 22) != 0 {replyTo!.serialize(buffer, true)}
if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 24) != 0 {serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)}
if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)}
if Int(flags) & Int(1 << 20) != 0 {serializeInt32(videoTimestamp!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 20) != 0 {serializeInt32(videoTimestamp!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 21) != 0 {serializeInt64(allowPaidStars!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 21) != 0 {serializeInt64(allowPaidStars!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 23) != 0 {suggestedPost!.serialize(buffer, true)} if Int(flags) & Int(1 << 23) != 0 {suggestedPost!.serialize(buffer, true)}
return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", String(describing: flags)), ("fromPeer", String(describing: fromPeer)), ("id", String(describing: id)), ("randomId", String(describing: randomId)), ("toPeer", String(describing: toPeer)), ("topMsgId", String(describing: topMsgId)), ("replyTo", String(describing: replyTo)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("videoTimestamp", String(describing: videoTimestamp)), ("allowPaidStars", String(describing: allowPaidStars)), ("suggestedPost", String(describing: suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", String(describing: flags)), ("fromPeer", String(describing: fromPeer)), ("id", String(describing: id)), ("randomId", String(describing: randomId)), ("toPeer", String(describing: toPeer)), ("topMsgId", String(describing: topMsgId)), ("replyTo", String(describing: replyTo)), ("scheduleDate", String(describing: scheduleDate)), ("scheduleRepeatPeriod", String(describing: scheduleRepeatPeriod)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("videoTimestamp", String(describing: videoTimestamp)), ("allowPaidStars", String(describing: allowPaidStars)), ("suggestedPost", String(describing: suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Updates? var result: Api.Updates?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -8355,9 +8357,9 @@ public extension Api.functions.messages {
} }
} }
public extension Api.functions.messages { public extension Api.functions.messages {
static func sendMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func sendMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-1403659839) buffer.appendInt32(53536639)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true) peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)}
@ -8371,12 +8373,13 @@ public extension Api.functions.messages {
item.serialize(buffer, true) item.serialize(buffer, true)
}} }}
if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 24) != 0 {serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)}
if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)}
if Int(flags) & Int(1 << 18) != 0 {serializeInt64(effect!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 18) != 0 {serializeInt64(effect!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 21) != 0 {serializeInt64(allowPaidStars!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 21) != 0 {serializeInt64(allowPaidStars!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 22) != 0 {suggestedPost!.serialize(buffer, true)} if Int(flags) & Int(1 << 22) != 0 {suggestedPost!.serialize(buffer, true)}
return (FunctionDescription(name: "messages.sendMedia", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("media", String(describing: media)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("effect", String(describing: effect)), ("allowPaidStars", String(describing: allowPaidStars)), ("suggestedPost", String(describing: suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in return (FunctionDescription(name: "messages.sendMedia", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("media", String(describing: media)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("scheduleRepeatPeriod", String(describing: scheduleRepeatPeriod)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("effect", String(describing: effect)), ("allowPaidStars", String(describing: allowPaidStars)), ("suggestedPost", String(describing: suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Updates? var result: Api.Updates?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -8387,9 +8390,9 @@ public extension Api.functions.messages {
} }
} }
public extension Api.functions.messages { public extension Api.functions.messages {
static func sendMessage(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func sendMessage(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-33170278) buffer.appendInt32(1415369050)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true) peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)}
@ -8402,12 +8405,13 @@ public extension Api.functions.messages {
item.serialize(buffer, true) item.serialize(buffer, true)
}} }}
if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 24) != 0 {serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)}
if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)}
if Int(flags) & Int(1 << 18) != 0 {serializeInt64(effect!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 18) != 0 {serializeInt64(effect!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 21) != 0 {serializeInt64(allowPaidStars!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 21) != 0 {serializeInt64(allowPaidStars!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 22) != 0 {suggestedPost!.serialize(buffer, true)} if Int(flags) & Int(1 << 22) != 0 {suggestedPost!.serialize(buffer, true)}
return (FunctionDescription(name: "messages.sendMessage", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("effect", String(describing: effect)), ("allowPaidStars", String(describing: allowPaidStars)), ("suggestedPost", String(describing: suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in return (FunctionDescription(name: "messages.sendMessage", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("scheduleRepeatPeriod", String(describing: scheduleRepeatPeriod)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("effect", String(describing: effect)), ("allowPaidStars", String(describing: allowPaidStars)), ("suggestedPost", String(describing: suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Updates? var result: Api.Updates?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -10567,12 +10571,13 @@ public extension Api.functions.phone {
} }
} }
public extension Api.functions.phone { public extension Api.functions.phone {
static func getGroupCallStreamRtmpUrl(peer: Api.InputPeer, revoke: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.GroupCallStreamRtmpUrl>) { static func getGroupCallStreamRtmpUrl(flags: Int32, peer: Api.InputPeer, revoke: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.GroupCallStreamRtmpUrl>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-558650433) buffer.appendInt32(1525991226)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true) peer.serialize(buffer, true)
revoke.serialize(buffer, true) revoke.serialize(buffer, true)
return (FunctionDescription(name: "phone.getGroupCallStreamRtmpUrl", parameters: [("peer", String(describing: peer)), ("revoke", String(describing: revoke))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCallStreamRtmpUrl? in return (FunctionDescription(name: "phone.getGroupCallStreamRtmpUrl", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("revoke", String(describing: revoke))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCallStreamRtmpUrl? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.phone.GroupCallStreamRtmpUrl? var result: Api.phone.GroupCallStreamRtmpUrl?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -10829,13 +10834,15 @@ public extension Api.functions.phone {
} }
} }
public extension Api.functions.phone { public extension Api.functions.phone {
static func sendGroupCallMessage(call: Api.InputGroupCall, randomId: Int64, message: Api.TextWithEntities) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { static func sendGroupCallMessage(flags: Int32, call: Api.InputGroupCall, randomId: Int64, message: Api.TextWithEntities, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-2021052396) buffer.appendInt32(2124127245)
serializeInt32(flags, buffer: buffer, boxed: false)
call.serialize(buffer, true) call.serialize(buffer, true)
serializeInt64(randomId, buffer: buffer, boxed: false) serializeInt64(randomId, buffer: buffer, boxed: false)
message.serialize(buffer, true) message.serialize(buffer, true)
return (FunctionDescription(name: "phone.sendGroupCallMessage", parameters: [("call", String(describing: call)), ("randomId", String(describing: randomId)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in if Int(flags) & Int(1 << 0) != 0 {serializeInt64(allowPaidStars!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "phone.sendGroupCallMessage", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("randomId", String(describing: randomId)), ("message", String(describing: message)), ("allowPaidStars", String(describing: allowPaidStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Bool? var result: Api.Bool?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -10913,14 +10920,15 @@ public extension Api.functions.phone {
} }
} }
public extension Api.functions.phone { public extension Api.functions.phone {
static func toggleGroupCallSettings(flags: Int32, call: Api.InputGroupCall, joinMuted: Api.Bool?, messagesEnabled: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func toggleGroupCallSettings(flags: Int32, call: Api.InputGroupCall, joinMuted: Api.Bool?, messagesEnabled: Api.Bool?, sendPaidMessagesStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-378390524) buffer.appendInt32(-1757179150)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
call.serialize(buffer, true) call.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {joinMuted!.serialize(buffer, true)} if Int(flags) & Int(1 << 0) != 0 {joinMuted!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {messagesEnabled!.serialize(buffer, true)} if Int(flags) & Int(1 << 2) != 0 {messagesEnabled!.serialize(buffer, true)}
return (FunctionDescription(name: "phone.toggleGroupCallSettings", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("joinMuted", String(describing: joinMuted)), ("messagesEnabled", String(describing: messagesEnabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in if Int(flags) & Int(1 << 3) != 0 {serializeInt64(sendPaidMessagesStars!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "phone.toggleGroupCallSettings", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("joinMuted", String(describing: joinMuted)), ("messagesEnabled", String(describing: messagesEnabled)), ("sendPaidMessagesStars", String(describing: sendPaidMessagesStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Updates? var result: Api.Updates?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -12060,6 +12068,34 @@ public extension Api.functions.stories {
}) })
} }
} }
public extension Api.functions.stories {
static func startLive(flags: Int32, peer: Api.InputPeer, caption: String?, entities: [Api.MessageEntity]?, privacyRules: [Api.InputPrivacyRule], randomId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-1294237155)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeString(caption!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(entities!.count))
for item in entities! {
item.serialize(buffer, true)
}}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(privacyRules.count))
for item in privacyRules {
item.serialize(buffer, true)
}
serializeInt64(randomId, buffer: buffer, boxed: false)
return (FunctionDescription(name: "stories.startLive", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("caption", String(describing: caption)), ("entities", String(describing: entities)), ("privacyRules", String(describing: privacyRules)), ("randomId", String(describing: randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.stories { public extension Api.functions.stories {
static func toggleAllStoriesHidden(hidden: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { static func toggleAllStoriesHidden(hidden: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()

View file

@ -1330,13 +1330,13 @@ public extension Api {
} }
public extension Api { public extension Api {
enum GroupCallParticipant: TypeConstructorDescription { enum GroupCallParticipant: TypeConstructorDescription {
case groupCallParticipant(flags: Int32, peer: Api.Peer, date: Int32, activeDate: Int32?, source: Int32, volume: Int32?, about: String?, raiseHandRating: Int64?, video: Api.GroupCallParticipantVideo?, presentation: Api.GroupCallParticipantVideo?) case groupCallParticipant(flags: Int32, peer: Api.Peer, date: Int32, activeDate: Int32?, source: Int32, volume: Int32?, about: String?, raiseHandRating: Int64?, video: Api.GroupCallParticipantVideo?, presentation: Api.GroupCallParticipantVideo?, paidStarsTotal: Int64?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .groupCallParticipant(let flags, let peer, let date, let activeDate, let source, let volume, let about, let raiseHandRating, let video, let presentation): case .groupCallParticipant(let flags, let peer, let date, let activeDate, let source, let volume, let about, let raiseHandRating, let video, let presentation, let paidStarsTotal):
if boxed { if boxed {
buffer.appendInt32(-341428482) buffer.appendInt32(708691884)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true) peer.serialize(buffer, true)
@ -1348,14 +1348,15 @@ public extension Api {
if Int(flags) & Int(1 << 13) != 0 {serializeInt64(raiseHandRating!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 13) != 0 {serializeInt64(raiseHandRating!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 6) != 0 {video!.serialize(buffer, true)} if Int(flags) & Int(1 << 6) != 0 {video!.serialize(buffer, true)}
if Int(flags) & Int(1 << 14) != 0 {presentation!.serialize(buffer, true)} if Int(flags) & Int(1 << 14) != 0 {presentation!.serialize(buffer, true)}
if Int(flags) & Int(1 << 16) != 0 {serializeInt64(paidStarsTotal!, buffer: buffer, boxed: false)}
break break
} }
} }
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .groupCallParticipant(let flags, let peer, let date, let activeDate, let source, let volume, let about, let raiseHandRating, let video, let presentation): case .groupCallParticipant(let flags, let peer, let date, let activeDate, let source, let volume, let about, let raiseHandRating, let video, let presentation, let paidStarsTotal):
return ("groupCallParticipant", [("flags", flags as Any), ("peer", peer as Any), ("date", date as Any), ("activeDate", activeDate as Any), ("source", source as Any), ("volume", volume as Any), ("about", about as Any), ("raiseHandRating", raiseHandRating as Any), ("video", video as Any), ("presentation", presentation as Any)]) return ("groupCallParticipant", [("flags", flags as Any), ("peer", peer as Any), ("date", date as Any), ("activeDate", activeDate as Any), ("source", source as Any), ("volume", volume as Any), ("about", about as Any), ("raiseHandRating", raiseHandRating as Any), ("video", video as Any), ("presentation", presentation as Any), ("paidStarsTotal", paidStarsTotal as Any)])
} }
} }
@ -1386,6 +1387,8 @@ public extension Api {
if Int(_1!) & Int(1 << 14) != 0 {if let signature = reader.readInt32() { if Int(_1!) & Int(1 << 14) != 0 {if let signature = reader.readInt32() {
_10 = Api.parse(reader, signature: signature) as? Api.GroupCallParticipantVideo _10 = Api.parse(reader, signature: signature) as? Api.GroupCallParticipantVideo
} } } }
var _11: Int64?
if Int(_1!) & Int(1 << 16) != 0 {_11 = reader.readInt64() }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
@ -1396,8 +1399,9 @@ public extension Api {
let _c8 = (Int(_1!) & Int(1 << 13) == 0) || _8 != nil let _c8 = (Int(_1!) & Int(1 << 13) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil
let _c10 = (Int(_1!) & Int(1 << 14) == 0) || _10 != nil let _c10 = (Int(_1!) & Int(1 << 14) == 0) || _10 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { let _c11 = (Int(_1!) & Int(1 << 16) == 0) || _11 != nil
return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, peer: _2!, date: _3!, activeDate: _4, source: _5!, volume: _6, about: _7, raiseHandRating: _8, video: _9, presentation: _10) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, peer: _2!, date: _3!, activeDate: _4, source: _5!, volume: _6, about: _7, raiseHandRating: _8, video: _9, presentation: _10, paidStarsTotal: _11)
} }
else { else {
return nil return nil

View file

@ -572,44 +572,54 @@ public extension Api {
} }
public extension Api { public extension Api {
enum InputContact: TypeConstructorDescription { enum InputContact: TypeConstructorDescription {
case inputPhoneContact(clientId: Int64, phone: String, firstName: String, lastName: String) case inputPhoneContact(flags: Int32, clientId: Int64, phone: String, firstName: String, lastName: String, note: Api.TextWithEntities?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .inputPhoneContact(let clientId, let phone, let firstName, let lastName): case .inputPhoneContact(let flags, let clientId, let phone, let firstName, let lastName, let note):
if boxed { if boxed {
buffer.appendInt32(-208488460) buffer.appendInt32(1780335806)
} }
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(clientId, buffer: buffer, boxed: false) serializeInt64(clientId, buffer: buffer, boxed: false)
serializeString(phone, buffer: buffer, boxed: false) serializeString(phone, buffer: buffer, boxed: false)
serializeString(firstName, buffer: buffer, boxed: false) serializeString(firstName, buffer: buffer, boxed: false)
serializeString(lastName, buffer: buffer, boxed: false) serializeString(lastName, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {note!.serialize(buffer, true)}
break break
} }
} }
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .inputPhoneContact(let clientId, let phone, let firstName, let lastName): case .inputPhoneContact(let flags, let clientId, let phone, let firstName, let lastName, let note):
return ("inputPhoneContact", [("clientId", clientId as Any), ("phone", phone as Any), ("firstName", firstName as Any), ("lastName", lastName as Any)]) return ("inputPhoneContact", [("flags", flags as Any), ("clientId", clientId as Any), ("phone", phone as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("note", note as Any)])
} }
} }
public static func parse_inputPhoneContact(_ reader: BufferReader) -> InputContact? { public static func parse_inputPhoneContact(_ reader: BufferReader) -> InputContact? {
var _1: Int64? var _1: Int32?
_1 = reader.readInt64() _1 = reader.readInt32()
var _2: String? var _2: Int64?
_2 = parseString(reader) _2 = reader.readInt64()
var _3: String? var _3: String?
_3 = parseString(reader) _3 = parseString(reader)
var _4: String? var _4: String?
_4 = parseString(reader) _4 = parseString(reader)
var _5: String?
_5 = parseString(reader)
var _6: Api.TextWithEntities?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
let _c4 = _4 != nil let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 { let _c5 = _5 != nil
return Api.InputContact.inputPhoneContact(clientId: _1!, phone: _2!, firstName: _3!, lastName: _4!) let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.InputContact.inputPhoneContact(flags: _1!, clientId: _2!, phone: _3!, firstName: _4!, lastName: _5!, note: _6)
} }
else { else {
return nil return nil

View file

@ -84,7 +84,6 @@ swift_library(
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode", "//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
"//submodules/AlertUI:AlertUI", "//submodules/AlertUI:AlertUI",
"//submodules/DirectionalPanGesture:DirectionalPanGesture", "//submodules/DirectionalPanGesture:DirectionalPanGesture",
"//submodules/PeerInfoUI:PeerInfoUI",
"//submodules/AnimatedCountLabelNode:AnimatedCountLabelNode", "//submodules/AnimatedCountLabelNode:AnimatedCountLabelNode",
"//submodules/DeviceProximity:DeviceProximity", "//submodules/DeviceProximity:DeviceProximity",
"//submodules/ManagedAnimationNode:ManagedAnimationNode", "//submodules/ManagedAnimationNode:ManagedAnimationNode",

View file

@ -367,6 +367,7 @@ public final class MediaStreamComponent: CombinedComponent {
isVisible: environment.isVisible && context.state.isVisibleInHierarchy, isVisible: environment.isVisible && context.state.isVisibleInHierarchy,
isAdmin: context.state.canManageCall, isAdmin: context.state.canManageCall,
peerTitle: context.state.peerTitle, peerTitle: context.state.peerTitle,
addInset: !isFullscreen,
isFullscreen: isFullscreen, isFullscreen: isFullscreen,
videoLoading: context.state.videoStalled, videoLoading: context.state.videoStalled,
callPeer: context.state.chatPeer, callPeer: context.state.chatPeer,
@ -595,7 +596,7 @@ public final class MediaStreamComponent: CombinedComponent {
} }
let credentialsPromise = Promise<GroupCallStreamCredentials>() let credentialsPromise = Promise<GroupCallStreamCredentials>()
credentialsPromise.set(call.accountContext.engine.calls.getGroupCallStreamCredentials(peerId: peerId, revokePreviousCredentials: false) |> `catch` { _ -> Signal<GroupCallStreamCredentials, NoError> in return .never() }) credentialsPromise.set(call.accountContext.engine.calls.getGroupCallStreamCredentials(peerId: peerId, isLiveStream: false, revokePreviousCredentials: false) |> `catch` { _ -> Signal<GroupCallStreamCredentials, NoError> in return .never() })
items.append(.action(ContextMenuActionItem(id: nil, text: presentationData.strings.LiveStream_ViewCredentials, textColor: .primary, textLayout: .singleLine, textFont: .regular, badge: nil, icon: { theme in items.append(.action(ContextMenuActionItem(id: nil, text: presentationData.strings.LiveStream_ViewCredentials, textColor: .primary, textLayout: .singleLine, textFont: .regular, badge: nil, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor, backgroundColor: nil) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor, backgroundColor: nil)

View file

@ -13,7 +13,7 @@ import Postbox
import TelegramVoip import TelegramVoip
import ComponentDisplayAdapters import ComponentDisplayAdapters
final class MediaStreamVideoComponent: Component { public final class MediaStreamVideoComponent: Component {
let call: PresentationGroupCallImpl let call: PresentationGroupCallImpl
let hasVideo: Bool let hasVideo: Bool
let isVisible: Bool let isVisible: Bool
@ -23,18 +23,20 @@ final class MediaStreamVideoComponent: Component {
let deactivatePictureInPicture: ActionSlot<Void> let deactivatePictureInPicture: ActionSlot<Void>
let bringBackControllerForPictureInPictureDeactivation: (@escaping () -> Void) -> Void let bringBackControllerForPictureInPictureDeactivation: (@escaping () -> Void) -> Void
let pictureInPictureClosed: () -> Void let pictureInPictureClosed: () -> Void
let addInset: Bool
let isFullscreen: Bool let isFullscreen: Bool
let onVideoSizeRetrieved: (CGSize) -> Void let onVideoSizeRetrieved: (CGSize) -> Void
let videoLoading: Bool let videoLoading: Bool
let callPeer: Peer? let callPeer: Peer?
let onVideoPlaybackLiveChange: (Bool) -> Void let onVideoPlaybackLiveChange: (Bool) -> Void
init( public init(
call: PresentationGroupCallImpl, call: PresentationGroupCallImpl,
hasVideo: Bool, hasVideo: Bool,
isVisible: Bool, isVisible: Bool,
isAdmin: Bool, isAdmin: Bool,
peerTitle: String, peerTitle: String,
addInset: Bool,
isFullscreen: Bool, isFullscreen: Bool,
videoLoading: Bool, videoLoading: Bool,
callPeer: Peer?, callPeer: Peer?,
@ -58,6 +60,7 @@ final class MediaStreamVideoComponent: Component {
self.onVideoPlaybackLiveChange = onVideoPlaybackLiveChange self.onVideoPlaybackLiveChange = onVideoPlaybackLiveChange
self.callPeer = callPeer self.callPeer = callPeer
self.addInset = addInset
self.isFullscreen = isFullscreen self.isFullscreen = isFullscreen
self.onVideoSizeRetrieved = onVideoSizeRetrieved self.onVideoSizeRetrieved = onVideoSizeRetrieved
} }
@ -78,6 +81,9 @@ final class MediaStreamVideoComponent: Component {
if lhs.peerTitle != rhs.peerTitle { if lhs.peerTitle != rhs.peerTitle {
return false return false
} }
if lhs.addInset != rhs.addInset {
return false
}
if lhs.isFullscreen != rhs.isFullscreen { if lhs.isFullscreen != rhs.isFullscreen {
return false return false
} }
@ -334,7 +340,7 @@ final class MediaStreamVideoComponent: Component {
} }
}) })
stallTimer = _stallTimer stallTimer = _stallTimer
self.clipsToBounds = component.isFullscreen // or just true self.clipsToBounds = true
if let videoView = self.videoRenderingContext.makeView(input: input, blur: false, forceSampleBufferDisplayLayer: true) { if let videoView = self.videoRenderingContext.makeView(input: input, blur: false, forceSampleBufferDisplayLayer: true) {
self.videoView = videoView self.videoView = videoView
@ -420,7 +426,7 @@ final class MediaStreamVideoComponent: Component {
} }
} }
if let videoView = self.videoView, let videoBlurView = self.videoRenderingContext.makeBlurView(input: input, mainView: videoView) { if component.addInset, let videoView = self.videoView, let videoBlurView = self.videoRenderingContext.makeBlurView(input: input, mainView: videoView) {
self.videoBlurView = videoBlurView self.videoBlurView = videoBlurView
self.insertSubview(videoBlurView, belowSubview: self.blurTintView) self.insertSubview(videoBlurView, belowSubview: self.blurTintView)
videoBlurView.alpha = 0 videoBlurView.alpha = 0
@ -453,14 +459,14 @@ final class MediaStreamVideoComponent: Component {
fullScreenBackgroundPlaceholder.frame = .init(origin: .zero, size: availableSize) fullScreenBackgroundPlaceholder.frame = .init(origin: .zero, size: availableSize)
let videoInset: CGFloat let videoInset: CGFloat
if !component.isFullscreen { if component.addInset && !component.isFullscreen {
videoInset = 16 videoInset = 16
} else { } else {
videoInset = 0 videoInset = 0
} }
let videoSize: CGSize let videoSize: CGSize
let videoCornerRadius: CGFloat = component.isFullscreen ? 0 : 10 let videoCornerRadius: CGFloat = (component.isFullscreen || !component.addInset) ? 0 : 10
let videoFrameUpdateTransition: ComponentTransition let videoFrameUpdateTransition: ComponentTransition
if self.wasFullscreen != component.isFullscreen { if self.wasFullscreen != component.isFullscreen {
@ -478,6 +484,7 @@ final class MediaStreamVideoComponent: Component {
} }
var aspect = videoView.getAspect() var aspect = videoView.getAspect()
if component.isFullscreen && self.hadVideo { if component.isFullscreen && self.hadVideo {
if aspect <= 0.01 { if aspect <= 0.01 {
aspect = 16.0 / 9 aspect = 16.0 / 9
@ -485,8 +492,12 @@ final class MediaStreamVideoComponent: Component {
} else if !self.hadVideo { } else if !self.hadVideo {
aspect = 16.0 / 9 aspect = 16.0 / 9
} }
#if DEBUG
aspect = 9.0 / 16.0
#endif
if component.isFullscreen { if component.isFullscreen || !component.addInset {
videoSize = CGSize(width: aspect * 100.0, height: 100.0).aspectFitted(.init(width: availableSize.width - videoInset * 2, height: availableSize.height)) videoSize = CGSize(width: aspect * 100.0, height: 100.0).aspectFitted(.init(width: availableSize.width - videoInset * 2, height: availableSize.height))
} else { } else {
// Limiting by smallest side -- redundant if passing precalculated availableSize // Limiting by smallest side -- redundant if passing precalculated availableSize
@ -629,17 +640,15 @@ final class MediaStreamVideoComponent: Component {
if !self.hadVideo && !component.call.accountContext.sharedContext.immediateExperimentalUISettings.liveStreamV2 { if !self.hadVideo && !component.call.accountContext.sharedContext.immediateExperimentalUISettings.liveStreamV2 {
if self.noSignalTimer == nil { if self.noSignalTimer == nil {
if #available(iOS 10.0, *) { let noSignalTimer = Timer(timeInterval: 20.0, repeats: false, block: { [weak self] _ in
let noSignalTimer = Timer(timeInterval: 20.0, repeats: false, block: { [weak self] _ in guard let strongSelf = self else {
guard let strongSelf = self else { return
return }
} strongSelf.noSignalTimeout = true
strongSelf.noSignalTimeout = true strongSelf.state?.updated(transition: .immediate)
strongSelf.state?.updated(transition: .immediate) })
}) self.noSignalTimer = noSignalTimer
self.noSignalTimer = noSignalTimer RunLoop.main.add(noSignalTimer, forMode: .common)
RunLoop.main.add(noSignalTimer, forMode: .common)
}
} }
if self.noSignalTimeout, !"".isEmpty { if self.noSignalTimeout, !"".isEmpty {
@ -696,7 +705,7 @@ final class MediaStreamVideoComponent: Component {
return availableSize return availableSize
} }
func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
if let videoView = self.videoView, let presentation = videoView.snapshotView(afterScreenUpdates: false) { if let videoView = self.videoView, let presentation = videoView.snapshotView(afterScreenUpdates: false) {
let presentationParent = self.window ?? self let presentationParent = self.window ?? self
presentationParent.addSubview(presentation) presentationParent.addSubview(presentation)
@ -750,12 +759,12 @@ final class MediaStreamVideoComponent: Component {
} }
} }
func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { public func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
self.didRequestBringBack = false self.didRequestBringBack = false
self.state?.updated(transition: .immediate) self.state?.updated(transition: .immediate)
} }
func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { public func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
if self.requestedExpansion { if self.requestedExpansion {
self.requestedExpansion = false self.requestedExpansion = false
} else if !didRequestBringBack { } else if !didRequestBringBack {
@ -772,7 +781,7 @@ final class MediaStreamVideoComponent: Component {
} }
} }
func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { public func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
self.videoView?.alpha = 1 self.videoView?.alpha = 1
self.state?.updated(transition: .immediate) self.state?.updated(transition: .immediate)
} }
@ -810,94 +819,3 @@ private final class CustomIntensityVisualEffectView: UIVisualEffectView {
animator.stopAnimation(true) animator.stopAnimation(true)
} }
} }
private final class ProxyVideoView: UIView {
private let call: PresentationGroupCallImpl
private let id: Int64
private let player: AVPlayer
private let playerItem: AVPlayerItem
let playerLayer: AVPlayerLayer
private var contextDisposable: Disposable?
private var failureObserverId: AnyObject?
private var errorObserverId: AnyObject?
private var rateObserver: NSKeyValueObservation?
private var isActiveDisposable: Disposable?
init(context: AccountContext, call: PresentationGroupCallImpl) {
self.call = call
self.id = Int64.random(in: Int64.min ... Int64.max)
let assetUrl = "http://127.0.0.1:\(SharedHLSServer.shared.port)/\(call.internalId)/master.m3u8"
Logger.shared.log("MediaStreamVideoComponent", "Initializing HLS asset at \(assetUrl)")
#if DEBUG
print("Initializing HLS asset at \(assetUrl)")
#endif
let asset = AVURLAsset(url: URL(string: assetUrl)!, options: [:])
self.playerItem = AVPlayerItem(asset: asset)
self.player = AVPlayer(playerItem: self.playerItem)
self.player.allowsExternalPlayback = true
self.playerLayer = AVPlayerLayer(player: self.player)
super.init(frame: CGRect())
self.failureObserverId = NotificationCenter.default.addObserver(forName: AVPlayerItem.failedToPlayToEndTimeNotification, object: playerItem, queue: .main, using: { notification in
print("Player Error: \(notification.description)")
})
self.errorObserverId = NotificationCenter.default.addObserver(forName: AVPlayerItem.newErrorLogEntryNotification, object: playerItem, queue: .main, using: { notification in
print("Player Error: \(notification.description)")
})
self.rateObserver = self.player.observe(\.rate, changeHandler: { [weak self] _, change in
guard let self else {
return
}
print("Player rate: \(self.player.rate)")
})
self.layer.addSublayer(self.playerLayer)
self.isActiveDisposable = (context.sharedContext.applicationBindings.applicationIsActive
|> distinctUntilChanged
|> deliverOnMainQueue).start(next: { [weak self] isActive in
guard let self else {
return
}
if isActive {
self.playerLayer.player = self.player
if self.player.rate == 0.0 {
self.player.play()
}
} else {
self.playerLayer.player = nil
}
})
self.player.play()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.contextDisposable?.dispose()
if let failureObserverId = self.failureObserverId {
NotificationCenter.default.removeObserver(failureObserverId)
}
if let errorObserverId = self.errorObserverId {
NotificationCenter.default.removeObserver(errorObserverId)
}
if let rateObserver = self.rateObserver {
rateObserver.invalidate()
}
self.isActiveDisposable?.dispose()
}
func update(size: CGSize) {
self.playerLayer.frame = CGRect(origin: CGPoint(), size: size)
}
}

View file

@ -838,7 +838,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private var lastErrorAlertTimestamp: Double = 0.0 private var lastErrorAlertTimestamp: Double = 0.0
init( public init(
accountContext: AccountContext, accountContext: AccountContext,
audioSession: ManagedAudioSession, audioSession: ManagedAudioSession,
callKitIntegration: CallKitIntegration?, callKitIntegration: CallKitIntegration?,
@ -1356,7 +1356,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
muteState: self.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false), muteState: self.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
volume: nil, volume: nil,
about: about, about: about,
joinedVideo: self.temporaryJoinedVideo joinedVideo: self.temporaryJoinedVideo,
paidStarsTotal: nil
)) ))
participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) }) participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) })
} }
@ -1437,7 +1438,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
muteState: self.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false), muteState: self.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
volume: nil, volume: nil,
about: about, about: about,
joinedVideo: self.temporaryJoinedVideo joinedVideo: self.temporaryJoinedVideo,
paidStarsTotal: nil
)) ))
} }
@ -1625,7 +1627,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
muteState: self.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: canManageCall || !state.defaultParticipantsAreMuted.isMuted, mutedByYou: false), muteState: self.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: canManageCall || !state.defaultParticipantsAreMuted.isMuted, mutedByYou: false),
volume: nil, volume: nil,
about: about, about: about,
joinedVideo: self.temporaryJoinedVideo joinedVideo: self.temporaryJoinedVideo,
paidStarsTotal: nil
)) ))
} }
@ -1743,7 +1746,30 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
shouldJoin = callInfo.scheduleTimestamp == nil shouldJoin = callInfo.scheduleTimestamp == nil
activeCallInfo = callInfo activeCallInfo = callInfo
} else { } else {
activeCallInfo = nil if case let .id(id, accessHash) = self.initialCall?.reference, case .requesting = internalState, self.isStream {
if self.genericCallContext == nil {
shouldJoin = true
}
activeCallInfo = GroupCallInfo(
id: id,
accessHash: accessHash,
participantCount: 0,
streamDcId: nil,
title: nil,
scheduleTimestamp: nil,
subscribedToScheduled: false,
recordingStartTimestamp: nil,
sortAscending: false,
defaultParticipantsAreMuted: nil,
messagesAreEnabled: nil,
isVideoEnabled: false,
unmutedVideoLimit: 0,
isStream: true,
isCreator: false
)
} else {
activeCallInfo = nil
}
} }
} }
if self.leaving { if self.leaving {
@ -2003,6 +2029,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
joinAs: self.joinAsPeerId, joinAs: self.joinAsPeerId,
callId: callInfo.id, callId: callInfo.id,
reference: reference, reference: reference,
isStream: self.isStream,
preferMuted: isEffectivelyMuted, preferMuted: isEffectivelyMuted,
joinPayload: joinPayload, joinPayload: joinPayload,
peerAdminIds: peerAdminIds, peerAdminIds: peerAdminIds,
@ -2460,7 +2487,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
muteState: self.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false), muteState: self.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
volume: nil, volume: nil,
about: about, about: about,
joinedVideo: self.temporaryJoinedVideo joinedVideo: self.temporaryJoinedVideo,
paidStarsTotal: nil
)) ))
participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) }) participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) })
} }
@ -3649,7 +3677,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
return return
} }
if let value = value { if let value {
var reference: InternalGroupCallReference = .id(id: value.id, accessHash: value.accessHash) var reference: InternalGroupCallReference = .id(id: value.id, accessHash: value.accessHash)
if let current = self.initialCall { if let current = self.initialCall {
switch current.reference { switch current.reference {

View file

@ -1263,7 +1263,8 @@ final class VideoChatScreenComponent: Component {
muteState: nil, muteState: nil,
volume: nil, volume: nil,
about: nil, about: nil,
joinedVideo: false joinedVideo: false,
paidStarsTotal: nil
)) ))
} }
if let remotePeer { if let remotePeer {
@ -1289,7 +1290,8 @@ final class VideoChatScreenComponent: Component {
muteState: nil, muteState: nil,
volume: nil, volume: nil,
about: nil, about: nil,
joinedVideo: false joinedVideo: false,
paidStarsTotal: nil
)) ))
} }
let members = PresentationGroupCallMembers( let members = PresentationGroupCallMembers(
@ -3712,7 +3714,7 @@ final class VideoChatScreenComponent: Component {
context: call.accountContext, context: call.accountContext,
animationCache: call.accountContext.animationCache, animationCache: call.accountContext.animationCache,
presentationData: call.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme), presentationData: call.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme),
style: .glass, style: .glass(isTinted: true),
items: reactionItems.map { ReactionContextItem.reaction(item: $0, icon: .none) }, items: reactionItems.map { ReactionContextItem.reaction(item: $0, icon: .none) },
selectedItems: Set(), selectedItems: Set(),
title: nil, title: nil,

View file

@ -3,12 +3,12 @@ import UIKit
import Display import Display
import TelegramCore import TelegramCore
import SwiftSignalKit import SwiftSignalKit
import PeerInfoUI
import OverlayStatusController import OverlayStatusController
import PresentationDataUtils import PresentationDataUtils
import InviteLinksUI import InviteLinksUI
import UndoUI import UndoUI
import TelegramPresentationData import TelegramPresentationData
import AccountContext
extension VideoChatScreenComponent.View { extension VideoChatScreenComponent.View {
func openInviteMembers() { func openInviteMembers() {
@ -113,7 +113,7 @@ extension VideoChatScreenComponent.View {
filters.append(.excludeBots) filters.append(.excludeBots)
var dismissController: (() -> Void)? var dismissController: (() -> Void)?
let controller = ChannelMembersSearchController(context: groupCall.accountContext, peerId: groupPeer.id, forceTheme: environment.theme, mode: .inviteToCall, filters: filters, openPeer: { [weak self] peer, participant in let controller = groupCall.accountContext.sharedContext.makeChannelMembersSearchController(params: ChannelMembersSearchControllerParams(context: groupCall.accountContext, peerId: groupPeer.id, forceTheme: environment.theme, mode: .inviteToCall, filters: filters, openPeer: { [weak self] peer, participant in
guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else {
dismissController?() dismissController?()
return return
@ -327,7 +327,7 @@ extension VideoChatScreenComponent.View {
})]), in: .window(.root)) })]), in: .window(.root))
} }
} }
}) }))
controller.copyInviteLink = { [weak self] in controller.copyInviteLink = { [weak self] in
dismissController?() dismissController?()

View file

@ -21,7 +21,6 @@ import UndoUI
import AlertUI import AlertUI
import PresentationDataUtils import PresentationDataUtils
import DirectionalPanGesture import DirectionalPanGesture
import PeerInfoUI
import AvatarNode import AvatarNode
import TooltipUI import TooltipUI
import LegacyUI import LegacyUI

View file

@ -10,7 +10,6 @@ import TelegramUIPreferences
import AccountContext import AccountContext
import AlertUI import AlertUI
import PresentationDataUtils import PresentationDataUtils
import PeerInfoUI
import ShareController import ShareController
import AvatarNode import AvatarNode
import UndoUI import UndoUI

View file

@ -238,6 +238,7 @@ private var declaredEncodables: Void = {
declareEncodable(TelegramMediaTodo.Completion.self, f: { TelegramMediaTodo.Completion(decoder: $0) }) declareEncodable(TelegramMediaTodo.Completion.self, f: { TelegramMediaTodo.Completion(decoder: $0) })
declareEncodable(SuggestedPostMessageAttribute.self, f: { SuggestedPostMessageAttribute(decoder: $0) }) declareEncodable(SuggestedPostMessageAttribute.self, f: { SuggestedPostMessageAttribute(decoder: $0) })
declareEncodable(PublishedSuggestedPostMessageAttribute.self, f: { PublishedSuggestedPostMessageAttribute(decoder: $0) }) declareEncodable(PublishedSuggestedPostMessageAttribute.self, f: { PublishedSuggestedPostMessageAttribute(decoder: $0) })
declareEncodable(TelegramMediaLiveStream.self, f: { TelegramMediaLiveStream(decoder: $0) })
return return
}() }()

View file

@ -128,7 +128,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
switch messsage { switch messsage {
case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
let chatPeerId = messagePeerId let chatPeerId = messagePeerId
return chatPeerId.peerId return chatPeerId.peerId
case let .messageEmpty(_, _, peerId): case let .messageEmpty(_, _, peerId):
@ -144,7 +144,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
switch message { switch message {
case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
let peerId: PeerId = chatPeerId.peerId let peerId: PeerId = chatPeerId.peerId
var result = [peerId] var result = [peerId]
@ -279,7 +279,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: ReferencedReplyMessageIds, generalIds: [MessageId])? { func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: ReferencedReplyMessageIds, generalIds: [MessageId])? {
switch message { switch message {
case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
if let replyTo = replyTo { if let replyTo = replyTo {
let peerId: PeerId = chatPeerId.peerId let peerId: PeerId = chatPeerId.peerId
@ -495,6 +495,10 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
return (TelegramMediaGiveawayResults(flags: flags, launchMessageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: launchMsgId), additionalChannelsCount: additionalPeersCount ?? 0, winnersPeerIds: winners.map { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }, winnersCount: winnersCount, unclaimedCount: unclaimedCount, prize: prize, untilDate: untilDate, prizeDescription: prizeDescription), nil, nil, nil, nil, nil) return (TelegramMediaGiveawayResults(flags: flags, launchMessageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: launchMsgId), additionalChannelsCount: additionalPeersCount ?? 0, winnersPeerIds: winners.map { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }, winnersCount: winnersCount, unclaimedCount: unclaimedCount, prize: prize, untilDate: untilDate, prizeDescription: prizeDescription), nil, nil, nil, nil, nil)
case let .messageMediaPaidMedia(starsAmount, apiExtendedMedia): case let .messageMediaPaidMedia(starsAmount, apiExtendedMedia):
return (TelegramMediaPaidContent(amount: starsAmount, extendedMedia: apiExtendedMedia.compactMap({ TelegramExtendedMedia(apiExtendedMedia: $0, peerId: peerId) })), nil, nil, nil, nil, nil) return (TelegramMediaPaidContent(amount: starsAmount, extendedMedia: apiExtendedMedia.compactMap({ TelegramExtendedMedia(apiExtendedMedia: $0, peerId: peerId) })), nil, nil, nil, nil, nil)
case let .messageMediaVideoStream(call):
if let call = GroupCallReference(call) {
return (TelegramMediaLiveStream(call: call), nil, nil, nil, nil, nil)
}
} }
} }
@ -693,7 +697,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes
extension StoreMessage { extension StoreMessage {
convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) {
switch apiMessage { switch apiMessage {
case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck, reportDeliveryUntilDate, paidMessageStars, suggestedPost): case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck, reportDeliveryUntilDate, paidMessageStars, suggestedPost, _):
var attributes: [MessageAttribute] = [] var attributes: [MessageAttribute] = []
if (flags2 & (1 << 4)) != 0 { if (flags2 & (1 << 4)) != 0 {

View file

@ -192,7 +192,7 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
flags |= Int32(1 << 17) flags |= Int32(1 << 17)
} }
return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: effectiveScheduleTime, quickReplyShortcutId: quickReplyShortcutId)) return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: effectiveScheduleTime, scheduleRepeatPeriod: nil, quickReplyShortcutId: quickReplyShortcutId))
|> map { result -> Api.Updates? in |> map { result -> Api.Updates? in
return result return result
} }
@ -340,7 +340,7 @@ func _internal_requestEditLiveLocation(postbox: Postbox, network: Network, state
inputMedia = .inputMediaGeoLive(flags: 1 << 0, geoPoint: .inputGeoPoint(flags: 0, lat: media.latitude, long: media.longitude, accuracyRadius: nil), heading: nil, period: nil, proximityNotificationRadius: nil) inputMedia = .inputMediaGeoLive(flags: 1 << 0, geoPoint: .inputGeoPoint(flags: 0, lat: media.latitude, long: media.longitude, accuracyRadius: nil), heading: nil, period: nil, proximityNotificationRadius: nil)
} }
return network.request(Api.functions.messages.editMessage(flags: 1 << 14, peer: inputPeer, id: messageId.id, message: nil, media: inputMedia, replyMarkup: nil, entities: nil, scheduleDate: nil, quickReplyShortcutId: nil)) return network.request(Api.functions.messages.editMessage(flags: 1 << 14, peer: inputPeer, id: messageId.id, message: nil, media: inputMedia, replyMarkup: nil, entities: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, quickReplyShortcutId: nil))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in |> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil) return .single(nil)

View file

@ -454,7 +454,7 @@ private func sendUploadedMessageContent(
flags |= 1 << 22 flags |= 1 << 22
} }
sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), info: .acknowledgement, tag: dependencyTag) sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), info: .acknowledgement, tag: dependencyTag)
case let .media(inputMedia, text): case let .media(inputMedia, text):
if bubbleUpEmojiOrStickersets { if bubbleUpEmojiOrStickersets {
flags |= Int32(1 << 15) flags |= Int32(1 << 15)
@ -486,7 +486,7 @@ private func sendUploadedMessageContent(
flags |= 1 << 22 flags |= 1 << 22
} }
sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag)
|> map(NetworkRequestResult.result) |> map(NetworkRequestResult.result)
case let .forward(sourceInfo): case let .forward(sourceInfo):
if topMsgId != nil { if topMsgId != nil {
@ -494,7 +494,7 @@ private func sendUploadedMessageContent(
} }
if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) { if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) {
sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: nil, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: nil), tag: dependencyTag) sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: nil, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: nil), tag: dependencyTag)
|> map(NetworkRequestResult.result) |> map(NetworkRequestResult.result)
} else { } else {
sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal")) sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal"))
@ -701,7 +701,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: nil, todoItemId: nil) replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: nil, todoItemId: nil)
} }
sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: nil)) sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: nil))
|> `catch` { _ -> Signal<Api.Updates, NoError> in |> `catch` { _ -> Signal<Api.Updates, NoError> in
return .complete() return .complete()
} }
@ -726,7 +726,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
flags |= 1 << 22 flags |= 1 << 22
} }
sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost)) sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost))
|> `catch` { _ -> Signal<Api.Updates, NoError> in |> `catch` { _ -> Signal<Api.Updates, NoError> in
return .complete() return .complete()
} }

View file

@ -1678,13 +1678,12 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
break break
} }
case let .updateGroupCall(_, channelId, call): case let .updateGroupCall(_, channelId, call):
updatedState.updateGroupCall(peerId: channelId.flatMap { PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value($0)) }, call: call) updatedState.updateGroupCall(peerId: channelId?.peerId, call: call)
updatedState.updateGroupCall(peerId: channelId.flatMap { PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value($0)) }, call: call)
case let .updateGroupCallChainBlocks(call, subChainId, blocks, nextOffset): case let .updateGroupCallChainBlocks(call, subChainId, blocks, nextOffset):
if case let .inputGroupCall(id, accessHash) = call { if case let .inputGroupCall(id, accessHash) = call {
updatedState.updateGroupCallChainBlocks(id: id, accessHash: accessHash, subChainId: subChainId, blocks: blocks.map { $0.makeData() }, nextOffset: nextOffset) updatedState.updateGroupCallChainBlocks(id: id, accessHash: accessHash, subChainId: subChainId, blocks: blocks.map { $0.makeData() }, nextOffset: nextOffset)
} }
case let .updateGroupCallMessage(call, fromId, randomId, message): case let .updateGroupCallMessage(_, call, fromId, randomId, message, _):
if case let .inputGroupCall(id, _) = call { if case let .inputGroupCall(id, _) = call {
updatedState.updateGroupCallMessage(id: id, authorId: fromId.peerId, randomId: randomId, text: message) updatedState.updateGroupCallMessage(id: id, authorId: fromId.peerId, randomId: randomId, text: message)
} }
@ -5156,12 +5155,12 @@ func replayFinalState(
if let currentIndex = updatedPeerEntries.firstIndex(where: { $0.id == storedItem.id }) { if let currentIndex = updatedPeerEntries.firstIndex(where: { $0.id == storedItem.id }) {
if case .item = storedItem { if case .item = storedItem {
if let codedEntry = CodableEntry(storedItem) { if let codedEntry = CodableEntry(storedItem) {
updatedPeerEntries[currentIndex] = StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp, isCloseFriends: storedItem.isCloseFriends) updatedPeerEntries[currentIndex] = StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp, isCloseFriends: storedItem.isCloseFriends, isLiveStream: storedItem.isLiveStream)
} }
} }
} else { } else {
if let codedEntry = CodableEntry(storedItem) { if let codedEntry = CodableEntry(storedItem) {
updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp, isCloseFriends: storedItem.isCloseFriends)) updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp, isCloseFriends: storedItem.isCloseFriends, isLiveStream: storedItem.isLiveStream))
} }
} }
if case .item = storedItem { if case .item = storedItem {
@ -5246,7 +5245,7 @@ func replayFinalState(
folderIds: item.folderIds folderIds: item.folderIds
)) ))
if let entry = CodableEntry(updatedItem) { if let entry = CodableEntry(updatedItem) {
updatedPeerEntries[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: item.expirationTimestamp, isCloseFriends: item.isCloseFriends) updatedPeerEntries[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: item.expirationTimestamp, isCloseFriends: item.isCloseFriends, isLiveStream: item.isLiveStream)
} }
} }
} }

View file

@ -104,7 +104,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
var updatedTimestamp: Int32? var updatedTimestamp: Int32?
if let apiMessage = apiMessage { if let apiMessage = apiMessage {
switch apiMessage { switch apiMessage {
case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
updatedTimestamp = date updatedTimestamp = date
case .messageEmpty: case .messageEmpty:
break break
@ -400,7 +400,7 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage
} else if let message = messages.first, let apiMessage = result.messages.first { } else if let message = messages.first, let apiMessage = result.messages.first {
if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp {
namespace = Namespaces.Message.ScheduledCloud namespace = Namespaces.Message.ScheduledCloud
} else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 {
namespace = Namespaces.Message.ScheduledCloud namespace = Namespaces.Message.ScheduledCloud
} }
} }

View file

@ -53,7 +53,7 @@ public struct CallId: Equatable {
} }
} }
public struct GroupCallReference: Equatable { public struct GroupCallReference: Codable, Equatable {
public var id: Int64 public var id: Int64
public var accessHash: Int64 public var accessHash: Int64

View file

@ -326,7 +326,7 @@ private func pushDeviceContactData(accountPeerId: PeerId, postbox: Postbox, netw
batches = batches batches = batches
|> mapToSignal { intermediateResult -> Signal<PushDeviceContactsResult, NoError> in |> mapToSignal { intermediateResult -> Signal<PushDeviceContactsResult, NoError> in
return network.request(Api.functions.contacts.importContacts(contacts: zip(0 ..< batch.count, batch).map { index, item -> Api.InputContact in return network.request(Api.functions.contacts.importContacts(contacts: zip(0 ..< batch.count, batch).map { index, item -> Api.InputContact in
return .inputPhoneContact(clientId: Int64(index), phone: item.0.rawValue, firstName: item.1.firstName, lastName: item.1.lastName) return .inputPhoneContact(flags: 0, clientId: Int64(index), phone: item.0.rawValue, firstName: item.1.firstName, lastName: item.1.lastName, note: nil)
})) }))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.contacts.ImportedContacts?, NoError> in |> `catch` { _ -> Signal<Api.contacts.ImportedContacts?, NoError> in

View file

@ -1122,7 +1122,7 @@ public final class PendingMessageManager {
} else if let inputSourcePeerId = forwardPeerIds.first, let inputSourcePeer = transaction.getPeer(inputSourcePeerId).flatMap(apiInputPeer) { } else if let inputSourcePeerId = forwardPeerIds.first, let inputSourcePeer = transaction.getPeer(inputSourcePeerId).flatMap(apiInputPeer) {
let dependencyTag = PendingMessageRequestDependencyTag(messageId: messages[0].0.id) let dependencyTag = PendingMessageRequestDependencyTag(messageId: messages[0].0.id)
sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: inputSourcePeer, id: forwardIds.map { $0.0.id }, randomId: forwardIds.map { $0.1 }, toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: inputSourcePeer, id: forwardIds.map { $0.0.id }, randomId: forwardIds.map { $0.1 }, toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag)
} else { } else {
assertionFailure() assertionFailure()
sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "Invalid forward source")) sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "Invalid forward source"))
@ -1671,7 +1671,7 @@ public final class PendingMessageManager {
flags |= 1 << 22 flags |= 1 << 22
} }
sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), info: .acknowledgement, tag: dependencyTag) sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), info: .acknowledgement, tag: dependencyTag)
case let .media(inputMedia, text): case let .media(inputMedia, text):
if bubbleUpEmojiOrStickersets { if bubbleUpEmojiOrStickersets {
flags |= Int32(1 << 15) flags |= Int32(1 << 15)
@ -1770,7 +1770,7 @@ public final class PendingMessageManager {
flags |= 1 << 22 flags |= 1 << 22
} }
sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag)
|> map(NetworkRequestResult.result) |> map(NetworkRequestResult.result)
case let .forward(sourceInfo): case let .forward(sourceInfo):
var topMsgId: Int32? var topMsgId: Int32?
@ -1815,7 +1815,7 @@ public final class PendingMessageManager {
} }
if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) { if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) {
sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag)
|> map(NetworkRequestResult.result) |> map(NetworkRequestResult.result)
} else { } else {
sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal")) sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal"))
@ -2060,7 +2060,7 @@ public final class PendingMessageManager {
if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp {
isScheduled = true isScheduled = true
} }
if case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage { if case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage {
if (flags2 & (1 << 4)) != 0 { if (flags2 & (1 << 4)) != 0 {
isScheduled = true isScheduled = true
} }
@ -2104,7 +2104,7 @@ public final class PendingMessageManager {
namespace = Namespaces.Message.QuickReplyCloud namespace = Namespaces.Message.QuickReplyCloud
} else if let apiMessage = result.messages.first, message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { } else if let apiMessage = result.messages.first, message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp {
namespace = Namespaces.Message.ScheduledCloud namespace = Namespaces.Message.ScheduledCloud
} else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 {
namespace = Namespaces.Message.ScheduledCloud namespace = Namespaces.Message.ScheduledCloud
} }
} }

View file

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization { public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt { public func currentLayer() -> UInt {
return 216 return 217
} }
public func parseMessage(_ data: Data!) -> Any! { public func parseMessage(_ data: Data!) -> Any! {

View file

@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService {
self.putNext(groups) self.putNext(groups)
} }
case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod): case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod):
let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil) let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil)
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
if groups.count != 0 { if groups.count != 0 {
@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService {
let generatedPeerId = Api.Peer.peerUser(userId: userId) let generatedPeerId = Api.Peer.peerUser(userId: userId)
let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil) let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil)
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
if groups.count != 0 { if groups.count != 0 {

View file

@ -104,7 +104,7 @@ extension Api.MessageMedia {
extension Api.Message { extension Api.Message {
var rawId: Int32 { var rawId: Int32 {
switch self { switch self {
case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return id return id
case let .messageEmpty(_, id, _): case let .messageEmpty(_, id, _):
return id return id
@ -115,7 +115,7 @@ extension Api.Message {
func id(namespace: MessageId.Namespace = Namespaces.Message.Cloud) -> MessageId? { func id(namespace: MessageId.Namespace = Namespaces.Message.Cloud) -> MessageId? {
switch self { switch self {
case let .message(_, flags2, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .message(_, flags2, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
var namespace = namespace var namespace = namespace
if (flags2 & (1 << 4)) != 0 { if (flags2 & (1 << 4)) != 0 {
namespace = Namespaces.Message.ScheduledCloud namespace = Namespaces.Message.ScheduledCloud
@ -136,7 +136,7 @@ extension Api.Message {
var peerId: PeerId? { var peerId: PeerId? {
switch self { switch self {
case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
let peerId: PeerId = messagePeerId.peerId let peerId: PeerId = messagePeerId.peerId
return peerId return peerId
case let .messageEmpty(_, _, peerId): case let .messageEmpty(_, _, peerId):
@ -149,7 +149,7 @@ extension Api.Message {
var timestamp: Int32? { var timestamp: Int32? {
switch self { switch self {
case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return date return date
case let .messageService(_, _, _, _, _, _, date, _, _, _): case let .messageService(_, _, _, _, _, _, date, _, _, _):
return date return date
@ -160,7 +160,7 @@ extension Api.Message {
var preCachedResources: [(MediaResource, Data)]? { var preCachedResources: [(MediaResource, Data)]? {
switch self { switch self {
case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return media?.preCachedResources return media?.preCachedResources
default: default:
return nil return nil
@ -169,7 +169,7 @@ extension Api.Message {
var preCachedStories: [StoryId: Api.StoryItem]? { var preCachedStories: [StoryId: Api.StoryItem]? {
switch self { switch self {
case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return media?.preCachedStories return media?.preCachedStories
default: default:
return nil return nil

View file

@ -0,0 +1,44 @@
import Foundation
import Postbox
public final class TelegramMediaLiveStream: Media, Equatable {
public let peerIds: [PeerId] = []
public var id: MediaId? {
return nil
}
public let call: GroupCallReference
public init(call: GroupCallReference) {
self.call = call
}
public init(decoder: PostboxDecoder) {
self.call = decoder.decodeCodable(GroupCallReference.self, forKey: "call")!
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeCodable(self.call, forKey: "call")
}
public static func ==(lhs: TelegramMediaLiveStream, rhs: TelegramMediaLiveStream) -> Bool {
return lhs.isEqual(to: rhs)
}
public func isEqual(to other: Media) -> Bool {
guard let other = other as? TelegramMediaLiveStream else {
return false
}
if self.call != other.call {
return false
}
return true
}
public func isSemanticallyEqual(to other: Media) -> Bool {
return self.isEqual(to: other)
}
}

View file

@ -621,7 +621,7 @@ public class JoinGroupCallE2E {
} }
} }
func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, callId: Int64, reference: InternalGroupCallReference, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil, generateE2E: ((Data?) -> JoinGroupCallE2E?)?) -> Signal<JoinGroupCallResult, JoinGroupCallError> { func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, callId: Int64, reference: InternalGroupCallReference, isStream: Bool, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil, generateE2E: ((Data?) -> JoinGroupCallE2E?)?) -> Signal<JoinGroupCallResult, JoinGroupCallError> {
enum InternalJoinError { enum InternalJoinError {
case error(JoinGroupCallError) case error(JoinGroupCallError)
case restart case restart
@ -719,9 +719,31 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
} }
} }
let getParticipantsRequest = _internal_getGroupCallParticipants(account: account, reference: reference, offset: "", ssrcs: [], limit: 100, sortAscending: true) let getParticipantsRequest: Signal<GroupCallParticipantsContext.State, InternalJoinError>
|> mapError { _ -> InternalJoinError in if isStream {
return .error(.generic) getParticipantsRequest = .single(GroupCallParticipantsContext.State(
participants: [],
nextParticipantsFetchOffset: nil,
adminIds: Set(),
isCreator: false,
defaultParticipantsAreMuted: .init(isMuted: true, canChange: false),
messagesAreEnabled: .init(isEnabled: true, canChange: false),
sortAscending: true,
recordingStartTimestamp: nil,
title: nil,
scheduleTimestamp: nil,
subscribedToScheduled: false,
totalCount: 0,
isVideoEnabled: false,
unmutedVideoLimit: 0,
isStream: true,
version: 0
))
} else {
getParticipantsRequest = _internal_getGroupCallParticipants(account: account, reference: reference, offset: "", ssrcs: [], limit: 100, sortAscending: true)
|> mapError { _ -> InternalJoinError in
return .error(.generic)
}
} }
return combineLatest( return combineLatest(
@ -835,7 +857,7 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
case let .updateGroupCallParticipants(_, participants, _): case let .updateGroupCallParticipants(_, participants, _):
loop: for participant in participants { loop: for participant in participants {
switch participant { switch participant {
case let .groupCallParticipant(flags, apiPeerId, date, activeDate, source, volume, about, raiseHandRating, video, presentation): case let .groupCallParticipant(flags, apiPeerId, date, activeDate, source, volume, about, raiseHandRating, video, presentation, paidStarsTotal):
let peerId: PeerId = apiPeerId.peerId let peerId: PeerId = apiPeerId.peerId
let ssrc = UInt32(bitPattern: source) let ssrc = UInt32(bitPattern: source)
guard let peer = transaction.getPeer(peerId) else { guard let peer = transaction.getPeer(peerId) else {
@ -872,7 +894,8 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
muteState: muteState, muteState: muteState,
volume: volume, volume: volume,
about: about, about: about,
joinedVideo: joinedVideo joinedVideo: joinedVideo,
paidStarsTotal: paidStarsTotal
)) ))
} }
} }
@ -1233,6 +1256,7 @@ public final class GroupCallParticipantsContext {
public var volume: Int32? public var volume: Int32?
public var about: String? public var about: String?
public var joinedVideo: Bool public var joinedVideo: Bool
public var paidStarsTotal: Int64?
public init( public init(
id: Id, id: Id,
@ -1248,7 +1272,8 @@ public final class GroupCallParticipantsContext {
muteState: MuteState?, muteState: MuteState?,
volume: Int32?, volume: Int32?,
about: String?, about: String?,
joinedVideo: Bool joinedVideo: Bool,
paidStarsTotal: Int64?
) { ) {
self.id = id self.id = id
self.peer = peer self.peer = peer
@ -1264,6 +1289,7 @@ public final class GroupCallParticipantsContext {
self.volume = volume self.volume = volume
self.about = about self.about = about
self.joinedVideo = joinedVideo self.joinedVideo = joinedVideo
self.paidStarsTotal = paidStarsTotal
} }
public var description: String { public var description: String {
@ -1320,6 +1346,9 @@ public final class GroupCallParticipantsContext {
if lhs.raiseHandRating != rhs.raiseHandRating { if lhs.raiseHandRating != rhs.raiseHandRating {
return false return false
} }
if lhs.paidStarsTotal != rhs.paidStarsTotal {
return false
}
return true return true
} }
@ -1547,6 +1576,7 @@ public final class GroupCallParticipantsContext {
public var volume: Int32? public var volume: Int32?
public var about: String? public var about: String?
public var joinedVideo: Bool public var joinedVideo: Bool
public var paidStarsTotal: Int64?
public var isMin: Bool public var isMin: Bool
init( init(
@ -1562,6 +1592,7 @@ public final class GroupCallParticipantsContext {
volume: Int32?, volume: Int32?,
about: String?, about: String?,
joinedVideo: Bool, joinedVideo: Bool,
paidStarsTotal: Int64?,
isMin: Bool isMin: Bool
) { ) {
self.peerId = peerId self.peerId = peerId
@ -1576,6 +1607,7 @@ public final class GroupCallParticipantsContext {
self.volume = volume self.volume = volume
self.about = about self.about = about
self.joinedVideo = joinedVideo self.joinedVideo = joinedVideo
self.paidStarsTotal = paidStarsTotal
self.isMin = isMin self.isMin = isMin
} }
} }
@ -1679,7 +1711,8 @@ public final class GroupCallParticipantsContext {
muteState: nil, muteState: nil,
volume: nil, volume: nil,
about: nil, about: nil,
joinedVideo: false joinedVideo: false,
paidStarsTotal: nil
)) ))
} }
} }
@ -2266,7 +2299,8 @@ public final class GroupCallParticipantsContext {
muteState: muteState, muteState: muteState,
volume: volume, volume: volume,
about: participantUpdate.about, about: participantUpdate.about,
joinedVideo: participantUpdate.joinedVideo joinedVideo: participantUpdate.joinedVideo,
paidStarsTotal: participantUpdate.paidStarsTotal
) )
updatedParticipants.append(participant) updatedParticipants.append(participant)
} }
@ -2579,7 +2613,7 @@ public final class GroupCallParticipantsContext {
} }
self.stateValue.state.defaultParticipantsAreMuted.isMuted = isMuted self.stateValue.state.defaultParticipantsAreMuted.isMuted = isMuted
self.updateDefaultMuteDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 0, call: self.reference.apiInputGroupCall, joinMuted: isMuted ? .boolTrue : .boolFalse, messagesEnabled: nil)) self.updateDefaultMuteDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 0, call: self.reference.apiInputGroupCall, joinMuted: isMuted ? .boolTrue : .boolFalse, messagesEnabled: nil, sendPaidMessagesStars: nil))
|> deliverOnMainQueue).start(next: { [weak self] updates in |> deliverOnMainQueue).start(next: { [weak self] updates in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -2594,7 +2628,7 @@ public final class GroupCallParticipantsContext {
} }
self.stateValue.state.messagesAreEnabled.isEnabled = isEnabled self.stateValue.state.messagesAreEnabled.isEnabled = isEnabled
self.updateMessagesEnabledDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 2, call: self.reference.apiInputGroupCall, joinMuted: nil, messagesEnabled: isEnabled ? .boolTrue : .boolFalse)) self.updateMessagesEnabledDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 2, call: self.reference.apiInputGroupCall, joinMuted: nil, messagesEnabled: isEnabled ? .boolTrue : .boolFalse, sendPaidMessagesStars: nil))
|> deliverOnMainQueue).start(next: { [weak self] updates in |> deliverOnMainQueue).start(next: { [weak self] updates in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -2604,7 +2638,7 @@ public final class GroupCallParticipantsContext {
} }
public func resetInviteLinks() { public func resetInviteLinks() {
self.resetInviteLinksDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 1, call: self.reference.apiInputGroupCall, joinMuted: nil, messagesEnabled: nil)) self.resetInviteLinksDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 1, call: self.reference.apiInputGroupCall, joinMuted: nil, messagesEnabled: nil, sendPaidMessagesStars: nil))
|> deliverOnMainQueue).start(next: { [weak self] updates in |> deliverOnMainQueue).start(next: { [weak self] updates in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -2662,7 +2696,7 @@ public final class GroupCallParticipantsContext {
extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate { extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate {
init(_ apiParticipant: Api.GroupCallParticipant) { init(_ apiParticipant: Api.GroupCallParticipant) {
switch apiParticipant { switch apiParticipant {
case let .groupCallParticipant(flags, apiPeerId, date, activeDate, source, volume, about, raiseHandRating, video, presentation): case let .groupCallParticipant(flags, apiPeerId, date, activeDate, source, volume, about, raiseHandRating, video, presentation, paidStarsTotal):
let peerId: PeerId = apiPeerId.peerId let peerId: PeerId = apiPeerId.peerId
let ssrc = UInt32(bitPattern: source) let ssrc = UInt32(bitPattern: source)
let muted = (flags & (1 << 0)) != 0 let muted = (flags & (1 << 0)) != 0
@ -2707,6 +2741,7 @@ extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate {
volume: volume, volume: volume,
about: about, about: about,
joinedVideo: joinedVideo, joinedVideo: joinedVideo,
paidStarsTotal: paidStarsTotal,
isMin: isMin isMin: isMin
) )
} }
@ -3135,7 +3170,7 @@ func _internal_getVideoBroadcastPart(dataSource: AudioBroadcastDataSource, callI
extension GroupCallParticipantsContext.Participant { extension GroupCallParticipantsContext.Participant {
init?(_ apiParticipant: Api.GroupCallParticipant, transaction: Transaction) { init?(_ apiParticipant: Api.GroupCallParticipant, transaction: Transaction) {
switch apiParticipant { switch apiParticipant {
case let .groupCallParticipant(flags, apiPeerId, date, activeDate, source, volume, about, raiseHandRating, video, presentation): case let .groupCallParticipant(flags, apiPeerId, date, activeDate, source, volume, about, raiseHandRating, video, presentation, paidStarsTotal):
let peerId: PeerId = apiPeerId.peerId let peerId: PeerId = apiPeerId.peerId
let ssrc = UInt32(bitPattern: source) let ssrc = UInt32(bitPattern: source)
guard let peer = transaction.getPeer(peerId) else { guard let peer = transaction.getPeer(peerId) else {
@ -3173,7 +3208,8 @@ extension GroupCallParticipantsContext.Participant {
muteState: muteState, muteState: muteState,
volume: volume, volume: volume,
about: about, about: about,
joinedVideo: joinedVideo joinedVideo: joinedVideo,
paidStarsTotal: paidStarsTotal
) )
} }
} }
@ -3205,7 +3241,7 @@ public enum GetGroupCallStreamCredentialsError {
case generic case generic
} }
func _internal_getGroupCallStreamCredentials(account: Account, peerId: PeerId, revokePreviousCredentials: Bool) -> Signal<GroupCallStreamCredentials, GetGroupCallStreamCredentialsError> { func _internal_getGroupCallStreamCredentials(account: Account, peerId: PeerId, isLiveStream: Bool, revokePreviousCredentials: Bool) -> Signal<GroupCallStreamCredentials, GetGroupCallStreamCredentialsError> {
return account.postbox.transaction { transaction -> Api.InputPeer? in return account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer) return transaction.getPeer(peerId).flatMap(apiInputPeer)
} }
@ -3215,7 +3251,11 @@ func _internal_getGroupCallStreamCredentials(account: Account, peerId: PeerId, r
return .fail(.generic) return .fail(.generic)
} }
return account.network.request(Api.functions.phone.getGroupCallStreamRtmpUrl(peer: inputPeer, revoke: revokePreviousCredentials ? .boolTrue : .boolFalse)) var flags: Int32 = 0
if isLiveStream {
flags |= 1 << 0
}
return account.network.request(Api.functions.phone.getGroupCallStreamRtmpUrl(flags: flags, peer: inputPeer, revoke: revokePreviousCredentials ? .boolTrue : .boolFalse))
|> mapError { _ -> GetGroupCallStreamCredentialsError in |> mapError { _ -> GetGroupCallStreamCredentialsError in
return .generic return .generic
} }
@ -3295,7 +3335,7 @@ public enum RevokeConferenceInviteLinkError {
} }
func _internal_revokeConferenceInviteLink(account: Account, reference: InternalGroupCallReference, link: String) -> Signal<GroupCallInviteLinks, RevokeConferenceInviteLinkError> { func _internal_revokeConferenceInviteLink(account: Account, reference: InternalGroupCallReference, link: String) -> Signal<GroupCallInviteLinks, RevokeConferenceInviteLinkError> {
return account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 1, call: reference.apiInputGroupCall, joinMuted: .boolFalse, messagesEnabled: nil)) return account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 1, call: reference.apiInputGroupCall, joinMuted: .boolFalse, messagesEnabled: nil, sendPaidMessagesStars: nil))
|> mapError { _ -> RevokeConferenceInviteLinkError in |> mapError { _ -> RevokeConferenceInviteLinkError in
return .generic return .generic
} }
@ -3882,12 +3922,14 @@ public final class GroupCallMessagesContext {
arc4random_buf(&randomId, 8) arc4random_buf(&randomId, 8)
} }
self.sendMessageDisposables.add(self.account.network.request(Api.functions.phone.sendGroupCallMessage( self.sendMessageDisposables.add(self.account.network.request(Api.functions.phone.sendGroupCallMessage(
flags: 0,
call: self.reference.apiInputGroupCall, call: self.reference.apiInputGroupCall,
randomId: randomId, randomId: randomId,
message: .textWithEntities( message: .textWithEntities(
text: text, text: text,
entities: apiEntitiesFromMessageTextEntities(entities, associatedPeers: SimpleDictionary()) entities: apiEntitiesFromMessageTextEntities(entities, associatedPeers: SimpleDictionary())
) ),
allowPaidStars: nil
)).startStrict()) )).startStrict())
} }
}) })

View file

@ -58,8 +58,8 @@ public extension TelegramEngine {
return _internal_getGroupCallParticipants(account: self.account, reference: reference, offset: offset, ssrcs: ssrcs, limit: limit, sortAscending: sortAscending) return _internal_getGroupCallParticipants(account: self.account, reference: reference, offset: offset, ssrcs: ssrcs, limit: limit, sortAscending: sortAscending)
} }
public func joinGroupCall(peerId: PeerId?, joinAs: PeerId?, callId: Int64, reference: InternalGroupCallReference, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil, generateE2E: ((Data?) -> JoinGroupCallE2E?)?) -> Signal<JoinGroupCallResult, JoinGroupCallError> { public func joinGroupCall(peerId: PeerId?, joinAs: PeerId?, callId: Int64, reference: InternalGroupCallReference, isStream: Bool, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil, generateE2E: ((Data?) -> JoinGroupCallE2E?)?) -> Signal<JoinGroupCallResult, JoinGroupCallError> {
return _internal_joinGroupCall(account: self.account, peerId: peerId, joinAs: joinAs, callId: callId, reference: reference, preferMuted: preferMuted, joinPayload: joinPayload, peerAdminIds: peerAdminIds, inviteHash: inviteHash, generateE2E: generateE2E) return _internal_joinGroupCall(account: self.account, peerId: peerId, joinAs: joinAs, callId: callId, reference: reference, isStream: isStream, preferMuted: preferMuted, joinPayload: joinPayload, peerAdminIds: peerAdminIds, inviteHash: inviteHash, generateE2E: generateE2E)
} }
public func joinGroupCallAsScreencast(callId: Int64, accessHash: Int64, joinPayload: String) -> Signal<JoinGroupCallAsScreencastResult, JoinGroupCallError> { public func joinGroupCallAsScreencast(callId: Int64, accessHash: Int64, joinPayload: String) -> Signal<JoinGroupCallAsScreencastResult, JoinGroupCallError> {
@ -176,8 +176,8 @@ public extension TelegramEngine {
} }
} }
public func getGroupCallStreamCredentials(peerId: EnginePeer.Id, revokePreviousCredentials: Bool) -> Signal<GroupCallStreamCredentials, GetGroupCallStreamCredentialsError> { public func getGroupCallStreamCredentials(peerId: EnginePeer.Id, isLiveStream: Bool, revokePreviousCredentials: Bool) -> Signal<GroupCallStreamCredentials, GetGroupCallStreamCredentialsError> {
return _internal_getGroupCallStreamCredentials(account: self.account, peerId: peerId, revokePreviousCredentials: revokePreviousCredentials) return _internal_getGroupCallStreamCredentials(account: self.account, peerId: peerId, isLiveStream: isLiveStream, revokePreviousCredentials: revokePreviousCredentials)
} }
public func getGroupCallPersistentSettings(callId: Int64) -> Signal<CodableEntry?, NoError> { public func getGroupCallPersistentSettings(callId: Int64) -> Signal<CodableEntry?, NoError> {

View file

@ -6,7 +6,7 @@ import SwiftSignalKit
func _internal_importContact(account: Account, firstName: String, lastName: String, phoneNumber: String) -> Signal<PeerId?, NoError> { func _internal_importContact(account: Account, firstName: String, lastName: String, phoneNumber: String) -> Signal<PeerId?, NoError> {
let accountPeerId = account.peerId let accountPeerId = account.peerId
let input = Api.InputContact.inputPhoneContact(clientId: 1, phone: phoneNumber, firstName: firstName, lastName: lastName) let input = Api.InputContact.inputPhoneContact(flags: 0, clientId: 1, phone: phoneNumber, firstName: firstName, lastName: lastName, note: nil)
return account.network.request(Api.functions.contacts.importContacts(contacts: [input])) return account.network.request(Api.functions.contacts.importContacts(contacts: [input]))
|> map(Optional.init) |> map(Optional.init)

View file

@ -645,7 +645,7 @@ public final class EngineStoryViewListContext {
folderIds: item.folderIds folderIds: item.folderIds
)) ))
if let entry = CodableEntry(updatedItem) { if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends) currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends, isLiveStream: updatedItem.isLiveStream)
} }
} }
} }

View file

@ -14,7 +14,7 @@ func _internal_forwardGameWithScore(account: Account, messageId: MessageId, to p
flags |= (1 << 13) flags |= (1 << 13)
} }
return account.network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: fromInputPeer, id: [messageId.id], randomId: [Int64.random(in: Int64.min ... Int64.max)], toPeer: toInputPeer, topMsgId: threadId.flatMap { Int32(clamping: $0) }, replyTo: nil, scheduleDate: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: nil, allowPaidStars: nil, suggestedPost: nil)) return account.network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: fromInputPeer, id: [messageId.id], randomId: [Int64.random(in: Int64.min ... Int64.max)], toPeer: toInputPeer, topMsgId: threadId.flatMap { Int32(clamping: $0) }, replyTo: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: nil, allowPaidStars: nil, suggestedPost: nil))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in |> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil) return .single(nil)

View file

@ -21,6 +21,7 @@ public enum EngineMedia: Equatable {
case giveawayResults(TelegramMediaGiveawayResults) case giveawayResults(TelegramMediaGiveawayResults)
case paidContent(TelegramMediaPaidContent) case paidContent(TelegramMediaPaidContent)
case todo(TelegramMediaTodo) case todo(TelegramMediaTodo)
case liveStream(TelegramMediaLiveStream)
} }
public extension EngineMedia { public extension EngineMedia {
@ -62,6 +63,8 @@ public extension EngineMedia {
return paidContent.id return paidContent.id
case .todo: case .todo:
return nil return nil
case .liveStream:
return nil
} }
} }
} }
@ -105,6 +108,8 @@ public extension EngineMedia {
self = .paidContent(paidContent) self = .paidContent(paidContent)
case let todo as TelegramMediaTodo: case let todo as TelegramMediaTodo:
self = .todo(todo) self = .todo(todo)
case let liveStream as TelegramMediaLiveStream:
self = .liveStream(liveStream)
default: default:
preconditionFailure() preconditionFailure()
} }
@ -148,6 +153,8 @@ public extension EngineMedia {
return paidContent return paidContent
case let .todo(todo): case let .todo(todo):
return todo return todo
case let .liveStream(liveStream):
return liveStream
} }
} }
} }

View file

@ -142,7 +142,7 @@ func _internal_requestClosePoll(postbox: Postbox, network: Network, stateManager
pollMediaFlags |= 1 << 1 pollMediaFlags |= 1 << 1
} }
return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: nil, media: .inputMediaPoll(flags: pollMediaFlags, poll: .poll(id: poll.pollId.id, flags: pollFlags, question: .textWithEntities(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary())), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil), correctAnswers: correctAnswers, solution: mappedSolution, solutionEntities: mappedSolutionEntities), replyMarkup: nil, entities: nil, scheduleDate: nil, quickReplyShortcutId: nil)) return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: nil, media: .inputMediaPoll(flags: pollMediaFlags, poll: .poll(id: poll.pollId.id, flags: pollFlags, question: .textWithEntities(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary())), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil), correctAnswers: correctAnswers, solution: mappedSolution, solutionEntities: mappedSolutionEntities), replyMarkup: nil, entities: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, quickReplyShortcutId: nil))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in |> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil) return .single(nil)

View file

@ -290,6 +290,10 @@ public enum Stories {
public let authorId: PeerId? public let authorId: PeerId?
public let folderIds: [Int64]? public let folderIds: [Int64]?
public var isLiveStream: Bool {
return self.media is TelegramMediaLiveStream
}
public init( public init(
id: Int32, id: Int32,
timestamp: Int32, timestamp: Int32,
@ -620,6 +624,15 @@ public enum Stories {
} }
} }
public var isLiveStream: Bool {
switch self {
case let .item(item):
return item.media is TelegramMediaLiveStream
case .placeholder:
return false
}
}
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
@ -759,6 +772,7 @@ public final class EngineStorySubscriptions: Equatable {
public let peer: EnginePeer public let peer: EnginePeer
public let hasUnseen: Bool public let hasUnseen: Bool
public let hasUnseenCloseFriends: Bool public let hasUnseenCloseFriends: Bool
public let hasLiveItems: Bool
public let hasPending: Bool public let hasPending: Bool
public let storyCount: Int public let storyCount: Int
public let unseenCount: Int public let unseenCount: Int
@ -768,6 +782,7 @@ public final class EngineStorySubscriptions: Equatable {
peer: EnginePeer, peer: EnginePeer,
hasUnseen: Bool, hasUnseen: Bool,
hasUnseenCloseFriends: Bool, hasUnseenCloseFriends: Bool,
hasLiveItems: Bool,
hasPending: Bool, hasPending: Bool,
storyCount: Int, storyCount: Int,
unseenCount: Int, unseenCount: Int,
@ -776,6 +791,7 @@ public final class EngineStorySubscriptions: Equatable {
self.peer = peer self.peer = peer
self.hasUnseen = hasUnseen self.hasUnseen = hasUnseen
self.hasUnseenCloseFriends = hasUnseenCloseFriends self.hasUnseenCloseFriends = hasUnseenCloseFriends
self.hasLiveItems = hasLiveItems
self.hasPending = hasPending self.hasPending = hasPending
self.storyCount = storyCount self.storyCount = storyCount
self.unseenCount = unseenCount self.unseenCount = unseenCount
@ -795,6 +811,9 @@ public final class EngineStorySubscriptions: Equatable {
if lhs.hasUnseenCloseFriends != rhs.hasUnseenCloseFriends { if lhs.hasUnseenCloseFriends != rhs.hasUnseenCloseFriends {
return false return false
} }
if lhs.hasLiveItems != rhs.hasLiveItems {
return false
}
if lhs.storyCount != rhs.storyCount { if lhs.storyCount != rhs.storyCount {
return false return false
} }
@ -1093,6 +1112,22 @@ func _internal_cancelStoryUpload(account: Account, stableId: Int32) {
}).start() }).start()
} }
func _internal_beginStoryLivestream(account: Account) -> Signal<Never, NoError> {
var flags: Int32 = 0
flags |= 1 << 5
return account.network.request(Api.functions.stories.startLive(flags: flags, peer: .inputPeerSelf, caption: nil, entities: nil, privacyRules: [.inputPrivacyValueAllowAll], randomId: Int64.random(in: Int64.min ... Int64.max)))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { updates -> Signal<Never, NoError> in
if let updates {
account.stateManager.addUpdates(updates)
}
return .complete()
}
}
private struct PendingStoryIdMappingKey: Hashable { private struct PendingStoryIdMappingKey: Hashable {
var peerId: PeerId var peerId: PeerId
var stableId: Int32 var stableId: Int32
@ -1307,7 +1342,7 @@ func _internal_uploadStoryImpl(
folderIds: item.folderIds folderIds: item.folderIds
) )
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items.append(StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)) items.append(StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends, isLiveStream: updatedItem.isLiveStream))
} }
updatedItems.append(updatedItem) updatedItems.append(updatedItem)
} }
@ -1749,7 +1784,7 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
folderIds: item.folderIds folderIds: item.folderIds
) )
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends) items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends, isLiveStream: updatedItem.isLiveStream)
} }
updatedItems.append(updatedItem) updatedItems.append(updatedItem)
@ -1946,7 +1981,7 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In
folderIds: item.folderIds folderIds: item.folderIds
) )
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) { if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends) items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends, isLiveStream: updatedItem.isLiveStream)
} }
updatedItems.append(updatedItem) updatedItems.append(updatedItem)
@ -2490,7 +2525,7 @@ func _internal_refreshStories(account: Account, peerId: PeerId, ids: [Int32]) ->
if let updatedItem = result.first(where: { $0.id == currentItems[i].id }) { if let updatedItem = result.first(where: { $0.id == currentItems[i].id }) {
if case .item = updatedItem { if case .item = updatedItem {
if let entry = CodableEntry(updatedItem) { if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends) currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends, isLiveStream: updatedItem.isLiveStream)
} }
} }
} }
@ -2772,7 +2807,7 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
)) ))
updatedItemValue = updatedItem updatedItemValue = updatedItem
if let entry = CodableEntry(updatedItem) { if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends) currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends, isLiveStream: updatedItem.isLiveStream)
} }
} }
} }

View file

@ -443,7 +443,7 @@ public final class StorySubscriptionsContext {
updatedPeerEntries.append(previousEntry) updatedPeerEntries.append(previousEntry)
} else { } else {
if let codedEntry = CodableEntry(storedItem) { if let codedEntry = CodableEntry(storedItem) {
updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp, isCloseFriends: storedItem.isCloseFriends)) updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp, isCloseFriends: storedItem.isCloseFriends, isLiveStream: storedItem.isLiveStream))
} }
} }
} }
@ -2608,7 +2608,7 @@ public final class PeerExpiringStoryListContext {
updatedPeerEntries.append(previousEntry) updatedPeerEntries.append(previousEntry)
} else { } else {
if let codedEntry = CodableEntry(storedItem) { if let codedEntry = CodableEntry(storedItem) {
updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp, isCloseFriends: storedItem.isCloseFriends)) updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp, isCloseFriends: storedItem.isCloseFriends, isLiveStream: storedItem.isLiveStream))
} }
} }
} }
@ -2699,6 +2699,20 @@ public final class PeerExpiringStoryListContext {
return self.items.contains(where: { $0.id > self.maxReadId && $0.isCloseFriends }) return self.items.contains(where: { $0.id > self.maxReadId && $0.isCloseFriends })
} }
public var hasLiveItems: Bool {
return self.items.contains(where: { item in
switch item {
case let .item(item):
if case .liveStream = item.media {
return true
}
default:
break
}
return false
})
}
public init(items: [Item], isCached: Bool, maxReadId: Int32, isLoading: Bool) { public init(items: [Item], isCached: Bool, maxReadId: Int32, isLoading: Bool) {
self.items = items self.items = items
self.isCached = isCached self.isCached = isCached
@ -2775,7 +2789,7 @@ public func _internal_pollPeerStories(postbox: Postbox, network: Network, accoun
updatedPeerEntries.append(previousEntry) updatedPeerEntries.append(previousEntry)
} else { } else {
if let codedEntry = CodableEntry(storedItem) { if let codedEntry = CodableEntry(storedItem) {
updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp, isCloseFriends: storedItem.isCloseFriends)) updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id, expirationTimestamp: storedItem.expirationTimestamp, isCloseFriends: storedItem.isCloseFriends, isLiveStream: storedItem.isLiveStream))
} }
} }
} }

View file

@ -999,6 +999,7 @@ public extension TelegramEngine {
peer: EnginePeer(accountPeer), peer: EnginePeer(accountPeer),
hasUnseen: false, hasUnseen: false,
hasUnseenCloseFriends: false, hasUnseenCloseFriends: false,
hasLiveItems: false,
hasPending: accountPendingItemCount != 0, hasPending: accountPendingItemCount != 0,
storyCount: accountPendingItemCount, storyCount: accountPendingItemCount,
unseenCount: 0, unseenCount: 0,
@ -1015,8 +1016,9 @@ public extension TelegramEngine {
let peerState: Stories.PeerState? = stateView.value?.get(Stories.PeerState.self) let peerState: Stories.PeerState? = stateView.value?.get(Stories.PeerState.self)
var hasUnseen = false var hasUnseen = false
var hasUnseenCloseFriends = false var hasUnseenCloseFriends = false
var hasLiveItems = false
var unseenCount = 0 var unseenCount = 0
if let peerState = peerState { if let peerState {
hasUnseen = peerState.maxReadId < lastEntry.id hasUnseen = peerState.maxReadId < lastEntry.id
for item in itemsView.items { for item in itemsView.items {
@ -1030,6 +1032,9 @@ public extension TelegramEngine {
hasUnseenCloseFriends = true hasUnseenCloseFriends = true
} }
} }
if item.media is TelegramMediaLiveStream {
hasLiveItems = true
}
} }
} }
} }
@ -1038,6 +1043,7 @@ public extension TelegramEngine {
peer: EnginePeer(accountPeer), peer: EnginePeer(accountPeer),
hasUnseen: hasUnseen, hasUnseen: hasUnseen,
hasUnseenCloseFriends: hasUnseenCloseFriends, hasUnseenCloseFriends: hasUnseenCloseFriends,
hasLiveItems: hasLiveItems,
hasPending: accountPendingItemCount != 0, hasPending: accountPendingItemCount != 0,
storyCount: itemsView.items.count + accountPendingItemCount, storyCount: itemsView.items.count + accountPendingItemCount,
unseenCount: unseenCount, unseenCount: unseenCount,
@ -1079,6 +1085,7 @@ public extension TelegramEngine {
let peerState: Stories.PeerState? = stateView.value?.get(Stories.PeerState.self) let peerState: Stories.PeerState? = stateView.value?.get(Stories.PeerState.self)
var hasUnseen = false var hasUnseen = false
var hasUnseenCloseFriends = false var hasUnseenCloseFriends = false
var hasLiveItems = false
var unseenCount = 0 var unseenCount = 0
if let peerState = peerState { if let peerState = peerState {
hasUnseen = peerState.maxReadId < lastEntry.id hasUnseen = peerState.maxReadId < lastEntry.id
@ -1091,6 +1098,9 @@ public extension TelegramEngine {
if item.isCloseFriends { if item.isCloseFriends {
hasUnseenCloseFriends = true hasUnseenCloseFriends = true
} }
if item.media is TelegramMediaLiveStream {
hasLiveItems = true
}
} }
} }
} }
@ -1116,6 +1126,7 @@ public extension TelegramEngine {
peer: EnginePeer(peer), peer: EnginePeer(peer),
hasUnseen: hasUnseen, hasUnseen: hasUnseen,
hasUnseenCloseFriends: hasUnseenCloseFriends, hasUnseenCloseFriends: hasUnseenCloseFriends,
hasLiveItems: hasLiveItems,
hasPending: maxPendingTimestamp != nil, hasPending: maxPendingTimestamp != nil,
storyCount: itemsView.items.count, storyCount: itemsView.items.count,
unseenCount: unseenCount, unseenCount: unseenCount,
@ -1153,6 +1164,7 @@ public extension TelegramEngine {
peer: EnginePeer(peer), peer: EnginePeer(peer),
hasUnseen: false, hasUnseen: false,
hasUnseenCloseFriends: false, hasUnseenCloseFriends: false,
hasLiveItems: false,
hasPending: true, hasPending: true,
storyCount: 0, storyCount: 0,
unseenCount: 0, unseenCount: 0,
@ -1387,7 +1399,7 @@ public extension TelegramEngine {
folderIds: item.folderIds folderIds: item.folderIds
)) ))
if let entry = CodableEntry(updatedItem) { if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends) currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends, isLiveStream: updatedItem.isLiveStream)
} }
} }
} }
@ -1402,6 +1414,10 @@ public extension TelegramEngine {
return _internal_uploadStory(account: self.account, target: target, media: media, mediaAreas: mediaAreas, text: text, entities: entities, pin: pin, privacy: privacy, isForwardingDisabled: isForwardingDisabled, period: period, randomId: randomId, forwardInfo: forwardInfo, folders: folders, uploadInfo: uploadInfo) return _internal_uploadStory(account: self.account, target: target, media: media, mediaAreas: mediaAreas, text: text, entities: entities, pin: pin, privacy: privacy, isForwardingDisabled: isForwardingDisabled, period: period, randomId: randomId, forwardInfo: forwardInfo, folders: folders, uploadInfo: uploadInfo)
} }
public func beginStoryLivestream() -> Signal<Never, NoError> {
return _internal_beginStoryLivestream(account: self.account)
}
public func allStoriesUploadEvents() -> Signal<(Int32, Int32), NoError> { public func allStoriesUploadEvents() -> Signal<(Int32, Int32), NoError> {
guard let pendingStoryManager = self.account.pendingStoryManager else { guard let pendingStoryManager = self.account.pendingStoryManager else {
return .complete() return .complete()

View file

@ -30,7 +30,7 @@ public final class ChatAvatarNavigationNode: ASDisplayNode {
private var avatarVideoNode: AvatarVideoNode? private var avatarVideoNode: AvatarVideoNode?
public private(set) var avatarStoryView: ComponentView<Empty>? public private(set) var avatarStoryView: ComponentView<Empty>?
public var storyData: (hasUnseen: Bool, hasUnseenCloseFriends: Bool)? public var storyData: (hasUnseen: Bool, hasUnseenCloseFriends: Bool, hasLiveItems: Bool)?
public var statusView: ComponentView<Empty> public var statusView: ComponentView<Empty>
private var starView: StarView? private var starView: StarView?
@ -241,6 +241,7 @@ public final class ChatAvatarNavigationNode: ASDisplayNode {
component: AnyComponent(AvatarStoryIndicatorComponent( component: AnyComponent(AvatarStoryIndicatorComponent(
hasUnseen: storyData.hasUnseen, hasUnseen: storyData.hasUnseen,
hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends, hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends,
hasLiveItems: storyData.hasLiveItems,
colors: AvatarStoryIndicatorComponent.Colors(theme: theme), colors: AvatarStoryIndicatorComponent.Colors(theme: theme),
activeLineWidth: 1.0, activeLineWidth: 1.0,
inactiveLineWidth: 1.0, inactiveLineWidth: 1.0,

View file

@ -144,7 +144,7 @@ public struct ChatMessageItemLayoutConstants {
let instantVideo = ChatMessageItemInstantVideoConstants(insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), dimensions: CGSize(width: 212.0, height: 212.0)) let instantVideo = ChatMessageItemInstantVideoConstants(insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), dimensions: CGSize(width: 212.0, height: 212.0))
let wallpapers = ChatMessageItemWallpaperLayoutConstants(maxTextWidth: 180.0) let wallpapers = ChatMessageItemWallpaperLayoutConstants(maxTextWidth: 180.0)
return ChatMessageItemLayoutConstants(avatarInset: 44.0, timestampHeaderHeight: 34.0, timestampDateAndTopicHeaderHeight: 7.0 * 2.0 + 20.0 * 2.0 + 7.0, bubble: bubble, image: image, video: video, text: text, file: file, instantVideo: instantVideo, wallpapers: wallpapers) return ChatMessageItemLayoutConstants(avatarInset: 34.0 + 4.0, timestampHeaderHeight: 34.0, timestampDateAndTopicHeaderHeight: 7.0 * 2.0 + 20.0 * 2.0 + 7.0, bubble: bubble, image: image, video: video, text: text, file: file, instantVideo: instantVideo, wallpapers: wallpapers)
} }
public static var regular: ChatMessageItemLayoutConstants { public static var regular: ChatMessageItemLayoutConstants {
@ -156,7 +156,7 @@ public struct ChatMessageItemLayoutConstants {
let instantVideo = ChatMessageItemInstantVideoConstants(insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), dimensions: CGSize(width: 240.0, height: 240.0)) let instantVideo = ChatMessageItemInstantVideoConstants(insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), dimensions: CGSize(width: 240.0, height: 240.0))
let wallpapers = ChatMessageItemWallpaperLayoutConstants(maxTextWidth: 180.0) let wallpapers = ChatMessageItemWallpaperLayoutConstants(maxTextWidth: 180.0)
return ChatMessageItemLayoutConstants(avatarInset: 44.0, timestampHeaderHeight: 34.0, timestampDateAndTopicHeaderHeight: 7.0 * 2.0 + 20.0 * 2.0 + 7.0, bubble: bubble, image: image, video: video, text: text, file: file, instantVideo: instantVideo, wallpapers: wallpapers) return ChatMessageItemLayoutConstants(avatarInset: 34.0 + 4.0, timestampHeaderHeight: 34.0, timestampDateAndTopicHeaderHeight: 7.0 * 2.0 + 20.0 * 2.0 + 7.0, bubble: bubble, image: image, video: video, text: text, file: file, instantVideo: instantVideo, wallpapers: wallpapers)
} }
} }

View file

@ -875,6 +875,10 @@ public final class ChatMessageDateHeaderNodeImpl: ListViewItemHeaderNode, ChatMe
} }
} }
private func avatarHeaderSize() -> CGFloat {
return 34.0
}
public final class ChatMessageAvatarHeader: ListViewItemHeader { public final class ChatMessageAvatarHeader: ListViewItemHeader {
public struct Id: Hashable { public struct Id: Hashable {
public var peerId: PeerId public var peerId: PeerId
@ -923,7 +927,7 @@ public final class ChatMessageAvatarHeader: ListViewItemHeader {
public let stickDirection: ListViewItemHeaderStickDirection public let stickDirection: ListViewItemHeaderStickDirection
public let stickOverInsets: Bool = false public let stickOverInsets: Bool = false
public let height: CGFloat = 40.0 public let height: CGFloat = avatarHeaderSize()
public func combinesWith(other: ListViewItemHeader) -> Bool { public func combinesWith(other: ListViewItemHeader) -> Bool {
if let other = other as? ChatMessageAvatarHeader, other.id == self.id { if let other = other as? ChatMessageAvatarHeader, other.id == self.id {
@ -1053,9 +1057,9 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat
if let previousPeer = self.peer, previousPeer.nameColor != peer.nameColor { if let previousPeer = self.peer, previousPeer.nameColor != peer.nameColor {
self.peer = peer self.peer = peer
if peer.smallProfileImage != nil { if peer.smallProfileImage != nil {
self.avatarNode.setPeerV2(context: self.context, theme: self.presentationData.theme.theme, peer: EnginePeer(peer), authorOfMessage: self.messageReference, overrideImage: nil, emptyColor: .black, synchronousLoad: false, displayDimensions: CGSize(width: 40.0, height: 40.0)) self.avatarNode.setPeerV2(context: self.context, theme: self.presentationData.theme.theme, peer: EnginePeer(peer), authorOfMessage: self.messageReference, overrideImage: nil, emptyColor: .black, synchronousLoad: false, displayDimensions: CGSize(width: avatarHeaderSize(), height: avatarHeaderSize()))
} else { } else {
self.avatarNode.setPeer(context: self.context, theme: self.presentationData.theme.theme, peer: EnginePeer(peer), authorOfMessage: self.messageReference, overrideImage: nil, emptyColor: .black, synchronousLoad: false, displayDimensions: CGSize(width: 40.0, height: 40.0)) self.avatarNode.setPeer(context: self.context, theme: self.presentationData.theme.theme, peer: EnginePeer(peer), authorOfMessage: self.messageReference, overrideImage: nil, emptyColor: .black, synchronousLoad: false, displayDimensions: CGSize(width: avatarHeaderSize(), height: avatarHeaderSize()))
} }
} }
} }
@ -1072,9 +1076,9 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat
overrideImage = .deletedIcon overrideImage = .deletedIcon
} }
if peer.smallProfileImage != nil { if peer.smallProfileImage != nil {
self.avatarNode.setPeerV2(context: context, theme: theme, peer: EnginePeer(peer), authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: 40.0, height: 40.0)) self.avatarNode.setPeerV2(context: context, theme: theme, peer: EnginePeer(peer), authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: avatarHeaderSize(), height: avatarHeaderSize()))
} else { } else {
self.avatarNode.setPeer(context: context, theme: theme, peer: EnginePeer(peer), authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: 40.0, height: 40.0)) self.avatarNode.setPeer(context: context, theme: theme, peer: EnginePeer(peer), authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: avatarHeaderSize(), height: avatarHeaderSize()))
} }
if peer.isPremium && context.sharedContext.energyUsageSettings.autoplayVideo { if peer.isPremium && context.sharedContext.energyUsageSettings.autoplayVideo {
@ -1111,7 +1115,7 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat
strongSelf.avatarNode.contentNode.addSubnode(videoNode) strongSelf.avatarNode.contentNode.addSubnode(videoNode)
strongSelf.avatarVideoNode = videoNode strongSelf.avatarVideoNode = videoNode
} }
videoNode.update(peer: EnginePeer(peer), photo: photo, size: CGSize(width: 40.0, height: 40.0)) videoNode.update(peer: EnginePeer(peer), photo: photo, size: CGSize(width: avatarHeaderSize(), height: avatarHeaderSize()))
if strongSelf.hierarchyTrackingLayer == nil { if strongSelf.hierarchyTrackingLayer == nil {
let hierarchyTrackingLayer = HierarchyTrackingLayer() let hierarchyTrackingLayer = HierarchyTrackingLayer()
@ -1220,8 +1224,8 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat
} }
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: leftInset + 8.0, y: 0.0), size: CGSize(width: 40.0, height: 40.0))) transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: leftInset + 8.0, y: -3.0), size: CGSize(width: avatarHeaderSize(), height: avatarHeaderSize())))
let avatarFrame = CGRect(origin: CGPoint(), size: CGSize(width: 40.0, height: 40.0)) let avatarFrame = CGRect(origin: CGPoint(), size: CGSize(width: avatarHeaderSize(), height: avatarHeaderSize()))
self.avatarNode.position = avatarFrame.center self.avatarNode.position = avatarFrame.center
self.avatarNode.bounds = CGRect(origin: CGPoint(), size: avatarFrame.size) self.avatarNode.bounds = CGRect(origin: CGPoint(), size: avatarFrame.size)
self.avatarNode.updateSize(size: avatarFrame.size) self.avatarNode.updateSize(size: avatarFrame.size)
@ -1265,7 +1269,7 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat
var avatarTransform: CATransform3D = CATransform3DIdentity var avatarTransform: CATransform3D = CATransform3DIdentity
if isHidden { if isHidden {
let scale: CGFloat = isHidden ? 0.001 : 1.0 let scale: CGFloat = isHidden ? 0.001 : 1.0
avatarTransform = CATransform3DTranslate(avatarTransform, -40.0 * 0.5, 40.0 * 0.5, 0.0) avatarTransform = CATransform3DTranslate(avatarTransform, -avatarHeaderSize() * 0.5, avatarHeaderSize() * 0.5, 0.0)
avatarTransform = CATransform3DScale(avatarTransform, scale, scale, 1.0) avatarTransform = CATransform3DScale(avatarTransform, scale, scale, 1.0)
} }
transition.updateTransform(node: self.avatarNode, transform: avatarTransform) transition.updateTransform(node: self.avatarNode, transform: avatarTransform)

View file

@ -64,7 +64,7 @@ private final class ChatMessageSelectionInputPanelNodeViewForOverlayContent: UIV
} }
} }
private final class GlassButtonView: HighlightTrackingButton { private final class GlassButtonView: UIView {
private struct Params: Equatable { private struct Params: Equatable {
let theme: PresentationTheme let theme: PresentationTheme
let size: CGSize let size: CGSize
@ -86,6 +86,7 @@ private final class GlassButtonView: HighlightTrackingButton {
} }
private let backgroundView: GlassBackgroundView private let backgroundView: GlassBackgroundView
let button: HighlightTrackingButton
private let iconView: GlassBackgroundView.ContentImageView private let iconView: GlassBackgroundView.ContentImageView
private var params: Params? private var params: Params?
@ -96,7 +97,7 @@ private final class GlassButtonView: HighlightTrackingButton {
} }
} }
override var isEnabled: Bool { var isEnabled: Bool = true {
didSet { didSet {
self.updateIsEnabled() self.updateIsEnabled()
} }
@ -125,19 +126,25 @@ private final class GlassButtonView: HighlightTrackingButton {
self.iconView = GlassBackgroundView.ContentImageView() self.iconView = GlassBackgroundView.ContentImageView()
self.backgroundView.contentView.addSubview(self.iconView) self.backgroundView.contentView.addSubview(self.iconView)
self.button = HighlightTrackingButton()
self.backgroundView.contentView.addSubview(self.button)
super.init(frame: frame) super.init(frame: frame)
self.addSubview(self.backgroundView) self.addSubview(self.backgroundView)
self.highligthedChanged = { [weak self] highlighted in if #available(iOS 26.0, *) {
guard let self else { } else {
return self.button.highligthedChanged = { [weak self] highlighted in
} guard let self else {
if highlighted && self.isEnabled && !self.isImplicitlyDisabled { return
self.backgroundView.contentView.alpha = 0.6 }
} else { if highlighted && self.isEnabled && !self.isImplicitlyDisabled {
self.backgroundView.contentView.alpha = 1.0 self.backgroundView.contentView.alpha = 0.6
self.backgroundView.contentView.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2) } else {
self.backgroundView.contentView.alpha = 1.0
self.backgroundView.contentView.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
}
} }
} }
} }
@ -156,17 +163,22 @@ private final class GlassButtonView: HighlightTrackingButton {
} }
private func updateImpl(params: Params, transition: ComponentTransition) { private func updateImpl(params: Params, transition: ComponentTransition) {
let isEnabled = self.isEnabled && !self.isImplicitlyDisabled
if let image = self.iconView.image { if let image = self.iconView.image {
let iconFrame = image.size.centered(in: CGRect(origin: CGPoint(), size: params.size)) let iconFrame = image.size.centered(in: CGRect(origin: CGPoint(), size: params.size))
transition.setFrame(view: self.iconView, frame: iconFrame) transition.setFrame(view: self.iconView, frame: iconFrame)
} }
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: params.size)) transition.setFrame(view: self.button, frame: CGRect(origin: CGPoint(), size: params.size))
self.backgroundView.update(size: params.size, cornerRadius: min(params.size.width, params.size.height) * 0.5, isDark: params.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: params.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: transition)
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: params.size))
self.backgroundView.update(size: params.size, cornerRadius: min(params.size.width, params.size.height) * 0.5, isDark: params.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: params.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: isEnabled, transition: transition)
let isEnabled = self.isEnabled && !self.isImplicitlyDisabled
self.iconView.alpha = isEnabled ? 1.0 : 0.5 self.iconView.alpha = isEnabled ? 1.0 : 0.5
self.iconView.tintMask.alpha = self.iconView.alpha self.iconView.tintMask.alpha = self.iconView.alpha
self.button.isEnabled = isEnabled
} }
private func updateIsEnabled() { private func updateIsEnabled() {
@ -255,12 +267,12 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
self.forwardButton.isImplicitlyDisabled = true self.forwardButton.isImplicitlyDisabled = true
self.shareButton.isImplicitlyDisabled = true self.shareButton.isImplicitlyDisabled = true
self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: .touchUpInside) self.deleteButton.button.addTarget(self, action: #selector(self.deleteButtonPressed), for: .touchUpInside)
self.reportButton.addTarget(self, action: #selector(self.reportButtonPressed), for: .touchUpInside) self.reportButton.button.addTarget(self, action: #selector(self.reportButtonPressed), for: .touchUpInside)
self.forwardButton.addTarget(self, action: #selector(self.forwardButtonPressed), for: .touchUpInside) self.forwardButton.button.addTarget(self, action: #selector(self.forwardButtonPressed), for: .touchUpInside)
self.shareButton.addTarget(self, action: #selector(self.shareButtonPressed), for: .touchUpInside) self.shareButton.button.addTarget(self, action: #selector(self.shareButtonPressed), for: .touchUpInside)
self.tagButton.addTarget(self, action: #selector(self.tagButtonPressed), for: .touchUpInside) self.tagButton.button.addTarget(self, action: #selector(self.tagButtonPressed), for: .touchUpInside)
self.tagEditButton.addTarget(self, action: #selector(self.tagButtonPressed), for: .touchUpInside) self.tagEditButton.button.addTarget(self, action: #selector(self.tagButtonPressed), for: .touchUpInside)
} }
deinit { deinit {

View file

@ -286,6 +286,7 @@ public class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode {
component: AnyComponent(AvatarStoryIndicatorComponent( component: AnyComponent(AvatarStoryIndicatorComponent(
hasUnseen: hasUnseen, hasUnseen: hasUnseen,
hasUnseenCloseFriendsItems: hasUnseen && (story?.isCloseFriends ?? false), hasUnseenCloseFriendsItems: hasUnseen && (story?.isCloseFriends ?? false),
hasLiveItems: false,
colors: storyColors, colors: storyColors,
activeLineWidth: 3.0, activeLineWidth: 3.0,
inactiveLineWidth: 1.0 + UIScreenPixel, inactiveLineWidth: 1.0 + UIScreenPixel,

View file

@ -273,7 +273,7 @@ public final class ChatFloatingTopicsPanel: Component {
} }
transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(), size: availableSize)) transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(), size: availableSize))
self.containerView.update(size: availableSize, transition: transition) self.containerView.update(size: availableSize, isDark: component.theme.overallDarkAppearance, transition: transition)
return availableSize return availableSize
} }

View file

@ -231,7 +231,7 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag
self.micButton.layer.allowsGroupOpacity = true self.micButton.layer.allowsGroupOpacity = true
self.view.addSubview(self.micButtonBackgroundView) self.view.addSubview(self.micButtonBackgroundView)
self.view.addSubview(self.micButton) self.micButtonBackgroundView.contentView.addSubview(self.micButton)
self.addSubnode(self.sendContainerNode) self.addSubnode(self.sendContainerNode)
self.sendContainerNode.view.addSubview(self.sendButtonBackgroundView) self.sendContainerNode.view.addSubview(self.sendButtonBackgroundView)

View file

@ -2405,7 +2405,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
if useBounceAnimation, case let .animated(_, curve) = transition, case .spring = curve { if useBounceAnimation, case let .animated(_, curve) = transition, case .spring = curve {
textInputContainerBackgroundTransition = textInputContainerBackgroundTransition.withUserData(GlassBackgroundView.TransitionFlagBounce()) textInputContainerBackgroundTransition = textInputContainerBackgroundTransition.withUserData(GlassBackgroundView.TransitionFlagBounce())
} }
self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: false, transition: textInputContainerBackgroundTransition) self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: true, transition: textInputContainerBackgroundTransition)
transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: textInputContainerBackgroundFrame) transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: textInputContainerBackgroundFrame)
transition.updateAlpha(node: self.textInputBackgroundNode, alpha: audioRecordingItemsAlpha) transition.updateAlpha(node: self.textInputBackgroundNode, alpha: audioRecordingItemsAlpha)
@ -2931,7 +2931,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
let containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: contentHeight + 64.0)) let containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: contentHeight + 64.0))
transition.updateFrame(view: self.glassBackgroundContainer, frame: containerFrame) transition.updateFrame(view: self.glassBackgroundContainer, frame: containerFrame)
self.glassBackgroundContainer.update(size: containerFrame.size, transition: ComponentTransition(transition)) self.glassBackgroundContainer.update(size: containerFrame.size, isDark: interfaceState.theme.overallDarkAppearance, transition: ComponentTransition(transition))
return contentHeight return contentHeight
} }

View file

@ -545,8 +545,10 @@ public final class GlassBackgroundContainerView: UIView {
return result return result
} }
public func update(size: CGSize, transition: ComponentTransition) { public func update(size: CGSize, isDark: Bool, transition: ComponentTransition) {
if let nativeView = self.nativeView { if let nativeView = self.nativeView {
nativeView.overrideUserInterfaceStyle = isDark ? .dark : .light
transition.setFrame(view: nativeView, frame: CGRect(origin: CGPoint(), size: size)) transition.setFrame(view: nativeView, frame: CGRect(origin: CGPoint(), size: size))
} else if let legacyView = self.legacyView { } else if let legacyView = self.legacyView {
transition.setFrame(view: legacyView, frame: CGRect(origin: CGPoint(), size: size)) transition.setFrame(view: legacyView, frame: CGRect(origin: CGPoint(), size: size))

View file

@ -160,9 +160,9 @@ public final class MessageInputActionButtonComponent: Component {
case up case up
} }
public enum Style { public enum Style: Equatable {
case legacy case legacy
case glass case glass(isTinted: Bool)
} }
public let mode: Mode public let mode: Mode
@ -519,7 +519,7 @@ public final class MessageInputActionButtonComponent: Component {
microphoneAlpha = 0.4 microphoneAlpha = 0.4
} }
if component.style == .glass, [.send, .close].contains(component.mode) { if case let .glass(isTinted) = component.style {
let backgroundView: GlassBackgroundView let backgroundView: GlassBackgroundView
if let current = self.backgroundView { if let current = self.backgroundView {
backgroundView = current backgroundView = current
@ -534,8 +534,16 @@ public final class MessageInputActionButtonComponent: Component {
if case .send = component.mode { if case .send = component.mode {
tintColor = UIColor(rgb: 0x029dff) tintColor = UIColor(rgb: 0x029dff)
} }
let glassTint: GlassBackgroundView.TintColor
if isTinted {
glassTint = .init(kind: tintKind, color: tintColor)
} else {
glassTint = .init(kind: .panel, color: defaultDarkPresentationTheme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7))
}
let buttonSize = CGSize(width: 40.0, height: 40.0) let buttonSize = CGSize(width: 40.0, height: 40.0)
backgroundView.update(size: buttonSize, cornerRadius: buttonSize.height / 2.0, isDark: true, tintColor: .init(kind: tintKind, color: tintColor), transition: transition) backgroundView.update(size: buttonSize, cornerRadius: buttonSize.height / 2.0, isDark: true, tintColor: glassTint, transition: transition)
backgroundView.frame = CGRect(origin: .zero, size: buttonSize) backgroundView.frame = CGRect(origin: .zero, size: buttonSize)
} }

View file

@ -809,18 +809,16 @@ public final class MessageInputPanelComponent: Component {
insets.left = 41.0 insets.left = 41.0
} }
if let _ = component.setMediaRecordingActive { if let _ = component.setMediaRecordingActive {
insets.right = 41.0 insets.right = 40.0 + 8.0 * 2.0
} }
let textFieldSideInset: CGFloat var textFieldSideInset: CGFloat = 8.0
switch component.style { if component.bottomInset <= 32.0 && !component.forceIsEditing && !component.hideKeyboard && !self.textFieldExternalState.isEditing {
case .media, .glass: textFieldSideInset += 18.0
textFieldSideInset = 8.0 insets.right += 18.0
default:
textFieldSideInset = 9.0
} }
var mediaInsets = UIEdgeInsets(top: insets.top, left: textFieldSideInset, bottom: insets.bottom, right: 41.0) var mediaInsets = UIEdgeInsets(top: insets.top, left: textFieldSideInset, bottom: insets.bottom, right: 40.0 + 8.0)
if case .glass = component.style { if case .glass = component.style {
mediaInsets.right = 54.0 mediaInsets.right = 54.0
} }
@ -1095,7 +1093,7 @@ public final class MessageInputPanelComponent: Component {
//transition.setFrame(view: self.vibrancyEffectView, frame: CGRect(origin: CGPoint(), size: fieldBackgroundFrame.size)) //transition.setFrame(view: self.vibrancyEffectView, frame: CGRect(origin: CGPoint(), size: fieldBackgroundFrame.size))
switch component.style { switch component.style {
case .glass: case .glass, .story:
if self.fieldGlassBackgroundView == nil { if self.fieldGlassBackgroundView == nil {
let fieldGlassBackgroundView = GlassBackgroundView(frame: fieldBackgroundFrame) let fieldGlassBackgroundView = GlassBackgroundView(frame: fieldBackgroundFrame)
self.insertSubview(fieldGlassBackgroundView, aboveSubview: self.fieldBackgroundView) self.insertSubview(fieldGlassBackgroundView, aboveSubview: self.fieldBackgroundView)
@ -1105,7 +1103,7 @@ public final class MessageInputPanelComponent: Component {
self.fieldBackgroundTint.isHidden = true self.fieldBackgroundTint.isHidden = true
} }
if let fieldGlassBackgroundView = self.fieldGlassBackgroundView { if let fieldGlassBackgroundView = self.fieldGlassBackgroundView {
fieldGlassBackgroundView.update(size: fieldBackgroundFrame.size, cornerRadius: baseFieldHeight * 0.5, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0x25272e, alpha: 0.72)), transition: transition) fieldGlassBackgroundView.update(size: fieldBackgroundFrame.size, cornerRadius: baseFieldHeight * 0.5, isDark: true, tintColor: component.style == .story ? .init(kind: .panel, color: defaultDarkPresentationTheme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)) : .init(kind: .custom, color: UIColor(rgb: 0x25272e, alpha: 0.72)), transition: transition)
transition.setFrame(view: fieldGlassBackgroundView, frame: fieldBackgroundFrame) transition.setFrame(view: fieldGlassBackgroundView, frame: fieldBackgroundFrame)
} }
default: default:
@ -1551,6 +1549,9 @@ public final class MessageInputPanelComponent: Component {
inputActionButtonMode = .close inputActionButtonMode = .close
} }
} else { } else {
if case .story = component.style {
inputActionButtonAvailableSize = CGSize(width: 40.0, height: 40.0)
}
if hasMediaEditing { if hasMediaEditing {
inputActionButtonMode = .send inputActionButtonMode = .send
} else { } else {
@ -1571,11 +1572,19 @@ public final class MessageInputPanelComponent: Component {
} }
} }
} }
let inputActionButtonStyle: MessageInputActionButtonComponent.Style
if component.style == .glass {
inputActionButtonStyle = .glass(isTinted: true)
} else if component.style == .story {
inputActionButtonStyle = .glass(isTinted: false)
} else {
inputActionButtonStyle = .legacy
}
let inputActionButtonSize = self.inputActionButton.update( let inputActionButtonSize = self.inputActionButton.update(
transition: transition, transition: transition,
component: AnyComponent(MessageInputActionButtonComponent( component: AnyComponent(MessageInputActionButtonComponent(
mode: inputActionButtonMode, mode: inputActionButtonMode,
style: component.style == .glass ? .glass : .legacy, style: inputActionButtonStyle,
storyId: component.storyItem?.id, storyId: component.storyItem?.id,
action: { [weak self] mode, action, sendAction in action: { [weak self] mode, action, sendAction in
guard let self, let component = self.component else { guard let self, let component = self.component else {
@ -1690,26 +1699,22 @@ public final class MessageInputPanelComponent: Component {
if rightButtonsOffsetX != 0.0 { if rightButtonsOffsetX != 0.0 {
inputActionButtonOriginX = availableSize.width - 3.0 + rightButtonsOffsetX inputActionButtonOriginX = availableSize.width - 3.0 + rightButtonsOffsetX
if displayLikeAction { if displayLikeAction {
inputActionButtonOriginX -= 39.0 inputActionButtonOriginX -= 40.0 + 8.0
} }
if component.forwardAction != nil { if component.forwardAction != nil {
inputActionButtonOriginX -= 46.0 inputActionButtonOriginX -= 40.0 + 8.0
} }
} else { } else {
if component.setMediaRecordingActive != nil || isEditing || component.style == .glass { if component.setMediaRecordingActive != nil || isEditing || component.style == .glass {
switch component.style { switch component.style {
case .glass: case .glass, .story:
inputActionButtonOriginX = fieldBackgroundFrame.maxX + 6.0 inputActionButtonOriginX = fieldBackgroundFrame.maxX + 8.0
default: default:
inputActionButtonOriginX = fieldBackgroundFrame.maxX + floorToScreenPixels((41.0 - inputActionButtonSize.width) * 0.5) inputActionButtonOriginX = fieldBackgroundFrame.maxX + floorToScreenPixels((41.0 - inputActionButtonSize.width) * 0.5)
} }
} else { } else {
inputActionButtonOriginX = size.width inputActionButtonOriginX = size.width
} }
if hasLikeAction {
inputActionButtonOriginX += 3.0
}
} }
if let inputActionButtonView = self.inputActionButton.view { if let inputActionButtonView = self.inputActionButton.view {
@ -1727,21 +1732,27 @@ public final class MessageInputPanelComponent: Component {
transition.setBounds(view: inputActionButtonView, bounds: CGRect(origin: CGPoint(), size: inputActionButtonFrame.size)) transition.setBounds(view: inputActionButtonView, bounds: CGRect(origin: CGPoint(), size: inputActionButtonFrame.size))
transition.setAlpha(view: inputActionButtonView, alpha: likeActionReplacesInputAction ? 0.0 : inputActionButtonAlpha) transition.setAlpha(view: inputActionButtonView, alpha: likeActionReplacesInputAction ? 0.0 : inputActionButtonAlpha)
if rightButtonsOffsetX != 0.0 { if hasLikeAction {
if hasLikeAction { inputActionButtonOriginX += 40.0 + 8.0
inputActionButtonOriginX += 46.0
}
} else {
if hasLikeAction {
inputActionButtonOriginX += 41.0
}
} }
} }
let likeActionButtonStyle: MessageInputActionButtonComponent.Style
var likeButtonContainerSize = CGSize(width: 33.0, height: 33.0)
if component.style == .glass {
likeActionButtonStyle = .glass(isTinted: true)
likeButtonContainerSize = CGSize(width: 40.0, height: 40.0)
} else if component.style == .story {
likeActionButtonStyle = .glass(isTinted: false)
likeButtonContainerSize = CGSize(width: 40.0, height: 40.0)
} else {
likeActionButtonStyle = .legacy
}
let likeButtonSize = self.likeButton.update( let likeButtonSize = self.likeButton.update(
transition: transition, transition: transition,
component: AnyComponent(MessageInputActionButtonComponent( component: AnyComponent(MessageInputActionButtonComponent(
mode: .like(reaction: component.myReaction?.reaction, file: component.myReaction?.file, animationFileId: component.myReaction?.animationFileId), mode: .like(reaction: component.myReaction?.reaction, file: component.myReaction?.file, animationFileId: component.myReaction?.animationFileId),
style: likeActionButtonStyle,
storyId: component.storyItem?.id, storyId: component.storyItem?.id,
action: { [weak self] _, action, _ in action: { [weak self] _, action, _ in
guard let self, let component = self.component else { guard let self, let component = self.component else {
@ -1770,7 +1781,7 @@ public final class MessageInputPanelComponent: Component {
videoRecordingStatus: nil videoRecordingStatus: nil
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: 33.0, height: 33.0) containerSize: likeButtonContainerSize
) )
if let likeButtonView = self.likeButton.view { if let likeButtonView = self.likeButton.view {
if likeButtonView.superview == nil { if likeButtonView.superview == nil {
@ -1783,7 +1794,7 @@ public final class MessageInputPanelComponent: Component {
transition.setPosition(view: likeButtonView, position: likeButtonFrame.center) transition.setPosition(view: likeButtonView, position: likeButtonFrame.center)
transition.setBounds(view: likeButtonView, bounds: CGRect(origin: CGPoint(), size: likeButtonFrame.size)) transition.setBounds(view: likeButtonView, bounds: CGRect(origin: CGPoint(), size: likeButtonFrame.size))
transition.setAlpha(view: likeButtonView, alpha: displayLikeAction ? 1.0 : 0.0) transition.setAlpha(view: likeButtonView, alpha: displayLikeAction ? 1.0 : 0.0)
inputActionButtonOriginX += 41.0 inputActionButtonOriginX += 40.0 + 8.0
} }
var fieldIconNextX = fieldBackgroundFrame.maxX - 4.0 var fieldIconNextX = fieldBackgroundFrame.maxX - 4.0
@ -1855,7 +1866,7 @@ public final class MessageInputPanelComponent: Component {
component: AnyComponent(Button( component: AnyComponent(Button(
content: AnyComponent(LottieComponent( content: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(name: animationName), content: LottieComponent.AppBundleContent(name: animationName),
color: .white color: defaultDarkPresentationTheme.chat.inputPanel.inputControlColor
)), )),
action: { [weak self] in action: { [weak self] in
guard let self else { guard let self else {

View file

@ -183,7 +183,8 @@ private enum PeerMembersListEntry: Comparable, Identifiable {
return ( return (
total: storyStats.totalCount, total: storyStats.totalCount,
unseen: storyStats.unseenCount, unseen: storyStats.unseenCount,
hasUnseenCloseFriends: storyStats.hasUnseenCloseFriends hasUnseenCloseFriends: storyStats.hasUnseenCloseFriends,
hasLiveItems: storyStats.hasLiveItems
) )
}, },
openStories: { _, sourceNode in openStories: { _, sourceNode in

View file

@ -52,7 +52,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
private let playbackStartDisposable = MetaDisposable() private let playbackStartDisposable = MetaDisposable()
var storyData: (totalCount: Int, unseenCount: Int, hasUnseenCloseFriends: Bool)? var storyData: (totalCount: Int, unseenCount: Int, hasUnseenCloseFriends: Bool, hasLiveItems: Bool)?
var storyProgress: Float? var storyProgress: Float?
init(context: AccountContext) { init(context: AccountContext) {
@ -146,6 +146,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
totalCount: storyData.totalCount, totalCount: storyData.totalCount,
unseenCount: storyData.unseenCount, unseenCount: storyData.unseenCount,
hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends, hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends,
hasLiveItems: storyData.hasLiveItems,
progress: self.storyProgress progress: self.storyProgress
) )
} else if let storyProgress = self.storyProgress { } else if let storyProgress = self.storyProgress {
@ -153,6 +154,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
totalCount: 1, totalCount: 1,
unseenCount: 1, unseenCount: 1,
hasUnseenCloseFriendsItems: false, hasUnseenCloseFriendsItems: false,
hasLiveItems: false,
progress: storyProgress progress: storyProgress
) )
} }

View file

@ -5352,7 +5352,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} }
} }
self.headerNode.avatarListNode.avatarContainerNode.storyData = (totalCount, unseenCount, state.hasUnseenCloseFriends && peer.id != self.context.account.peerId) self.headerNode.avatarListNode.avatarContainerNode.storyData = (totalCount, unseenCount, state.hasUnseenCloseFriends && peer.id != self.context.account.peerId, state.hasLiveItems)
self.headerNode.avatarListNode.listContainerNode.storyParams = (peer, state.items.prefix(3).compactMap { item -> EngineStoryItem? in self.headerNode.avatarListNode.listContainerNode.storyParams = (peer, state.items.prefix(3).compactMap { item -> EngineStoryItem? in
switch item { switch item {
case let .item(item): case let .item(item):
@ -8997,7 +8997,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
if channel.hasPermission(.manageCalls) { if channel.hasPermission(.manageCalls) {
canCreateStream = true canCreateStream = true
credentialsPromise = Promise() credentialsPromise = Promise()
credentialsPromise?.set(context.engine.calls.getGroupCallStreamCredentials(peerId: peerId, revokePreviousCredentials: false) |> `catch` { _ -> Signal<GroupCallStreamCredentials, NoError> in return .never() }) credentialsPromise?.set(context.engine.calls.getGroupCallStreamCredentials(peerId: peerId, isLiveStream: false, revokePreviousCredentials: false) |> `catch` { _ -> Signal<GroupCallStreamCredentials, NoError> in return .never() })
} }
default: default:
break break

View file

@ -420,6 +420,7 @@ public final class AvatarStoryIndicatorComponent: Component {
public let hasUnseen: Bool public let hasUnseen: Bool
public let hasUnseenCloseFriendsItems: Bool public let hasUnseenCloseFriendsItems: Bool
public let hasLiveItems: Bool
public let colors: Colors public let colors: Colors
public let activeLineWidth: CGFloat public let activeLineWidth: CGFloat
public let inactiveLineWidth: CGFloat public let inactiveLineWidth: CGFloat
@ -430,6 +431,7 @@ public final class AvatarStoryIndicatorComponent: Component {
public init( public init(
hasUnseen: Bool, hasUnseen: Bool,
hasUnseenCloseFriendsItems: Bool, hasUnseenCloseFriendsItems: Bool,
hasLiveItems: Bool,
colors: Colors, colors: Colors,
activeLineWidth: CGFloat, activeLineWidth: CGFloat,
inactiveLineWidth: CGFloat, inactiveLineWidth: CGFloat,
@ -439,6 +441,7 @@ public final class AvatarStoryIndicatorComponent: Component {
) { ) {
self.hasUnseen = hasUnseen self.hasUnseen = hasUnseen
self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems
self.hasLiveItems = hasLiveItems
self.colors = colors self.colors = colors
self.activeLineWidth = activeLineWidth self.activeLineWidth = activeLineWidth
self.inactiveLineWidth = inactiveLineWidth self.inactiveLineWidth = inactiveLineWidth
@ -454,6 +457,9 @@ public final class AvatarStoryIndicatorComponent: Component {
if lhs.hasUnseenCloseFriendsItems != rhs.hasUnseenCloseFriendsItems { if lhs.hasUnseenCloseFriendsItems != rhs.hasUnseenCloseFriendsItems {
return false return false
} }
if lhs.hasLiveItems != rhs.hasLiveItems {
return false
}
if lhs.colors != rhs.colors { if lhs.colors != rhs.colors {
return false return false
} }
@ -659,7 +665,10 @@ public final class AvatarStoryIndicatorComponent: Component {
let activeColors: [CGColor] let activeColors: [CGColor]
let inactiveColors: [CGColor] let inactiveColors: [CGColor]
if component.hasUnseenCloseFriendsItems { if component.hasLiveItems {
//TODO:localize
activeColors = [UIColor(rgb: 0xFF3777).cgColor, UIColor(rgb: 0xFF2D55).cgColor]
} else if component.hasUnseenCloseFriendsItems {
activeColors = component.colors.unseenCloseFriendsColors.map(\.cgColor) activeColors = component.colors.unseenCloseFriendsColors.map(\.cgColor)
} else { } else {
activeColors = component.colors.unseenColors.map(\.cgColor) activeColors = component.colors.unseenColors.map(\.cgColor)
@ -681,7 +690,7 @@ public final class AvatarStoryIndicatorComponent: Component {
var locations: [CGFloat] = [0.0, 1.0] var locations: [CGFloat] = [0.0, 1.0]
if let counters = component.counters, counters.totalCount > 1 { if let counters = component.counters, !component.hasLiveItems, counters.totalCount > 1 {
if component.isRoundedRect { if component.isRoundedRect {
let lineWidth: CGFloat = component.hasUnseen ? component.activeLineWidth : component.inactiveLineWidth let lineWidth: CGFloat = component.hasUnseen ? component.activeLineWidth : component.inactiveLineWidth
context.setLineWidth(lineWidth) context.setLineWidth(lineWidth)
@ -839,7 +848,9 @@ public final class AvatarStoryIndicatorComponent: Component {
context.clip() context.clip()
let colors: [CGColor] let colors: [CGColor]
if component.hasUnseen { if component.hasLiveItems {
colors = activeColors
} else if component.hasUnseen {
colors = activeColors colors = activeColors
} else { } else {
colors = inactiveColors colors = inactiveColors

View file

@ -940,7 +940,8 @@ public final class PeerListItemComponent: Component {
return AvatarNode.StoryStats( return AvatarNode.StoryStats(
totalCount: storyStats.totalCount == 0 ? 0 : 1, totalCount: storyStats.totalCount == 0 ? 0 : 1,
unseenCount: storyStats.unseenCount == 0 ? 0 : 1, unseenCount: storyStats.unseenCount == 0 ? 0 : 1,
hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends,
hasLiveItems: storyStats.hasLiveItems
) )
}, presentationParams: AvatarNode.StoryPresentationParams( }, presentationParams: AvatarNode.StoryPresentationParams(
colors: AvatarNode.Colors(theme: component.theme), colors: AvatarNode.Colors(theme: component.theme),

View file

@ -101,6 +101,7 @@ swift_library(
"//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController", "//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController",
"//submodules/DirectMediaImageCache", "//submodules/DirectMediaImageCache",
"//submodules/PromptUI", "//submodules/PromptUI",
"//submodules/TelegramCallsUI",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View file

@ -23,8 +23,9 @@ final class StoryAuthorInfoComponent: Component {
let timestamp: Int32 let timestamp: Int32
let counters: Counters? let counters: Counters?
let isEdited: Bool let isEdited: Bool
let isLiveStream: Bool
init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer?, forwardInfo: EngineStoryItem.ForwardInfo?, author: EnginePeer?, timestamp: Int32, counters: Counters?, isEdited: Bool) { init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer?, forwardInfo: EngineStoryItem.ForwardInfo?, author: EnginePeer?, timestamp: Int32, counters: Counters?, isEdited: Bool, isLiveStream: Bool) {
self.context = context self.context = context
self.strings = strings self.strings = strings
self.peer = peer self.peer = peer
@ -33,6 +34,7 @@ final class StoryAuthorInfoComponent: Component {
self.timestamp = timestamp self.timestamp = timestamp
self.counters = counters self.counters = counters
self.isEdited = isEdited self.isEdited = isEdited
self.isLiveStream = isLiveStream
} }
static func ==(lhs: StoryAuthorInfoComponent, rhs: StoryAuthorInfoComponent) -> Bool { static func ==(lhs: StoryAuthorInfoComponent, rhs: StoryAuthorInfoComponent) -> Bool {
@ -59,6 +61,9 @@ final class StoryAuthorInfoComponent: Component {
} }
if lhs.isEdited != rhs.isEdited { if lhs.isEdited != rhs.isEdited {
return false return false
}
if lhs.isLiveStream != rhs.isLiveStream {
return false
} }
return true return true
} }
@ -68,6 +73,7 @@ final class StoryAuthorInfoComponent: Component {
private var repostIconView: UIImageView? private var repostIconView: UIImageView?
private var avatarNode: AvatarNode? private var avatarNode: AvatarNode?
private let subtitle = ComponentView<Empty>() private let subtitle = ComponentView<Empty>()
private var liveBadgeView: UIImageView?
private var counterLabel: ComponentView<Empty>? private var counterLabel: ComponentView<Empty>?
private var component: StoryAuthorInfoComponent? private var component: StoryAuthorInfoComponent?
@ -238,7 +244,43 @@ final class StoryAuthorInfoComponent: Component {
avatarNode.view.removeFromSuperview() avatarNode.view.removeFromSuperview()
} }
let subtitleFrame = CGRect(origin: CGPoint(x: leftInset + subtitleOffset, y: titleFrame.maxY + spacing + UIScreenPixel), size: subtitleSize) var subtitleFrame = CGRect(origin: CGPoint(x: leftInset + subtitleOffset, y: titleFrame.maxY + spacing + UIScreenPixel), size: subtitleSize)
if component.isLiveStream {
let liveBadgeView: UIImageView
if let current = self.liveBadgeView {
liveBadgeView = current
} else {
liveBadgeView = UIImageView()
self.liveBadgeView = liveBadgeView
self.addSubview(liveBadgeView)
//TODO:localize
let liveString = NSAttributedString(string: "LIVE", font: Font.semibold(10.0), textColor: .white)
let liveStringBounds = liveString.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
let liveBadgeSize = CGSize(width: ceil(liveStringBounds.width) + 3.0 * 2.0, height: ceil(liveStringBounds.height) + 1.0 * 2.0)
liveBadgeView.image = generateImage(liveBadgeSize, rotatedContext: { size, context in
UIGraphicsPushContext(context)
defer {
UIGraphicsPopContext()
}
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor(rgb: 0xFF2D55).cgColor)
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: size.height * 0.5).cgPath)
context.fillPath()
liveString.draw(at: CGPoint(x: floorToScreenPixels((size.width - liveStringBounds.width) * 0.5), y: floorToScreenPixels((size.height - liveStringBounds.height) * 0.5)))
})
}
if let image = liveBadgeView.image {
let liveBadgeFrame = CGRect(origin: CGPoint(x: subtitleFrame.minX, y: subtitleFrame.minY), size: image.size)
liveBadgeView.frame = liveBadgeFrame
subtitleFrame.origin.x += image.size.width + 3.0
}
} else if let liveBadgeView = self.liveBadgeView {
self.liveBadgeView = nil
liveBadgeView.removeFromSuperview()
}
if let titleView = self.title.view { if let titleView = self.title.view {
if titleView.superview == nil { if titleView.superview == nil {

View file

@ -10,10 +10,12 @@ import AvatarNode
final class StoryAvatarInfoComponent: Component { final class StoryAvatarInfoComponent: Component {
let context: AccountContext let context: AccountContext
let peer: EnginePeer let peer: EnginePeer
let isLiveStream: Bool
init(context: AccountContext, peer: EnginePeer) { init(context: AccountContext, peer: EnginePeer, isLiveStream: Bool) {
self.context = context self.context = context
self.peer = peer self.peer = peer
self.isLiveStream = isLiveStream
} }
static func ==(lhs: StoryAvatarInfoComponent, rhs: StoryAvatarInfoComponent) -> Bool { static func ==(lhs: StoryAvatarInfoComponent, rhs: StoryAvatarInfoComponent) -> Bool {
@ -23,6 +25,9 @@ final class StoryAvatarInfoComponent: Component {
if lhs.peer != rhs.peer { if lhs.peer != rhs.peer {
return false return false
} }
if lhs.isLiveStream != rhs.isLiveStream {
return false
}
return true return true
} }
@ -57,6 +62,24 @@ final class StoryAvatarInfoComponent: Component {
peer: component.peer, peer: component.peer,
synchronousLoad: true synchronousLoad: true
) )
self.avatarNode.setStoryStats(
storyStats: component.isLiveStream ? AvatarNode.StoryStats(
totalCount: 1,
unseenCount: 1,
hasUnseenCloseFriendsItems: false,
hasLiveItems: true
) : nil,
presentationParams: AvatarNode.StoryPresentationParams(
colors: AvatarNode.Colors(
unseenColors: [.white],
unseenCloseFriendsColors: [.white],
seenColors: [.white]
),
lineWidth: 1.33,
inactiveLineWidth: 1.33
),
transition: .immediate
)
return size return size
} }

View file

@ -649,6 +649,7 @@ public final class StoryContentContextImpl: StoryContentContext {
peer: peer, peer: peer,
hasUnseen: state.hasUnseen, hasUnseen: state.hasUnseen,
hasUnseenCloseFriends: state.hasUnseenCloseFriends, hasUnseenCloseFriends: state.hasUnseenCloseFriends,
hasLiveItems: false,
hasPending: false, hasPending: false,
storyCount: state.items.count, storyCount: state.items.count,
unseenCount: 0, unseenCount: 0,

View file

@ -14,6 +14,7 @@ import HierarchyTrackingLayer
import ButtonComponent import ButtonComponent
import MultilineTextComponent import MultilineTextComponent
import TelegramPresentationData import TelegramPresentationData
import TelegramCallsUI
final class StoryItemContentComponent: Component { final class StoryItemContentComponent: Component {
typealias EnvironmentType = StoryContentItem.Environment typealias EnvironmentType = StoryContentItem.Environment
@ -92,6 +93,8 @@ final class StoryItemContentComponent: Component {
private let imageView: StoryItemImageView private let imageView: StoryItemImageView
private let overlaysView: StoryItemOverlaysView private let overlaysView: StoryItemOverlaysView
private var videoNode: UniversalVideoNode? private var videoNode: UniversalVideoNode?
private var mediaStreamCall: PresentationGroupCallImpl?
private var mediaStream: ComponentView<Empty>?
private var loadingEffectView: StoryItemLoadingEffectView? private var loadingEffectView: StoryItemLoadingEffectView?
private var loadingEffectAppearanceTimer: SwiftSignalKit.Timer? private var loadingEffectAppearanceTimer: SwiftSignalKit.Timer?
@ -385,6 +388,10 @@ final class StoryItemContentComponent: Component {
if !self.isSeeking { if !self.isSeeking {
self.updateVideoPlaybackProgress() self.updateVideoPlaybackProgress()
} }
} else if case .liveStream = self.currentMessageMedia {
if !self.isSeeking {
self.updateVideoPlaybackProgress()
}
} else { } else {
if !self.markedAsSeen { if !self.markedAsSeen {
self.markedAsSeen = true self.markedAsSeen = true
@ -599,7 +606,10 @@ final class StoryItemContentComponent: Component {
let selectedMedia: EngineMedia let selectedMedia: EngineMedia
var messageMedia: EngineMedia? var messageMedia: EngineMedia?
if !component.preferHighQuality, !component.item.isMy, let alternativeMediaValue = component.item.alternativeMediaList.first { if case .liveStream = component.item.media {
selectedMedia = component.item.media
messageMedia = selectedMedia
} else if !component.preferHighQuality, !component.item.isMy, let alternativeMediaValue = component.item.alternativeMediaList.first {
selectedMedia = alternativeMediaValue selectedMedia = alternativeMediaValue
switch alternativeMediaValue { switch alternativeMediaValue {
@ -628,7 +638,7 @@ final class StoryItemContentComponent: Component {
} }
var reloadMedia = false var reloadMedia = false
if self.currentMessageMedia?.id != messageMedia?.id { if self.currentMessageMedia?.id != messageMedia?.id || (self.currentMessageMedia == nil) != (messageMedia == nil) {
self.currentMessageMedia = messageMedia self.currentMessageMedia = messageMedia
reloadMedia = true reloadMedia = true
@ -707,47 +717,154 @@ final class StoryItemContentComponent: Component {
} }
} }
if let messageMedia { if case let .liveStream(liveStream) = messageMedia {
var applyState = false var mediaStreamTransition = transition
self.imageView.didLoadContents = { [weak self] in let mediaStream: ComponentView<Empty>
guard let self else { if let current = self.mediaStream {
return mediaStream = current
} } else {
self.contentLoaded = true mediaStreamTransition = mediaStreamTransition.withAnimation(.none)
if applyState { mediaStream = ComponentView()
self.state?.updated(transition: .immediate) self.mediaStream = mediaStream
}
} }
self.imageView.update(
context: component.context,
strings: component.strings,
peer: component.peer,
storyId: component.item.id,
media: messageMedia,
size: availableSize,
isCaptureProtected: component.item.isForwardingDisabled,
attemptSynchronous: synchronousLoad,
transition: transition
)
self.updateOverlays(component: component, size: availableSize, synchronousLoad: synchronousLoad, transition: transition)
applyState = true
if self.imageView.isContentLoaded {
self.contentLoaded = true
}
transition.setFrame(view: self.imageView, frame: CGRect(origin: CGPoint(), size: availableSize))
transition.setFrame(view: self.overlaysView, frame: CGRect(origin: CGPoint(), size: availableSize))
var dimensions: CGSize? let mediaStreamCall: PresentationGroupCallImpl
switch messageMedia { if let current = self.mediaStreamCall {
case let .image(image): mediaStreamCall = current
dimensions = image.representations.last?.dimensions.cgSize } else {
case let .file(file): let initialCall = EngineGroupCallDescription(
dimensions = file.dimensions?.cgSize id: liveStream.call.id,
default: accessHash: liveStream.call.accessHash,
break title: nil,
scheduleTimestamp: nil,
subscribedToScheduled: false,
isStream: true
)
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
mediaStreamCall = PresentationGroupCallImpl(
accountContext: component.context,
audioSession: component.context.sharedContext.mediaManager.audioSession,
callKitIntegration: nil,
getDeviceAccessData: {
(
presentationData: presentationData,
present: { c, a in
},
openSettings: {
}
)
},
initialCall: (initialCall, .id(id: liveStream.call.id, accessHash: liveStream.call.accessHash)),
internalId: CallSessionInternalId(),
peerId: nil,
isChannel: false,
invite: nil,
joinAsPeerId: nil,
isStream: true,
keyPair: nil,
conferenceSourceId: nil,
isConference: false,
beginWithVideo: false,
sharedAudioContext: nil,
unmuteByDefault: false
)
self.mediaStreamCall = mediaStreamCall
let _ = mediaStreamCall.accountContext.engine.calls.getGroupCallStreamCredentials(peerId: mediaStreamCall.accountContext.account.peerId, isLiveStream: true, revokePreviousCredentials: false).startStandalone(next: { params in
print("url: \(params.url), streamKey: \(params.streamKey)")
})
} }
if dimensions == nil {
switch component.item.media { let _ = mediaStream.update(
transition: mediaStreamTransition,
component: AnyComponent(MediaStreamVideoComponent(
call: mediaStreamCall,
hasVideo: true,
isVisible: true,
isAdmin: false,
peerTitle: "",
addInset: false,
isFullscreen: false,
videoLoading: false,
callPeer: nil,
activatePictureInPicture: ActionSlot(),
deactivatePictureInPicture: ActionSlot(),
bringBackControllerForPictureInPictureDeactivation: { f in
f()
},
pictureInPictureClosed: {
},
onVideoSizeRetrieved: { _ in
},
onVideoPlaybackLiveChange: { [weak self] isLive in
guard let self else {
return
}
self.videoPlaybackStatus = MediaPlayerStatus(
generationTimestamp: CACurrentMediaTime(),
duration: .infinity,
dimensions: CGSize(),
timestamp: 0.0,
baseRate: 1.0,
seekId: 0,
status: isLive ? .playing : .buffering(initial: false, whilePlaying: true, progress: 0.0, display: true),
soundEnabled: true
)
if !self.isSeeking {
self.updateVideoPlaybackProgress()
}
}
)),
environment: {},
containerSize: availableSize
)
let mediaStreamFrame = CGRect(origin: CGPoint(), size: availableSize)
if let mediaStreamView = mediaStream.view {
if mediaStreamView.superview == nil {
self.insertSubview(mediaStreamView, aboveSubview: self.imageView)
}
mediaStreamTransition.setFrame(view: mediaStreamView, frame: mediaStreamFrame)
}
} else {
if let mediaStream = self.mediaStream {
self.mediaStream = nil
mediaStream.view?.removeFromSuperview()
}
if let messageMedia {
var applyState = false
self.imageView.didLoadContents = { [weak self] in
guard let self else {
return
}
self.contentLoaded = true
if applyState {
self.state?.updated(transition: .immediate)
}
}
self.imageView.update(
context: component.context,
strings: component.strings,
peer: component.peer,
storyId: component.item.id,
media: messageMedia,
size: availableSize,
isCaptureProtected: component.item.isForwardingDisabled,
attemptSynchronous: synchronousLoad,
transition: transition
)
self.updateOverlays(component: component, size: availableSize, synchronousLoad: synchronousLoad, transition: transition)
applyState = true
if self.imageView.isContentLoaded {
self.contentLoaded = true
}
transition.setFrame(view: self.imageView, frame: CGRect(origin: CGPoint(), size: availableSize))
transition.setFrame(view: self.overlaysView, frame: CGRect(origin: CGPoint(), size: availableSize))
var dimensions: CGSize?
switch messageMedia {
case let .image(image): case let .image(image):
dimensions = image.representations.last?.dimensions.cgSize dimensions = image.representations.last?.dimensions.cgSize
case let .file(file): case let .file(file):
@ -755,28 +872,38 @@ final class StoryItemContentComponent: Component {
default: default:
break break
} }
} if dimensions == nil {
switch component.item.media {
if let dimensions { case let .image(image):
var imageSize = dimensions.aspectFilled(availableSize) dimensions = image.representations.last?.dimensions.cgSize
if imageSize.width < availableSize.width && imageSize.width >= availableSize.width - 5.0 { case let .file(file):
imageSize.width = availableSize.width dimensions = file.dimensions?.cgSize
default:
break
}
} }
if imageSize.height < availableSize.height && imageSize.height >= availableSize.height - 5.0 {
imageSize.height = availableSize.height
}
let _ = imageSize
if let videoNode = self.videoNode { if let dimensions {
let videoSize = dimensions.aspectFilled(availableSize) var imageSize = dimensions.aspectFilled(availableSize)
videoNode.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - videoSize.width) * 0.5), y: floor((availableSize.height - videoSize.height) * 0.5)), size: videoSize) if imageSize.width < availableSize.width && imageSize.width >= availableSize.width - 5.0 {
videoNode.updateLayout(size: videoSize, transition: .immediate) imageSize.width = availableSize.width
}
if imageSize.height < availableSize.height && imageSize.height >= availableSize.height - 5.0 {
imageSize.height = availableSize.height
}
let _ = imageSize
if let videoNode = self.videoNode {
let videoSize = dimensions.aspectFilled(availableSize)
videoNode.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - videoSize.width) * 0.5), y: floor((availableSize.height - videoSize.height) * 0.5)), size: videoSize)
videoNode.updateLayout(size: videoSize, transition: .immediate)
}
} }
} }
} }
switch selectedMedia { switch selectedMedia {
case .image, .file: case .image, .file, .liveStream:
if let unsupportedText = self.unsupportedText { if let unsupportedText = self.unsupportedText {
self.unsupportedText = nil self.unsupportedText = nil
unsupportedText.view?.removeFromSuperview() unsupportedText.view?.removeFromSuperview()

View file

@ -1688,7 +1688,11 @@ public final class StoryItemSetContainerComponent: Component {
} }
} }
} else if component.slice.effectivePeer.id == component.context.account.peerId { } else if component.slice.effectivePeer.id == component.context.account.peerId {
displayFooter = true if case .liveStream = component.slice.item.storyItem.media {
displayFooter = false
} else {
displayFooter = true
}
} else if component.slice.item.storyItem.isPending { } else if component.slice.item.storyItem.isPending {
displayFooter = true displayFooter = true
} else if case let .user(user) = component.slice.peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { } else if case let .user(user) = component.slice.peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
@ -1926,7 +1930,9 @@ public final class StoryItemSetContainerComponent: Component {
} }
var displayViewLists = false var displayViewLists = false
if component.slice.effectivePeer.id == component.context.account.peerId { if case .liveStream = component.slice.item.storyItem.media {
displayViewLists = false
} else if component.slice.effectivePeer.id == component.context.account.peerId {
displayViewLists = true displayViewLists = true
} else if case let .channel(channel) = component.slice.effectivePeer, channel.flags.contains(.isCreator) || component.slice.additionalPeerData.canViewStats { } else if case let .channel(channel) = component.slice.effectivePeer, channel.flags.contains(.isCreator) || component.slice.additionalPeerData.canViewStats {
displayViewLists = true displayViewLists = true
@ -1941,7 +1947,9 @@ public final class StoryItemSetContainerComponent: Component {
return true return true
} else { } else {
var canReply = false var canReply = false
if case .user = component.slice.effectivePeer { if case .liveStream = component.slice.item.storyItem.media {
canReply = true
} else if case .user = component.slice.effectivePeer {
canReply = true canReply = true
if component.slice.effectivePeer.id == component.context.account.peerId { if component.slice.effectivePeer.id == component.context.account.peerId {
@ -1969,7 +1977,9 @@ public final class StoryItemSetContainerComponent: Component {
} }
var canReply = false var canReply = false
if case .user = component.slice.effectivePeer { if case .liveStream = component.slice.item.storyItem.media {
canReply = true
} else if case .user = component.slice.effectivePeer {
canReply = true canReply = true
if component.slice.effectivePeer.id == component.context.account.peerId { if component.slice.effectivePeer.id == component.context.account.peerId {
@ -2760,7 +2770,11 @@ public final class StoryItemSetContainerComponent: Component {
switch channel.info { switch channel.info {
case .broadcast: case .broadcast:
isChannel = true isChannel = true
showMessageInputPanel = false if case .liveStream = component.slice.item.storyItem.media {
showMessageInputPanel = true
} else {
showMessageInputPanel = false
}
case .group: case .group:
if let bannedSendText = channel.hasBannedPermission(.banSendText, ignoreDefault: canBypassRestrictions) { if let bannedSendText = channel.hasBannedPermission(.banSendText, ignoreDefault: canBypassRestrictions) {
if bannedSendText.1 || component.slice.additionalPeerData.boostsToUnrestrict == nil { if bannedSendText.1 || component.slice.additionalPeerData.boostsToUnrestrict == nil {
@ -2772,7 +2786,11 @@ public final class StoryItemSetContainerComponent: Component {
isGroup = true isGroup = true
} }
} else { } else {
showMessageInputPanel = component.slice.effectivePeer.id != component.context.account.peerId if case .liveStream = component.slice.item.storyItem.media {
showMessageInputPanel = true
} else {
showMessageInputPanel = component.slice.effectivePeer.id != component.context.account.peerId
}
} }
if case let .user(user) = component.slice.peer, let _ = user.botInfo { if case let .user(user) = component.slice.peer, let _ = user.botInfo {
showMessageInputPanel = false showMessageInputPanel = false
@ -2834,6 +2852,9 @@ public final class StoryItemSetContainerComponent: Component {
if let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars { if let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars {
let dateTimeFormat = component.context.sharedContext.currentPresentationData.with { $0 }.dateTimeFormat let dateTimeFormat = component.context.sharedContext.currentPresentationData.with { $0 }.dateTimeFormat
inputPlaceholder = .plain(component.strings.Chat_InputTextPaidMessagePlaceholder(" # \(presentationStringsFormattedNumber(Int32(sendPaidMessageStars.value), dateTimeFormat.groupingSeparator))").string) inputPlaceholder = .plain(component.strings.Chat_InputTextPaidMessagePlaceholder(" # \(presentationStringsFormattedNumber(Int32(sendPaidMessageStars.value), dateTimeFormat.groupingSeparator))").string)
} else if case .liveStream = component.slice.item.storyItem.media {
//TODO:localize
inputPlaceholder = .plain("Comment")
} else { } else {
inputPlaceholder = .plain(isGroup ? component.strings.Story_InputPlaceholderReplyInGroup : component.strings.Story_InputPlaceholderReplyPrivately) inputPlaceholder = .plain(isGroup ? component.strings.Story_InputPlaceholderReplyInGroup : component.strings.Story_InputPlaceholderReplyPrivately)
} }
@ -2859,6 +2880,8 @@ public final class StoryItemSetContainerComponent: Component {
self.inputPanel.parentState = state self.inputPanel.parentState = state
var inputPanelSize: CGSize? var inputPanelSize: CGSize?
let _ = inputNodeVisible
let startTime23 = CFAbsoluteTimeGetCurrent() let startTime23 = CFAbsoluteTimeGetCurrent()
if showMessageInputPanel { if showMessageInputPanel {
@ -2871,6 +2894,14 @@ public final class StoryItemSetContainerComponent: Component {
} }
} }
var displayAttachmentAction = true
if case .liveStream = component.slice.item.storyItem.media {
displayAttachmentAction = false
}
if component.slice.effectivePeer.isService {
displayAttachmentAction = false
}
inputPanelSize = self.inputPanel.update( inputPanelSize = self.inputPanel.update(
transition: inputPanelTransition, transition: inputPanelTransition,
component: AnyComponent(MessageInputPanelComponent( component: AnyComponent(MessageInputPanelComponent(
@ -2949,13 +2980,13 @@ public final class StoryItemSetContainerComponent: Component {
self.sendMessageContext.videoRecorderValue?.dismissVideo() self.sendMessageContext.videoRecorderValue?.dismissVideo()
self.sendMessageContext.discardMediaRecordingPreview(view: self) self.sendMessageContext.discardMediaRecordingPreview(view: self)
}, },
attachmentAction: component.slice.effectivePeer.isService ? nil : { [weak self] in attachmentAction: !displayAttachmentAction ? nil : { [weak self] in
guard let self else { guard let self else {
return return
} }
self.sendMessageContext.presentAttachmentMenu(view: self, subject: .default) self.sendMessageContext.presentAttachmentMenu(view: self, subject: .default)
}, },
attachmentButtonMode: component.slice.effectivePeer.isService ? nil : .attach, attachmentButtonMode: !displayAttachmentAction ? nil : .attach,
myReaction: component.slice.item.storyItem.myReaction.flatMap { value -> MessageInputPanelComponent.MyReaction? in myReaction: component.slice.item.storyItem.myReaction.flatMap { value -> MessageInputPanelComponent.MyReaction? in
var centerAnimation: TelegramMediaFile? var centerAnimation: TelegramMediaFile?
var animationFileId: Int64? var animationFileId: Int64?
@ -3082,7 +3113,7 @@ public final class StoryItemSetContainerComponent: Component {
timeoutValue: nil, timeoutValue: nil,
timeoutSelected: false, timeoutSelected: false,
displayGradient: false, displayGradient: false,
bottomInset: component.inputHeight != 0.0 || inputNodeVisible ? 0.0 : bottomContentInset, bottomInset: max(bottomContentInset, component.inputHeight),
isFormattingLocked: false, isFormattingLocked: false,
hideKeyboard: self.sendMessageContext.currentInputMode == .media, hideKeyboard: self.sendMessageContext.currentInputMode == .media,
customInputView: nil, customInputView: nil,
@ -3176,7 +3207,7 @@ public final class StoryItemSetContainerComponent: Component {
inputPanelIsOverlay = false inputPanelIsOverlay = false
} else { } else {
bottomContentInset += 44.0 bottomContentInset += 44.0
inputPanelBottomInset = inputHeight - inputPanelInset inputPanelBottomInset = inputHeight - inputPanelInset + 3.0
inputPanelIsOverlay = true inputPanelIsOverlay = true
} }
@ -3191,7 +3222,9 @@ public final class StoryItemSetContainerComponent: Component {
var validViewListIds: [StoryId] = [] var validViewListIds: [StoryId] = []
var displayViewLists = false var displayViewLists = false
if component.slice.effectivePeer.id == component.context.account.peerId { if case .liveStream = component.slice.item.storyItem.media {
displayViewLists = false
} else if component.slice.effectivePeer.id == component.context.account.peerId {
displayViewLists = true displayViewLists = true
} else if case let .channel(channel) = component.slice.effectivePeer, channel.flags.contains(.isCreator) || component.slice.additionalPeerData.canViewStats { } else if case let .channel(channel) = component.slice.effectivePeer, channel.flags.contains(.isCreator) || component.slice.additionalPeerData.canViewStats {
displayViewLists = true displayViewLists = true
@ -4028,9 +4061,14 @@ public final class StoryItemSetContainerComponent: Component {
let focusedItem: StoryContentItem? = component.slice.item let focusedItem: StoryContentItem? = component.slice.item
var isLiveStream = false
if case .liveStream = component.slice.item.storyItem.media {
isLiveStream = true
}
var currentLeftInfoItem: InfoItem? var currentLeftInfoItem: InfoItem?
if focusedItem != nil { if focusedItem != nil {
let leftInfoComponent = AnyComponent(StoryAvatarInfoComponent(context: component.context, peer: component.slice.effectivePeer)) let leftInfoComponent = AnyComponent(StoryAvatarInfoComponent(context: component.context, peer: component.slice.effectivePeer, isLiveStream: isLiveStream))
if let leftInfoItem = self.leftInfoItem, leftInfoItem.component == leftInfoComponent { if let leftInfoItem = self.leftInfoItem, leftInfoItem.component == leftInfoComponent {
currentLeftInfoItem = leftInfoItem currentLeftInfoItem = leftInfoItem
} else { } else {
@ -4066,7 +4104,8 @@ public final class StoryItemSetContainerComponent: Component {
author: component.slice.item.storyItem.author, author: component.slice.item.storyItem.author,
timestamp: component.slice.item.storyItem.timestamp, timestamp: component.slice.item.storyItem.timestamp,
counters: counters, counters: counters,
isEdited: component.slice.item.storyItem.isEdited isEdited: component.slice.item.storyItem.isEdited,
isLiveStream: isLiveStream
)) ))
if let centerInfoItem = self.centerInfoItem, centerInfoItem.component == centerInfoComponent { if let centerInfoItem = self.centerInfoItem, centerInfoItem.component == centerInfoComponent {
currentCenterInfoItem = centerInfoItem currentCenterInfoItem = centerInfoItem
@ -4176,7 +4215,13 @@ public final class StoryItemSetContainerComponent: Component {
if let inputPanelSize { if let inputPanelSize {
let inputPanelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - inputPanelSize.width) / 2.0), y: availableSize.height - inputPanelBottomInset - inputPanelSize.height), size: inputPanelSize) let inputPanelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - inputPanelSize.width) / 2.0), y: availableSize.height - inputPanelBottomInset - inputPanelSize.height), size: inputPanelSize)
inputPanelFrameValue = inputPanelFrame inputPanelFrameValue = inputPanelFrame
var inputPanelAlpha: CGFloat = (component.slice.effectivePeer.id == component.context.account.peerId || component.hideUI || self.isEditingStory || component.slice.item.storyItem.isPending) ? 0.0 : 1.0
var inputPanelAlpha: CGFloat = (component.hideUI || self.isEditingStory || component.slice.item.storyItem.isPending) ? 0.0 : 1.0
if case .liveStream = component.slice.item.storyItem.media {
} else if component.slice.effectivePeer.id == component.context.account.peerId {
inputPanelAlpha = 0.0
}
if case .regular = component.metrics.widthClass { if case .regular = component.metrics.widthClass {
inputPanelAlpha *= component.visibilityFraction inputPanelAlpha *= component.visibilityFraction
} }
@ -4465,14 +4510,21 @@ public final class StoryItemSetContainerComponent: Component {
if let current = self.reactionContextNode { if let current = self.reactionContextNode {
reactionContextNode = current reactionContextNode = current
} else { } else {
var reactionAdditionalTitle: String?
if case .liveStream = component.slice.item.storyItem.media {
} else {
reactionAdditionalTitle = self.displayLikeReactions ? nil : (isGroup ? component.strings.Story_SendReactionAsGroupMessage : component.strings.Story_SendReactionAsMessage)
}
reactionContextNodeTransition = .immediate reactionContextNodeTransition = .immediate
reactionContextNode = ReactionContextNode( reactionContextNode = ReactionContextNode(
context: component.context, context: component.context,
animationCache: component.context.animationCache, animationCache: component.context.animationCache,
presentationData: component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme), presentationData: component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme),
style: .glass(isTinted: false),
items: reactionItems.map { ReactionContextItem.reaction(item: $0, icon: .none) }, items: reactionItems.map { ReactionContextItem.reaction(item: $0, icon: .none) },
selectedItems: component.slice.item.storyItem.myReaction.flatMap { Set([$0]) } ?? Set(), selectedItems: component.slice.item.storyItem.myReaction.flatMap { Set([$0]) } ?? Set(),
title: self.displayLikeReactions ? nil : (isGroup ? component.strings.Story_SendReactionAsGroupMessage : component.strings.Story_SendReactionAsMessage), title: reactionAdditionalTitle,
reactionsLocked: false, reactionsLocked: false,
alwaysAllowPremiumReactions: false, alwaysAllowPremiumReactions: false,
allPresetReactionsAreAvailable: false, allPresetReactionsAreAvailable: false,
@ -4982,12 +5034,21 @@ public final class StoryItemSetContainerComponent: Component {
} }
component.externalState.derivedMediaSize = contentFrame.size component.externalState.derivedMediaSize = contentFrame.size
if component.slice.effectivePeer.id == component.context.account.peerId {
component.externalState.derivedBottomInset = availableSize.height - itemsContainerFrame.maxY if case .liveStream = component.slice.item.storyItem.media {
} else if let inputPanelFrameValue { if let inputPanelFrameValue {
component.externalState.derivedBottomInset = availableSize.height - min(inputPanelFrameValue.minY, contentFrame.maxY) component.externalState.derivedBottomInset = availableSize.height - min(inputPanelFrameValue.minY, contentFrame.maxY)
} else {
component.externalState.derivedBottomInset = availableSize.height - itemsContainerFrame.maxY
}
} else { } else {
component.externalState.derivedBottomInset = availableSize.height - itemsContainerFrame.maxY if component.slice.effectivePeer.id == component.context.account.peerId {
component.externalState.derivedBottomInset = availableSize.height - itemsContainerFrame.maxY
} else if let inputPanelFrameValue {
component.externalState.derivedBottomInset = availableSize.height - min(inputPanelFrameValue.minY, contentFrame.maxY)
} else {
component.externalState.derivedBottomInset = availableSize.height - itemsContainerFrame.maxY
}
} }
if !"".isEmpty { if !"".isEmpty {

View file

@ -1054,6 +1054,7 @@ public final class StoryPeerListComponent: Component {
} }
var hasUnseenCloseFriendsItems = itemSet.hasUnseenCloseFriends var hasUnseenCloseFriendsItems = itemSet.hasUnseenCloseFriends
let hasLiveItems = itemSet.hasLiveItems
var hasItems = true var hasItems = true
var itemRingAnimation: StoryPeerListItemComponent.RingAnimation? var itemRingAnimation: StoryPeerListItemComponent.RingAnimation?
@ -1139,6 +1140,7 @@ public final class StoryPeerListComponent: Component {
totalCount: totalCount, totalCount: totalCount,
unseenCount: unseenCount, unseenCount: unseenCount,
hasUnseenCloseFriendsItems: hasUnseenCloseFriendsItems, hasUnseenCloseFriendsItems: hasUnseenCloseFriendsItems,
hasLiveItems: hasLiveItems,
hasItems: hasItems, hasItems: hasItems,
ringAnimation: itemRingAnimation, ringAnimation: itemRingAnimation,
scale: itemScale, scale: itemScale,
@ -1278,6 +1280,7 @@ public final class StoryPeerListComponent: Component {
totalCount: 1, totalCount: 1,
unseenCount: itemSet.unseenCount != 0 ? 1 : 0, unseenCount: itemSet.unseenCount != 0 ? 1 : 0,
hasUnseenCloseFriendsItems: hasUnseenCloseFriendsItems, hasUnseenCloseFriendsItems: hasUnseenCloseFriendsItems,
hasLiveItems: itemSet.hasLiveItems,
hasItems: hasItems, hasItems: hasItems,
ringAnimation: itemRingAnimation, ringAnimation: itemRingAnimation,
scale: itemScale, scale: itemScale,

View file

@ -373,6 +373,7 @@ public final class StoryPeerListItemComponent: Component {
public let totalCount: Int public let totalCount: Int
public let unseenCount: Int public let unseenCount: Int
public let hasUnseenCloseFriendsItems: Bool public let hasUnseenCloseFriendsItems: Bool
public let hasLiveItems: Bool
public let hasItems: Bool public let hasItems: Bool
public let ringAnimation: RingAnimation? public let ringAnimation: RingAnimation?
public let scale: CGFloat public let scale: CGFloat
@ -393,6 +394,7 @@ public final class StoryPeerListItemComponent: Component {
totalCount: Int, totalCount: Int,
unseenCount: Int, unseenCount: Int,
hasUnseenCloseFriendsItems: Bool, hasUnseenCloseFriendsItems: Bool,
hasLiveItems: Bool,
hasItems: Bool, hasItems: Bool,
ringAnimation: RingAnimation?, ringAnimation: RingAnimation?,
scale: CGFloat, scale: CGFloat,
@ -412,6 +414,7 @@ public final class StoryPeerListItemComponent: Component {
self.totalCount = totalCount self.totalCount = totalCount
self.unseenCount = unseenCount self.unseenCount = unseenCount
self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems
self.hasLiveItems = hasLiveItems
self.hasItems = hasItems self.hasItems = hasItems
self.ringAnimation = ringAnimation self.ringAnimation = ringAnimation
self.scale = scale self.scale = scale
@ -447,6 +450,9 @@ public final class StoryPeerListItemComponent: Component {
if lhs.hasUnseenCloseFriendsItems != rhs.hasUnseenCloseFriendsItems { if lhs.hasUnseenCloseFriendsItems != rhs.hasUnseenCloseFriendsItems {
return false return false
} }
if lhs.hasLiveItems != rhs.hasLiveItems {
return false
}
if lhs.hasItems != rhs.hasItems { if lhs.hasItems != rhs.hasItems {
return false return false
} }
@ -494,6 +500,9 @@ public final class StoryPeerListItemComponent: Component {
private let avatarBackgroundView: UIImageView private let avatarBackgroundView: UIImageView
private var avatarNode: AvatarNode? private var avatarNode: AvatarNode?
private var avatarAddBadgeView: UIImageView? private var avatarAddBadgeView: UIImageView?
private var avatarLiveBadgeView: UIImageView?
private var avatarLiveBadgeMaskSeenLayer: SimpleLayer?
private var avatarLiveBadgeMaskUnseenLayer: SimpleLayer?
private let avatarShapeLayer: SimpleShapeLayer private let avatarShapeLayer: SimpleShapeLayer
private let indicatorMaskSeenLayer: SimpleLayer private let indicatorMaskSeenLayer: SimpleLayer
private let indicatorMaskUnseenLayer: SimpleLayer private let indicatorMaskUnseenLayer: SimpleLayer
@ -551,6 +560,8 @@ public final class StoryPeerListItemComponent: Component {
super.init(frame: frame) super.init(frame: frame)
self.layer.allowsGroupOpacity = true
self.extractedContainerNode.contentNode.view.addSubview(self.extractedBackgroundView) self.extractedContainerNode.contentNode.view.addSubview(self.extractedBackgroundView)
self.containerNode.addSubnode(self.extractedContainerNode) self.containerNode.addSubnode(self.extractedContainerNode)
@ -568,6 +579,15 @@ public final class StoryPeerListItemComponent: Component {
self.avatarContent.layer.addSublayer(self.indicatorColorSeenLayer) self.avatarContent.layer.addSublayer(self.indicatorColorSeenLayer)
self.avatarContent.layer.addSublayer(self.indicatorColorUnseenLayer) self.avatarContent.layer.addSublayer(self.indicatorColorUnseenLayer)
if let filter = CALayer.luminanceToAlpha() {
self.indicatorMaskSeenLayer.filters = [filter]
self.indicatorMaskUnseenLayer.filters = [filter]
}
self.indicatorMaskSeenLayer.backgroundColor = UIColor.black.cgColor
self.indicatorMaskUnseenLayer.backgroundColor = UIColor.black.cgColor
self.indicatorMaskSeenLayer.addSublayer(self.indicatorShapeSeenLayer) self.indicatorMaskSeenLayer.addSublayer(self.indicatorShapeSeenLayer)
self.indicatorMaskUnseenLayer.addSublayer(self.indicatorShapeUnseenLayer) self.indicatorMaskUnseenLayer.addSublayer(self.indicatorShapeUnseenLayer)
self.indicatorColorSeenLayer.mask = self.indicatorMaskSeenLayer self.indicatorColorSeenLayer.mask = self.indicatorMaskSeenLayer
@ -772,6 +792,79 @@ public final class StoryPeerListItemComponent: Component {
} }
} }
if component.hasLiveItems {
let avatarLiveBadgeView: UIImageView
var avatarLiveBadgeTransition = transition
if let current = self.avatarLiveBadgeView {
avatarLiveBadgeView = current
} else {
avatarLiveBadgeTransition = avatarLiveBadgeTransition.withAnimation(.none)
avatarLiveBadgeView = UIImageView()
self.avatarLiveBadgeView = avatarLiveBadgeView
self.avatarContainer.addSubview(avatarLiveBadgeView)
}
let avatarLiveBadgeMaskSeenLayer: SimpleLayer
if let current = self.avatarLiveBadgeMaskSeenLayer {
avatarLiveBadgeMaskSeenLayer = current
} else {
avatarLiveBadgeMaskSeenLayer = SimpleLayer()
avatarLiveBadgeMaskSeenLayer.backgroundColor = UIColor.black.cgColor
self.avatarLiveBadgeMaskSeenLayer = avatarLiveBadgeMaskSeenLayer
self.indicatorMaskSeenLayer.addSublayer(avatarLiveBadgeMaskSeenLayer)
}
let avatarLiveBadgeMaskUnseenLayer: SimpleLayer
if let current = self.avatarLiveBadgeMaskUnseenLayer {
avatarLiveBadgeMaskUnseenLayer = current
} else {
avatarLiveBadgeMaskUnseenLayer = SimpleLayer()
avatarLiveBadgeMaskUnseenLayer.backgroundColor = UIColor.black.cgColor
self.avatarLiveBadgeMaskUnseenLayer = avatarLiveBadgeMaskUnseenLayer
self.indicatorMaskUnseenLayer.addSublayer(avatarLiveBadgeMaskUnseenLayer)
}
if avatarLiveBadgeView.image == nil || themeUpdated {
//TODO:localize
let liveString = NSAttributedString(string: "LIVE", font: Font.semibold(10.0), textColor: .white)
let liveStringBounds = liveString.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
let liveBadgeSize = CGSize(width: ceil(liveStringBounds.width) + 4.0 * 2.0, height: ceil(liveStringBounds.height) + 2.0 * 2.0)
avatarLiveBadgeView.image = generateImage(liveBadgeSize, rotatedContext: { size, context in
UIGraphicsPushContext(context)
defer {
UIGraphicsPopContext()
}
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor(rgb: 0xFF2D55).cgColor)
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: size.height * 0.5).cgPath)
context.fillPath()
liveString.draw(at: CGPoint(x: floorToScreenPixels((size.width - liveStringBounds.width) * 0.5), y: floorToScreenPixels((size.height - liveStringBounds.height) * 0.5)))
})
}
if let image = avatarLiveBadgeView.image {
let badgeSize = image.size
let badgeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((avatarFrame.width - badgeSize.width) * 0.5), y: avatarFrame.height + 5.0 - badgeSize.height), size: badgeSize)
avatarLiveBadgeTransition.setFrame(view: avatarLiveBadgeView, frame: badgeFrame)
avatarLiveBadgeMaskSeenLayer.frame = badgeFrame.offsetBy(dx: 8.0, dy: 8.0).insetBy(dx: -2.0, dy: -2.0)
avatarLiveBadgeMaskSeenLayer.cornerRadius = avatarLiveBadgeMaskSeenLayer.bounds.height * 0.5
avatarLiveBadgeMaskUnseenLayer.frame = badgeFrame.offsetBy(dx: 8.0, dy: 8.0).insetBy(dx: -2.0, dy: -2.0)
avatarLiveBadgeMaskUnseenLayer.cornerRadius = avatarLiveBadgeMaskUnseenLayer.bounds.height * 0.5
}
} else {
if let avatarLiveBadgeView = self.avatarLiveBadgeView {
self.avatarLiveBadgeView = nil
avatarLiveBadgeView.removeFromSuperview()
}
if let avatarLiveBadgeMaskSeenLayer = self.avatarLiveBadgeMaskSeenLayer {
self.avatarLiveBadgeMaskSeenLayer = nil
avatarLiveBadgeMaskSeenLayer.removeFromSuperlayer()
}
if let avatarLiveBadgeMaskUnseenLayer = self.avatarLiveBadgeMaskUnseenLayer {
self.avatarLiveBadgeMaskUnseenLayer = nil
avatarLiveBadgeMaskUnseenLayer.removeFromSuperlayer()
}
}
self.avatarBackgroundView.isHidden = component.ringAnimation != nil || self.indicatorColorSeenLayer.isHidden self.avatarBackgroundView.isHidden = component.ringAnimation != nil || self.indicatorColorSeenLayer.isHidden
let baseRadius: CGFloat = 30.66 let baseRadius: CGFloat = 30.66
@ -788,7 +881,10 @@ public final class StoryPeerListItemComponent: Component {
let seenColors: [CGColor] let seenColors: [CGColor]
let unseenColors: [CGColor] let unseenColors: [CGColor]
if component.hasUnseenCloseFriendsItems { if component.hasLiveItems {
//TODO:localize
unseenColors = [UIColor(rgb: 0xFF3777).cgColor, UIColor(rgb: 0xFF2D55).cgColor]
} else if component.hasUnseenCloseFriendsItems {
unseenColors = [component.theme.chatList.storyUnseenPrivateColors.topColor.cgColor, component.theme.chatList.storyUnseenPrivateColors.bottomColor.cgColor] unseenColors = [component.theme.chatList.storyUnseenPrivateColors.topColor.cgColor, component.theme.chatList.storyUnseenPrivateColors.bottomColor.cgColor]
} else { } else {
unseenColors = [component.theme.chatList.storyUnseenColors.topColor.cgColor, component.theme.chatList.storyUnseenColors.bottomColor.cgColor] unseenColors = [component.theme.chatList.storyUnseenColors.topColor.cgColor, component.theme.chatList.storyUnseenColors.bottomColor.cgColor]
@ -831,7 +927,9 @@ public final class StoryPeerListItemComponent: Component {
let avatarPath = CGMutablePath() let avatarPath = CGMutablePath()
avatarPath.addEllipse(in: CGRect(origin: CGPoint(), size: avatarSize).insetBy(dx: -1.0, dy: -1.0)) avatarPath.addEllipse(in: CGRect(origin: CGPoint(), size: avatarSize).insetBy(dx: -1.0, dy: -1.0))
if component.peer.id == component.context.account.peerId && !component.hasItems && component.ringAnimation == nil { if let avatarLiveBadgeView = self.avatarLiveBadgeView {
avatarPath.addPath(UIBezierPath(roundedRect: avatarLiveBadgeView.frame.insetBy(dx: -2.0, dy: -2.0), cornerRadius: avatarLiveBadgeView.bounds.height * 0.5).cgPath)
} else if component.peer.id == component.context.account.peerId && !component.hasItems && component.ringAnimation == nil {
let cutoutSize: CGFloat = 18.0 + UIScreenPixel * 2.0 let cutoutSize: CGFloat = 18.0 + UIScreenPixel * 2.0
avatarPath.addEllipse(in: CGRect(origin: CGPoint(x: avatarSize.width - cutoutSize + UIScreenPixel, y: avatarSize.height - 1.0 - cutoutSize + UIScreenPixel), size: CGSize(width: cutoutSize, height: cutoutSize))) avatarPath.addEllipse(in: CGRect(origin: CGPoint(x: avatarSize.width - cutoutSize + UIScreenPixel, y: avatarSize.height - 1.0 - cutoutSize + UIScreenPixel), size: CGSize(width: cutoutSize, height: cutoutSize)))
} else if let mappedLeftCenter { } else if let mappedLeftCenter {
@ -839,8 +937,8 @@ public final class StoryPeerListItemComponent: Component {
} }
ComponentTransition.immediate.setShapeLayerPath(layer: self.avatarShapeLayer, path: avatarPath) ComponentTransition.immediate.setShapeLayerPath(layer: self.avatarShapeLayer, path: avatarPath)
ComponentTransition.immediate.setShapeLayerPath(layer: self.indicatorShapeSeenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.totalCount, unseenCount: component.unseenCount, isSeen: true, segmentFraction: component.expandedAlphaFraction, rotationFraction: component.expandEffectFraction)) ComponentTransition.immediate.setShapeLayerPath(layer: self.indicatorShapeSeenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.hasLiveItems ? 1 : component.totalCount, unseenCount: component.hasLiveItems ? 1 : component.unseenCount, isSeen: true, segmentFraction: component.expandedAlphaFraction, rotationFraction: component.expandEffectFraction))
ComponentTransition.immediate.setShapeLayerPath(layer: self.indicatorShapeUnseenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.totalCount, unseenCount: component.unseenCount, isSeen: false, segmentFraction: component.expandedAlphaFraction, rotationFraction: component.expandEffectFraction)) ComponentTransition.immediate.setShapeLayerPath(layer: self.indicatorShapeUnseenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.hasLiveItems ? 1 : component.totalCount, unseenCount: component.hasLiveItems ? 1 : component.unseenCount, isSeen: false, segmentFraction: component.expandedAlphaFraction, rotationFraction: component.expandEffectFraction))
let titleString: String let titleString: String
if component.peer.id == component.context.account.peerId { if component.peer.id == component.context.account.peerId {
@ -899,7 +997,7 @@ public final class StoryPeerListItemComponent: Component {
let titleSize = self.title.update( let titleSize = self.title.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: titleString, font: Font.regular(11.0), textColor: (component.unseenCount != 0 || component.peer.id == component.context.account.peerId) ? component.theme.list.itemPrimaryTextColor : component.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.5))), text: .plain(NSAttributedString(string: titleString, font: Font.regular(11.0), textColor: (component.unseenCount != 0 || component.hasLiveItems || component.peer.id == component.context.account.peerId) ? component.theme.list.itemPrimaryTextColor : component.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.5))),
maximumNumberOfLines: 1 maximumNumberOfLines: 1
)), )),
environment: {}, environment: {},

View file

@ -264,7 +264,8 @@ extension ChatControllerImpl {
return AvatarNode.StoryStats( return AvatarNode.StoryStats(
totalCount: storyStats.totalCount, totalCount: storyStats.totalCount,
unseenCount: storyStats.unseenCount, unseenCount: storyStats.unseenCount,
hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends,
hasLiveItems: storyStats.hasLiveItems
) )
}, presentationParams: AvatarNode.StoryPresentationParams( }, presentationParams: AvatarNode.StoryPresentationParams(
colors: AvatarNode.Colors(theme: self.presentationData.theme), colors: AvatarNode.Colors(theme: self.presentationData.theme),

View file

@ -2446,6 +2446,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
let navigateButtonsSize = self.navigateButtons.updateLayout(transition: transition) let navigateButtonsSize = self.navigateButtons.updateLayout(transition: transition)
var navigateButtonsFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - navigateButtonsSize.width - 8.0, y: layout.size.height - containerInsets.bottom - inputPanelsHeight - navigateButtonsSize.height - 20.0), size: navigateButtonsSize) var navigateButtonsFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - navigateButtonsSize.width - 8.0, y: layout.size.height - containerInsets.bottom - inputPanelsHeight - navigateButtonsSize.height - 20.0), size: navigateButtonsSize)
if containerInsets.bottom <= 32.0 {
navigateButtonsFrame.origin.x -= 18.0
}
if case .overlay = self.chatPresentationInterfaceState.mode { if case .overlay = self.chatPresentationInterfaceState.mode {
navigateButtonsFrame = navigateButtonsFrame.offsetBy(dx: -8.0, dy: -8.0) navigateButtonsFrame = navigateButtonsFrame.offsetBy(dx: -8.0, dy: -8.0)
} }

View file

@ -3967,6 +3967,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
completion: completion completion: completion
) )
} }
public func makeChannelMembersSearchController(params: ChannelMembersSearchControllerParams) -> ChannelMembersSearchController {
return ChannelMembersSearchControllerImpl(params: params)
}
} }
private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? { private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {

View file

@ -726,6 +726,12 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
} }
if let media { if let media {
#if DEBUG
if "".isEmpty {
let _ = context.engine.messages.beginStoryLivestream().startStandalone()
}
#endif
let _ = (context.engine.messages.uploadStory( let _ = (context.engine.messages.uploadStory(
target: target, target: target,
media: media, media: media,

View file

@ -930,6 +930,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
component: AnyComponent(AvatarStoryIndicatorComponent( component: AnyComponent(AvatarStoryIndicatorComponent(
hasUnseen: true, hasUnseen: true,
hasUnseenCloseFriendsItems: false, hasUnseenCloseFriendsItems: false,
hasLiveItems: false,
colors: AvatarStoryIndicatorComponent.Colors(theme: defaultDarkPresentationTheme), colors: AvatarStoryIndicatorComponent.Colors(theme: defaultDarkPresentationTheme),
activeLineWidth: 1.0 + UIScreenPixel, activeLineWidth: 1.0 + UIScreenPixel,
inactiveLineWidth: 1.0 + UIScreenPixel, inactiveLineWidth: 1.0 + UIScreenPixel,