diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index f4f6fccb82..dd0814a390 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1134,6 +1134,51 @@ public enum StarsWithdrawalScreenSubject { 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)? + 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)? = 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 { var sharedContainerPath: 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 makeBirthdayAcceptSuggestionScreen(context: AccountContext, birthday: TelegramBirthday, settings: Promise, openSettings: @escaping () -> Void, completion: @escaping (TelegramBirthday) -> Void) -> ViewController + func makeChannelMembersSearchController(params: ChannelMembersSearchControllerParams) -> ChannelMembersSearchController + func makeDebugSettingsController(context: AccountContext?) -> ViewController? func openCreateGroupCallUI(context: AccountContext, peerIds: [EnginePeer.Id], parentController: ViewController) diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index 7d329e142d..650588aa15 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -1156,6 +1156,11 @@ public final class AvatarNode: ASDisplayNode { public let contentNode: ContentNode private var storyIndicator: ComponentView? + 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? private var loadingStatuses = Bag() @@ -1164,22 +1169,26 @@ public final class AvatarNode: ASDisplayNode { public var totalCount: Int public var unseenCount: Int public var hasUnseenCloseFriendsItems: Bool + public var hasLiveItems: Bool public var progress: Float? public init( totalCount: Int, unseenCount: Int, hasUnseenCloseFriendsItems: Bool, + hasLiveItems: Bool, progress: Float? = nil ) { self.totalCount = totalCount self.unseenCount = unseenCount self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems + self.hasLiveItems = hasLiveItems self.progress = progress } } public private(set) var storyStats: StoryStats? + public var displayLiveBadge: Bool = false public var font: UIFont { get { @@ -1459,6 +1468,7 @@ public final class AvatarNode: ASDisplayNode { component: AnyComponent(AvatarStoryIndicatorComponent( hasUnseen: storyStats.unseenCount != 0, hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriendsItems, + hasLiveItems: storyStats.hasLiveItems, colors: AvatarStoryIndicatorComponent.Colors( unseenColors: storyPresentationParams.colors.unseenColors, 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() { diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index cea2cd220e..8c0c2d688b 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -2251,7 +2251,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController stats: EngineChatList.StoryStats( totalCount: rawStoryArchiveSubscriptions.items.count, unseenCount: unseenCount, - hasUnseenCloseFriends: hasUnseenCloseFriends + hasUnseenCloseFriends: hasUnseenCloseFriends, + hasLiveItems: false ), hasUnseenCloseFriends: hasUnseenCloseFriends ) diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index be628a147f..c3854ee848 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -367,7 +367,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable { animationCache: animationCache, animationRenderer: animationRenderer, 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 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 - return (stats.totalCount, stats.unseenCount, stats.hasUnseenCloseFriends) + return (stats.totalCount, stats.unseenCount, stats.hasUnseenCloseFriends, stats.hasLiveItems) }, openStories: { itemPeer, sourceNode in guard case let .peer(_, chatPeer) = itemPeer, let peer = chatPeer else { return @@ -1002,7 +1002,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { } } }, 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 guard case let .peer(_, chatPeer) = itemPeer, let peer = chatPeer else { return @@ -1080,7 +1080,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { peerContextAction(EnginePeer(peer.peer), .search(nil), node, gesture, location) } }, 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 guard case let .peer(_, chatPeer) = itemPeer, let peer = chatPeer else { return @@ -1205,7 +1205,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable { stats: EngineChatList.StoryStats( totalCount: stats.totalCount, unseenCount: stats.unseenCount, - hasUnseenCloseFriends: stats.hasUnseenCloseFriends + hasUnseenCloseFriends: stats.hasUnseenCloseFriends, + hasLiveItems: stats.hasLiveItems ), hasUnseenCloseFriends: stats.hasUnseenCloseFriends ) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 933db76461..43ccb3faf1 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -1601,6 +1601,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { self.avatarContainerNode = ASDisplayNode() self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) + self.avatarNode.displayLiveBadge = true self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true @@ -1791,7 +1792,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { return AvatarNode.StoryStats( totalCount: storyState.stats.totalCount, unseenCount: storyState.stats.unseenCount, - hasUnseenCloseFriendsItems: storyState.hasUnseenCloseFriends + hasUnseenCloseFriendsItems: storyState.hasUnseenCloseFriends, + hasLiveItems: storyState.stats.hasLiveItems ) }, presentationParams: AvatarNode.StoryPresentationParams( colors: AvatarNode.Colors(theme: item.presentationData.theme), diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index 40b0a51afa..ba104d746a 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -207,7 +207,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { let arrowAction: (() -> Void)? let animationCache: AnimationCache? 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 adButtonAction: ((ASDisplayNode) -> Void)? let visibilityUpdated: ((Bool) -> Void)? @@ -253,7 +253,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, arrowAction: (() -> Void)? = nil, animationCache: AnimationCache? = 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, adButtonAction: ((ASDisplayNode) -> Void)? = nil, visibilityUpdated: ((Bool) -> Void)? = nil @@ -1286,7 +1286,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { return AvatarNode.StoryStats( totalCount: stats.total, unseenCount: stats.unseen, - hasUnseenCloseFriendsItems: stats.hasUnseenCloseFriends + hasUnseenCloseFriendsItems: stats.hasUnseenCloseFriends, + hasLiveItems: stats.hasLiveItems ) }, presentationParams: AvatarNode.StoryPresentationParams( diff --git a/submodules/Display/Source/WindowContent.swift b/submodules/Display/Source/WindowContent.swift index cdab3a0df7..9e22491aea 100644 --- a/submodules/Display/Source/WindowContent.swift +++ b/submodules/Display/Source/WindowContent.swift @@ -1233,6 +1233,9 @@ public class Window1 { let updatedInputOffset = inputHeightOffsetForLayout(self.windowLayout) if !previousInputOffset.isEqual(to: updatedInputOffset) { 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 if let strongSelf = self, hide { 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 { + let springDuration: CGFloat + if #available(iOS 26.0, *) { + springDuration = 0.3832 + } else { + springDuration = 0.25 + } 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 { self.updateLayout { diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index 0f4cf342a6..ced811977e 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -1749,7 +1749,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo return AvatarNode.StoryStats( totalCount: storyStats.totalCount, unseenCount: storyStats.unseenCount, - hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends + hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends, + hasLiveItems: storyStats.hasLiveItems ) }, presentationParams: AvatarNode.StoryPresentationParams( colors: AvatarNode.Colors(theme: item.presentationData.theme), diff --git a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift index 357e95098c..f60934640f 100644 --- a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift +++ b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift @@ -65,7 +65,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent if let credentialsPromise = credentialsPromise { credentialsSignal = credentialsPromise.get() } else { - credentialsSignal = context.engine.calls.getGroupCallStreamCredentials(peerId: peerId, revokePreviousCredentials: false) + credentialsSignal = context.engine.calls.getGroupCallStreamCredentials(peerId: peerId, isLiveStream: false, revokePreviousCredentials: false) |> `catch` { _ -> Signal in return .never() } diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift index 2289970d80..feea2bc7c1 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift @@ -754,7 +754,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation |> deliverOnMainQueue).start(next: { peerView in updateState { current in 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?() let presentationData = context.sharedContext.currentPresentationData.with { $0 } 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 }, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership)) - }) + })) dismissController = { [weak controller] in controller?.dismiss() } diff --git a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift index 6ee7bdeeb8..2963312711 100644 --- a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift @@ -300,7 +300,7 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat } }, addPeer: { 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 { let presentationData = context.sharedContext.currentPresentationData.with { $0 } switch participant.participant { @@ -329,7 +329,7 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat dismissController?() })) }) - }) + })) dismissController = { [weak controller] in controller?.dismiss() } diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift index 46e0fc4489..bd022fb754 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift @@ -7,20 +7,7 @@ import TelegramPresentationData import AccountContext import SearchUI -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 ChannelMembersSearchController: ViewController { +public final class ChannelMembersSearchControllerImpl: ViewController, ChannelMembersSearchController { private let queue = Queue() private let context: AccountContext @@ -43,15 +30,15 @@ public final class ChannelMembersSearchController: ViewController { private var searchContentNode: NavigationBarSearchContentNode? - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id, forceTheme: PresentationTheme? = nil, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter] = [], openPeer: @escaping (EnginePeer, RenderedChannelParticipant?) -> Void) { - self.context = context - self.peerId = peerId - self.mode = mode - self.openPeer = openPeer - self.filters = filters - self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - self.forceTheme = forceTheme - if let forceTheme = forceTheme { + public init(params: ChannelMembersSearchControllerParams) { + self.context = params.context + self.peerId = params.peerId + self.mode = params.mode + self.openPeer = params.openPeer + self.filters = params.filters + self.presentationData = params.updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + self.forceTheme = params.forceTheme + if let forceTheme = params.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.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) + self.presentationDataDisposable = ((params.updatedPresentationData?.signal ?? params.context.sharedContext.presentationData) |> deliverOnMainQueue).start(next: { [weak self] presentationData in guard let strongSelf = self else { return @@ -88,7 +75,7 @@ public final class ChannelMembersSearchController: ViewController { strongSelf.controllerNode.updatePresentationData(presentationData) }) - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (params.context.account.postbox.loadedPeerWithId(peerId) |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self { diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index 814f2d52da..4e1bbc8d26 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -1027,7 +1027,7 @@ public func channelPermissionsController(context: AccountContext, updatedPresent |> take(1) |> deliverOnMainQueue).start(next: { peerId, _ in 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 { let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } switch participant.participant { @@ -1048,7 +1048,7 @@ public func channelPermissionsController(context: AccountContext, updatedPresent upgradedToSupergroupImpl?(upgradedPeerId, f) }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }) - }) + })) dismissController = { [weak controller] in controller?.dismiss() } diff --git a/submodules/Postbox/Sources/ChatListView.swift b/submodules/Postbox/Sources/ChatListView.swift index 61ea52324b..7d94bdf90f 100644 --- a/submodules/Postbox/Sources/ChatListView.swift +++ b/submodules/Postbox/Sources/ChatListView.swift @@ -282,11 +282,13 @@ public struct PeerStoryStats: Equatable { public var totalCount: Int public var unseenCount: Int 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.unseenCount = unseenCount self.hasUnseenCloseFriends = hasUnseenCloseFriends + self.hasLiveItems = hasLiveItems } } @@ -305,9 +307,9 @@ func fetchPeerStoryStats(postbox: PostboxImpl, peerId: PeerId) -> PeerStoryStats if topItems.isExact { 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 { - 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) } } diff --git a/submodules/Postbox/Sources/StoryItemsTable.swift b/submodules/Postbox/Sources/StoryItemsTable.swift index f7b4a3770a..5664c6a0cc 100644 --- a/submodules/Postbox/Sources/StoryItemsTable.swift +++ b/submodules/Postbox/Sources/StoryItemsTable.swift @@ -5,17 +5,20 @@ public final class StoryItemsTableEntry: Equatable { public let id: Int32 public let expirationTimestamp: Int32? public let isCloseFriends: Bool + public let isLiveStream: Bool public init( value: CodableEntry, id: Int32, expirationTimestamp: Int32?, - isCloseFriends: Bool + isCloseFriends: Bool, + isLiveStream: Bool ) { self.value = value self.id = id self.expirationTimestamp = expirationTimestamp self.isCloseFriends = isCloseFriends + self.isLiveStream = isLiveStream } public static func ==(lhs: StoryItemsTableEntry, rhs: StoryItemsTableEntry) -> Bool { @@ -34,6 +37,9 @@ public final class StoryItemsTableEntry: Equatable { if lhs.isCloseFriends != rhs.isCloseFriends { return false } + if lhs.isLiveStream != rhs.isLiveStream { + return false + } return true } } @@ -139,10 +145,11 @@ final class StoryItemsTable: Table { 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 unseen = 0 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 let id = key.getInt32(8) @@ -152,6 +159,7 @@ final class StoryItemsTable: Table { unseen += 1 var isCloseFriends = false + var isLiveStream = false let readBuffer = ReadBuffer(data: value.makeData()) var magic: UInt32 = 0 readBuffer.read(&magic, offset: 0, length: 4) @@ -168,6 +176,7 @@ final class StoryItemsTable: Table { var flags: UInt8 = 0 readBuffer.read(&flags, offset: 0, length: 1) isCloseFriends = (flags & (1 << 0)) != 0 + isLiveStream = (flags & (1 << 1)) != 0 } } } else { @@ -180,12 +189,15 @@ final class StoryItemsTable: Table { if isCloseFriends { hasUnseenCloseFriends = true } + if isLiveStream { + hasLiveItems = true + } } return true }, limit: 10000) - return (total, unseen, hasUnseenCloseFriends) + return (total, unseen, hasUnseenCloseFriends, hasLiveItems) } public func get(peerId: PeerId) -> [StoryItemsTableEntry] { @@ -199,6 +211,7 @@ final class StoryItemsTable: Table { let entry: CodableEntry var expirationTimestamp: Int32? var isCloseFriends = false + var isLiveStream = false let readBuffer = ReadBuffer(data: value.makeData()) var magic: UInt32 = 0 @@ -234,6 +247,7 @@ final class StoryItemsTable: Table { var flags: UInt8 = 0 readBuffer.read(&flags, offset: 0, length: 1) isCloseFriends = (flags & (1 << 0)) != 0 + isLiveStream = (flags & (1 << 1)) != 0 } } } else { @@ -245,7 +259,7 @@ final class StoryItemsTable: Table { 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 }, limit: 10000) @@ -386,6 +400,9 @@ final class StoryItemsTable: Table { if entry.isCloseFriends { flags |= (1 << 0) } + if entry.isLiveStream { + flags |= (1 << 1) + } buffer.write(&flags, length: 1) self.valueBox.set(self.table, key: self.key(Key(peerId: peerId, id: entry.id)), value: buffer.readBufferNoCopy()) diff --git a/submodules/PremiumUI/Sources/StoriesPageComponent.swift b/submodules/PremiumUI/Sources/StoriesPageComponent.swift index 83e1f3cb01..9ec5754cea 100644 --- a/submodules/PremiumUI/Sources/StoriesPageComponent.swift +++ b/submodules/PremiumUI/Sources/StoriesPageComponent.swift @@ -82,6 +82,7 @@ private final class AvatarComponent: Component { AvatarStoryIndicatorComponent( hasUnseen: true, hasUnseenCloseFriendsItems: false, + hasLiveItems: false, colors: AvatarStoryIndicatorComponent.Colors(unseenColors: colors, unseenCloseFriendsColors: colors, seenColors: colors), activeLineWidth: 3.0, inactiveLineWidth: 3.0, diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift index e2ec36e6ab..f05830b9da 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift @@ -39,6 +39,14 @@ private func generateBubbleShadowImage(shadow: UIColor, diameter: CGFloat, shado final class ReactionContextBackgroundNode: ASDisplayNode { + struct GlassParams { + var isTinted: Bool + + init(isTinted: Bool) { + self.isTinted = isTinted + } + } + private let largeCircleSize: CGFloat private let smallCircleSize: CGFloat @@ -59,14 +67,16 @@ final class ReactionContextBackgroundNode: ASDisplayNode { private let smallCircleLayer: SimpleLayer private let smallCircleShadowLayer: SimpleLayer + private let glass: GlassParams? 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.smallCircleSize = smallCircleSize self.backgroundView = BlurredBackgroundView(color: nil, enableBlur: true) - if glass { + if glass != nil { self.glassBackgroundView = GlassBackgroundView() } else { self.glassBackgroundView = nil @@ -237,7 +247,13 @@ final class ReactionContextBackgroundNode: ASDisplayNode { var glassBackgroundFrame = contentBounds.insetBy(dx: 10.0, dy: 10.0) glassBackgroundFrame.size.height -= 8.0 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)) } else { diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index 96d968d7cf..4b752d3e6a 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -489,7 +489,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { public enum Style { case legacy - case glass + case glass(isTinted: Bool) } public init(context: AccountContext, animationCache: AnimationCache, presentationData: PresentationData, style: Style = .legacy, items: [ReactionContextItem], selectedItems: Set, title: String? = nil, reactionsLocked: Bool, alwaysAllowPremiumReactions: Bool, allPresetReactionsAreAvailable: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?, 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.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.backgroundColor = .black self.rightBackgroundMaskNode = ASDisplayNode() diff --git a/submodules/StatisticsUI/Sources/StatsMessageItem.swift b/submodules/StatisticsUI/Sources/StatsMessageItem.swift index 564c4c2d06..1a2bf60b6a 100644 --- a/submodules/StatisticsUI/Sources/StatsMessageItem.swift +++ b/submodules/StatisticsUI/Sources/StatsMessageItem.swift @@ -661,6 +661,7 @@ final class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { component: AnyComponent(AvatarStoryIndicatorComponent( hasUnseen: true, hasUnseenCloseFriendsItems: false, + hasLiveItems: false, colors: AvatarStoryIndicatorComponent.Colors( unseenColors: item.presentationData.theme.chatList.storyUnseenColors.array, unseenCloseFriendsColors: item.presentationData.theme.chatList.storyUnseenPrivateColors.array, diff --git a/submodules/StatisticsUI/Sources/StoryIconNode.swift b/submodules/StatisticsUI/Sources/StoryIconNode.swift index bcde70961d..7fec70ab34 100644 --- a/submodules/StatisticsUI/Sources/StoryIconNode.swift +++ b/submodules/StatisticsUI/Sources/StoryIconNode.swift @@ -70,6 +70,7 @@ final class StoryIconNode: ASDisplayNode { component: AnyComponent(AvatarStoryIndicatorComponent( hasUnseen: true, hasUnseenCloseFriendsItems: false, + hasLiveItems: false, colors: AvatarStoryIndicatorComponent.Colors( unseenColors: theme.chatList.storyUnseenColors.array, unseenCloseFriendsColors: theme.chatList.storyUnseenPrivateColors.array, diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index f646f57908..231ff8d621 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -303,7 +303,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-29248689] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) } dict[1429932961] = { return Api.GroupCall.parse_groupCall($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[-592373577] = { return Api.GroupCallParticipantVideoSourceGroup.parse_groupCallParticipantVideoSourceGroup($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[-1562241884] = { return Api.InputCollectible.parse_inputCollectiblePhone($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[1684014375] = { return Api.InputDialogPeer.parse_inputDialogPeerFolder($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[1235637404] = { return Api.MediaArea.parse_mediaAreaWeather($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[2055212554] = { return Api.Message.parse_messageService($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[-1618676578] = { return Api.MessageMedia.parse_messageMediaUnsupported($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[-1938180548] = { return Api.MessagePeerReaction.parse_messagePeerReaction($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[422972864] = { return Api.Update.parse_updateFolderPeers($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[192428418] = { return Api.Update.parse_updateGroupCallConnection($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[1763610706] = { return Api.Update.parse_updateInlineBotCallbackQuery($0) } dict[1442983757] = { return Api.Update.parse_updateLangPack($0) } diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index 07bdc0774d..89438d58cd 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -60,15 +60,15 @@ public extension Api { } public extension Api { 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 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) { 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 { - buffer.appendInt32(-1743401272) + buffer.appendInt32(-1188071729) } serializeInt32(flags, 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 << 6) != 0 {serializeInt64(paidMessageStars!, buffer: buffer, boxed: false)} 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 case .messageEmpty(let flags, let id, let peerId): if boxed { @@ -138,8 +139,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { 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): - 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)]) + 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), ("scheduleRepeatPeriod", scheduleRepeatPeriod as Any)]) case .messageEmpty(let flags, let id, let peerId): return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)]) case .messageService(let flags, let id, let fromId, let peerId, let 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() { _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 _c2 = _2 != nil let _c3 = _3 != nil @@ -267,8 +270,9 @@ public extension Api { let _c29 = (Int(_2!) & Int(1 << 5) == 0) || _29 != nil let _c30 = (Int(_2!) & Int(1 << 6) == 0) || _30 != 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 { - 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) + let _c32 = (Int(_2!) & Int(1 << 10) == 0) || _32 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 { + 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 { return nil diff --git a/submodules/TelegramApi/Sources/Api16.swift b/submodules/TelegramApi/Sources/Api16.swift index 8638c17fac..5c44d32f1e 100644 --- a/submodules/TelegramApi/Sources/Api16.swift +++ b/submodules/TelegramApi/Sources/Api16.swift @@ -725,6 +725,7 @@ public extension Api { case messageMediaToDo(flags: Int32, todo: Api.TodoList, completions: [Api.TodoCompletion]?) case messageMediaUnsupported 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) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { @@ -908,6 +909,12 @@ public extension Api { serializeString(venueId, buffer: buffer, boxed: false) serializeString(venueType, buffer: buffer, boxed: false) break + case .messageMediaVideoStream(let call): + if boxed { + buffer.appendInt32(1059290001) + } + call.serialize(buffer, true) + break case .messageMediaWebPage(let flags, let webpage): if boxed { buffer.appendInt32(-571405253) @@ -954,6 +961,8 @@ public extension Api { return ("messageMediaUnsupported", []) 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)]) + case .messageMediaVideoStream(let call): + return ("messageMediaVideoStream", [("call", call as Any)]) case .messageMediaWebPage(let flags, let webpage): return ("messageMediaWebPage", [("flags", flags as Any), ("webpage", webpage as Any)]) } @@ -1329,6 +1338,19 @@ public extension Api { 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? { var _1: Int32? _1 = reader.readInt32() diff --git a/submodules/TelegramApi/Sources/Api27.swift b/submodules/TelegramApi/Sources/Api27.swift index 5052ea908c..619d6a970b 100644 --- a/submodules/TelegramApi/Sources/Api27.swift +++ b/submodules/TelegramApi/Sources/Api27.swift @@ -604,11 +604,11 @@ public extension Api { case updateFavedStickers case updateFolderPeers(folderPeers: [Api.FolderPeer], pts: Int32, ptsCount: 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 updateGroupCallConnection(flags: Int32, params: Api.DataJSON) 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 updateInlineBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int64, msgId: Api.InputBotInlineMessageID, chatInstance: Int64, data: Buffer?, gameShortName: String?) case updateLangPack(difference: Api.LangPackDifference) @@ -1271,12 +1271,12 @@ public extension Api { peer.serialize(buffer, true) serializeInt32(msgId, buffer: buffer, boxed: false) break - case .updateGroupCall(let flags, let chatId, let call): + case .updateGroupCall(let flags, let peer, let call): if boxed { - buffer.appendInt32(-1747565759) + buffer.appendInt32(-1658710304) } 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) break case .updateGroupCallChainBlocks(let call, let subChainId, let blocks, let nextOffset): @@ -1307,14 +1307,16 @@ public extension Api { fromId.serialize(buffer, true) serializeBytes(encryptedMessage, buffer: buffer, boxed: false) 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 { - buffer.appendInt32(2026050784) + buffer.appendInt32(-964095818) } + serializeInt32(flags, buffer: buffer, boxed: false) call.serialize(buffer, true) fromId.serialize(buffer, true) serializeInt64(randomId, buffer: buffer, boxed: false) message.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(paidMessageStars!, buffer: buffer, boxed: false)} break case .updateGroupCallParticipants(let call, let participants, let version): if boxed { @@ -2110,16 +2112,16 @@ public extension Api { return ("updateFolderPeers", [("folderPeers", folderPeers as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) case .updateGeoLiveViewed(let peer, let msgId): return ("updateGeoLiveViewed", [("peer", peer as Any), ("msgId", msgId as Any)]) - case .updateGroupCall(let flags, let chatId, let call): - return ("updateGroupCall", [("flags", flags as Any), ("chatId", chatId as Any), ("call", call as Any)]) + case .updateGroupCall(let flags, let peer, let call): + return ("updateGroupCall", [("flags", flags as Any), ("peer", peer as Any), ("call", call as Any)]) 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)]) case .updateGroupCallConnection(let flags, let params): return ("updateGroupCallConnection", [("flags", flags as Any), ("params", params as Any)]) case .updateGroupCallEncryptedMessage(let call, let fromId, let encryptedMessage): return ("updateGroupCallEncryptedMessage", [("call", call as Any), ("fromId", fromId as Any), ("encryptedMessage", encryptedMessage as Any)]) - case .updateGroupCallMessage(let call, let fromId, let randomId, let message): - return ("updateGroupCallMessage", [("call", call as Any), ("fromId", fromId as Any), ("randomId", randomId as Any), ("message", message as Any)]) + case .updateGroupCallMessage(let flags, let call, let fromId, let randomId, let message, let paidMessageStars): + 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): 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): @@ -3513,17 +3515,19 @@ public extension Api { public static func parse_updateGroupCall(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt64() } + var _2: Api.Peer? + 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? if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.GroupCall } 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 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 { return nil @@ -3591,26 +3595,32 @@ public extension Api { } } 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() { - _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() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer + _3 = Api.parse(reader, signature: signature) as? Api.Peer } - var _3: Int64? - _3 = reader.readInt64() - var _4: Api.TextWithEntities? + var _4: Int64? + _4 = reader.readInt64() + var _5: Api.TextWithEntities? 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 _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateGroupCallMessage(call: _1!, fromId: _2!, randomId: _3!, message: _4!) + let _c5 = _5 != nil + 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 { return nil diff --git a/submodules/TelegramApi/Sources/Api39.swift b/submodules/TelegramApi/Sources/Api39.swift index 7fd583eee2..26acd6c292 100644 --- a/submodules/TelegramApi/Sources/Api39.swift +++ b/submodules/TelegramApi/Sources/Api39.swift @@ -5656,9 +5656,9 @@ 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) { + 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) { let buffer = Buffer() - buffer.appendInt32(-539934715) + buffer.appendInt32(1374175969) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) serializeInt32(id, buffer: buffer, boxed: false) @@ -5671,8 +5671,9 @@ public extension Api.functions.messages { item.serialize(buffer, true) }} 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)} - 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) var result: Api.Updates? if let signature = reader.readInt32() { @@ -5735,9 +5736,9 @@ 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) { + 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) { let buffer = Buffer() - buffer.appendInt32(-1752618806) + buffer.appendInt32(1104419550) serializeInt32(flags, buffer: buffer, boxed: false) fromPeer.serialize(buffer, true) 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 << 22) != 0 {replyTo!.serialize(buffer, true)} 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 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} 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 << 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) var result: Api.Updates? if let signature = reader.readInt32() { @@ -8355,9 +8357,9 @@ 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) { + 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) { let buffer = Buffer() - buffer.appendInt32(-1403659839) + buffer.appendInt32(53536639) serializeInt32(flags, buffer: buffer, boxed: false) peer.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) }} 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 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} 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 << 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) var result: Api.Updates? if let signature = reader.readInt32() { @@ -8387,9 +8390,9 @@ 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) { + 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) { let buffer = Buffer() - buffer.appendInt32(-33170278) + buffer.appendInt32(1415369050) serializeInt32(flags, buffer: buffer, boxed: false) peer.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) }} 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 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} 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 << 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) var result: Api.Updates? if let signature = reader.readInt32() { @@ -10567,12 +10571,13 @@ public extension Api.functions.phone { } } public extension Api.functions.phone { - static func getGroupCallStreamRtmpUrl(peer: Api.InputPeer, revoke: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func getGroupCallStreamRtmpUrl(flags: Int32, peer: Api.InputPeer, revoke: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-558650433) + buffer.appendInt32(1525991226) + serializeInt32(flags, buffer: buffer, boxed: false) peer.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) var result: Api.phone.GroupCallStreamRtmpUrl? if let signature = reader.readInt32() { @@ -10829,13 +10834,15 @@ public extension Api.functions.phone { } } public extension Api.functions.phone { - static func sendGroupCallMessage(call: Api.InputGroupCall, randomId: Int64, message: Api.TextWithEntities) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendGroupCallMessage(flags: Int32, call: Api.InputGroupCall, randomId: Int64, message: Api.TextWithEntities, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-2021052396) + buffer.appendInt32(2124127245) + serializeInt32(flags, buffer: buffer, boxed: false) call.serialize(buffer, true) serializeInt64(randomId, buffer: buffer, boxed: false) 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) var result: Api.Bool? if let signature = reader.readInt32() { @@ -10913,14 +10920,15 @@ 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) { + static func toggleGroupCallSettings(flags: Int32, call: Api.InputGroupCall, joinMuted: Api.Bool?, messagesEnabled: Api.Bool?, sendPaidMessagesStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-378390524) + buffer.appendInt32(-1757179150) serializeInt32(flags, buffer: buffer, boxed: false) call.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)} - 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) var result: Api.Updates? 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) { + 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 { static func toggleAllStoriesHidden(hidden: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramApi/Sources/Api7.swift b/submodules/TelegramApi/Sources/Api7.swift index 4b7df359d9..c83d4a0c79 100644 --- a/submodules/TelegramApi/Sources/Api7.swift +++ b/submodules/TelegramApi/Sources/Api7.swift @@ -1330,13 +1330,13 @@ public extension Api { } public extension Api { 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) { 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 { - buffer.appendInt32(-341428482) + buffer.appendInt32(708691884) } serializeInt32(flags, buffer: buffer, boxed: false) 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 << 6) != 0 {video!.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 } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .groupCallParticipant(let flags, let peer, let date, let activeDate, let source, let volume, let about, let raiseHandRating, let video, let presentation): - 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)]) + 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), ("paidStarsTotal", paidStarsTotal as Any)]) } } @@ -1386,6 +1387,8 @@ public extension Api { if Int(_1!) & Int(1 << 14) != 0 {if let signature = reader.readInt32() { _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 _c2 = _2 != nil let _c3 = _3 != nil @@ -1396,8 +1399,9 @@ public extension Api { let _c8 = (Int(_1!) & Int(1 << 13) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil let _c10 = (Int(_1!) & Int(1 << 14) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, peer: _2!, date: _3!, activeDate: _4, source: _5!, volume: _6, about: _7, raiseHandRating: _8, video: _9, presentation: _10) + let _c11 = (Int(_1!) & Int(1 << 16) == 0) || _11 != nil + 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 { return nil diff --git a/submodules/TelegramApi/Sources/Api9.swift b/submodules/TelegramApi/Sources/Api9.swift index d67c5a00ae..6ccf553180 100644 --- a/submodules/TelegramApi/Sources/Api9.swift +++ b/submodules/TelegramApi/Sources/Api9.swift @@ -572,44 +572,54 @@ public extension Api { } public extension Api { 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) { 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 { - buffer.appendInt32(-208488460) + buffer.appendInt32(1780335806) } + serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(clientId, buffer: buffer, boxed: false) serializeString(phone, buffer: buffer, boxed: false) serializeString(firstName, buffer: buffer, boxed: false) serializeString(lastName, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {note!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .inputPhoneContact(let clientId, let phone, let firstName, let lastName): - return ("inputPhoneContact", [("clientId", clientId as Any), ("phone", phone as Any), ("firstName", firstName as Any), ("lastName", lastName as Any)]) + case .inputPhoneContact(let flags, let clientId, let phone, let firstName, let lastName, let note): + 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? { - var _1: Int64? - _1 = reader.readInt64() - var _2: String? - _2 = parseString(reader) + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() var _3: String? _3 = parseString(reader) var _4: String? _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 _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputContact.inputPhoneContact(clientId: _1!, phone: _2!, firstName: _3!, lastName: _4!) + let _c5 = _5 != nil + 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 { return nil diff --git a/submodules/TelegramCallsUI/BUILD b/submodules/TelegramCallsUI/BUILD index 68eeb68510..906c96938c 100644 --- a/submodules/TelegramCallsUI/BUILD +++ b/submodules/TelegramCallsUI/BUILD @@ -84,7 +84,6 @@ swift_library( "//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode", "//submodules/AlertUI:AlertUI", "//submodules/DirectionalPanGesture:DirectionalPanGesture", - "//submodules/PeerInfoUI:PeerInfoUI", "//submodules/AnimatedCountLabelNode:AnimatedCountLabelNode", "//submodules/DeviceProximity:DeviceProximity", "//submodules/ManagedAnimationNode:ManagedAnimationNode", diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift index 8691273307..ecaff36f9b 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift @@ -367,6 +367,7 @@ public final class MediaStreamComponent: CombinedComponent { isVisible: environment.isVisible && context.state.isVisibleInHierarchy, isAdmin: context.state.canManageCall, peerTitle: context.state.peerTitle, + addInset: !isFullscreen, isFullscreen: isFullscreen, videoLoading: context.state.videoStalled, callPeer: context.state.chatPeer, @@ -595,7 +596,7 @@ public final class MediaStreamComponent: CombinedComponent { } let credentialsPromise = Promise() - credentialsPromise.set(call.accountContext.engine.calls.getGroupCallStreamCredentials(peerId: peerId, revokePreviousCredentials: false) |> `catch` { _ -> Signal in return .never() }) + credentialsPromise.set(call.accountContext.engine.calls.getGroupCallStreamCredentials(peerId: peerId, isLiveStream: false, revokePreviousCredentials: false) |> `catch` { _ -> Signal 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 return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor, backgroundColor: nil) diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift index f029ed407e..f4742e46b8 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift @@ -13,7 +13,7 @@ import Postbox import TelegramVoip import ComponentDisplayAdapters -final class MediaStreamVideoComponent: Component { +public final class MediaStreamVideoComponent: Component { let call: PresentationGroupCallImpl let hasVideo: Bool let isVisible: Bool @@ -23,18 +23,20 @@ final class MediaStreamVideoComponent: Component { let deactivatePictureInPicture: ActionSlot let bringBackControllerForPictureInPictureDeactivation: (@escaping () -> Void) -> Void let pictureInPictureClosed: () -> Void + let addInset: Bool let isFullscreen: Bool let onVideoSizeRetrieved: (CGSize) -> Void let videoLoading: Bool let callPeer: Peer? let onVideoPlaybackLiveChange: (Bool) -> Void - init( + public init( call: PresentationGroupCallImpl, hasVideo: Bool, isVisible: Bool, isAdmin: Bool, peerTitle: String, + addInset: Bool, isFullscreen: Bool, videoLoading: Bool, callPeer: Peer?, @@ -58,6 +60,7 @@ final class MediaStreamVideoComponent: Component { self.onVideoPlaybackLiveChange = onVideoPlaybackLiveChange self.callPeer = callPeer + self.addInset = addInset self.isFullscreen = isFullscreen self.onVideoSizeRetrieved = onVideoSizeRetrieved } @@ -78,6 +81,9 @@ final class MediaStreamVideoComponent: Component { if lhs.peerTitle != rhs.peerTitle { return false } + if lhs.addInset != rhs.addInset { + return false + } if lhs.isFullscreen != rhs.isFullscreen { return false } @@ -334,7 +340,7 @@ final class MediaStreamVideoComponent: Component { } }) stallTimer = _stallTimer - self.clipsToBounds = component.isFullscreen // or just true + self.clipsToBounds = true if let videoView = self.videoRenderingContext.makeView(input: input, blur: false, forceSampleBufferDisplayLayer: true) { 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.insertSubview(videoBlurView, belowSubview: self.blurTintView) videoBlurView.alpha = 0 @@ -453,14 +459,14 @@ final class MediaStreamVideoComponent: Component { fullScreenBackgroundPlaceholder.frame = .init(origin: .zero, size: availableSize) let videoInset: CGFloat - if !component.isFullscreen { + if component.addInset && !component.isFullscreen { videoInset = 16 } else { videoInset = 0 } let videoSize: CGSize - let videoCornerRadius: CGFloat = component.isFullscreen ? 0 : 10 + let videoCornerRadius: CGFloat = (component.isFullscreen || !component.addInset) ? 0 : 10 let videoFrameUpdateTransition: ComponentTransition if self.wasFullscreen != component.isFullscreen { @@ -478,6 +484,7 @@ final class MediaStreamVideoComponent: Component { } var aspect = videoView.getAspect() + if component.isFullscreen && self.hadVideo { if aspect <= 0.01 { aspect = 16.0 / 9 @@ -485,8 +492,12 @@ final class MediaStreamVideoComponent: Component { } else if !self.hadVideo { 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)) } else { // 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.noSignalTimer == nil { - if #available(iOS 10.0, *) { - let noSignalTimer = Timer(timeInterval: 20.0, repeats: false, block: { [weak self] _ in - guard let strongSelf = self else { - return - } - strongSelf.noSignalTimeout = true - strongSelf.state?.updated(transition: .immediate) - }) - self.noSignalTimer = noSignalTimer - RunLoop.main.add(noSignalTimer, forMode: .common) - } + let noSignalTimer = Timer(timeInterval: 20.0, repeats: false, block: { [weak self] _ in + guard let strongSelf = self else { + return + } + strongSelf.noSignalTimeout = true + strongSelf.state?.updated(transition: .immediate) + }) + self.noSignalTimer = noSignalTimer + RunLoop.main.add(noSignalTimer, forMode: .common) } if self.noSignalTimeout, !"".isEmpty { @@ -696,7 +705,7 @@ final class MediaStreamVideoComponent: Component { return availableSize } - func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { if let videoView = self.videoView, let presentation = videoView.snapshotView(afterScreenUpdates: false) { let presentationParent = self.window ?? self presentationParent.addSubview(presentation) @@ -750,12 +759,12 @@ final class MediaStreamVideoComponent: Component { } } - func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + public func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { self.didRequestBringBack = false self.state?.updated(transition: .immediate) } - func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + public func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { if self.requestedExpansion { self.requestedExpansion = false } 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.state?.updated(transition: .immediate) } @@ -810,94 +819,3 @@ private final class CustomIntensityVisualEffectView: UIVisualEffectView { 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) - } -} - diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 43c5274808..9fa7e67d2a 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -838,7 +838,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { private var lastErrorAlertTimestamp: Double = 0.0 - init( + public init( accountContext: AccountContext, audioSession: ManagedAudioSession, callKitIntegration: CallKitIntegration?, @@ -1356,7 +1356,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { muteState: self.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false), volume: nil, about: about, - joinedVideo: self.temporaryJoinedVideo + joinedVideo: self.temporaryJoinedVideo, + paidStarsTotal: nil )) 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), volume: nil, 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), volume: nil, about: about, - joinedVideo: self.temporaryJoinedVideo + joinedVideo: self.temporaryJoinedVideo, + paidStarsTotal: nil )) } @@ -1743,7 +1746,30 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { shouldJoin = callInfo.scheduleTimestamp == nil activeCallInfo = callInfo } 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 { @@ -2003,6 +2029,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { joinAs: self.joinAsPeerId, callId: callInfo.id, reference: reference, + isStream: self.isStream, preferMuted: isEffectivelyMuted, joinPayload: joinPayload, peerAdminIds: peerAdminIds, @@ -2460,7 +2487,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { muteState: self.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false), volume: nil, about: about, - joinedVideo: self.temporaryJoinedVideo + joinedVideo: self.temporaryJoinedVideo, + paidStarsTotal: nil )) participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) }) } @@ -3649,7 +3677,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { return } - if let value = value { + if let value { var reference: InternalGroupCallReference = .id(id: value.id, accessHash: value.accessHash) if let current = self.initialCall { switch current.reference { diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index 5ba680b6d9..78ef0dc5bf 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -1263,7 +1263,8 @@ final class VideoChatScreenComponent: Component { muteState: nil, volume: nil, about: nil, - joinedVideo: false + joinedVideo: false, + paidStarsTotal: nil )) } if let remotePeer { @@ -1289,7 +1290,8 @@ final class VideoChatScreenComponent: Component { muteState: nil, volume: nil, about: nil, - joinedVideo: false + joinedVideo: false, + paidStarsTotal: nil )) } let members = PresentationGroupCallMembers( @@ -3712,7 +3714,7 @@ final class VideoChatScreenComponent: Component { context: call.accountContext, animationCache: call.accountContext.animationCache, 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) }, selectedItems: Set(), title: nil, diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift index d35d00074b..dd47fdab48 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift @@ -3,12 +3,12 @@ import UIKit import Display import TelegramCore import SwiftSignalKit -import PeerInfoUI import OverlayStatusController import PresentationDataUtils import InviteLinksUI import UndoUI import TelegramPresentationData +import AccountContext extension VideoChatScreenComponent.View { func openInviteMembers() { @@ -113,7 +113,7 @@ extension VideoChatScreenComponent.View { filters.append(.excludeBots) 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 { dismissController?() return @@ -327,7 +327,7 @@ extension VideoChatScreenComponent.View { })]), in: .window(.root)) } } - }) + })) controller.copyInviteLink = { [weak self] in dismissController?() diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index d79cd9dc5a..e5625b62f8 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -21,7 +21,6 @@ import UndoUI import AlertUI import PresentationDataUtils import DirectionalPanGesture -import PeerInfoUI import AvatarNode import TooltipUI import LegacyUI diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift b/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift index 403f403b07..bfc203cdc3 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift @@ -10,7 +10,6 @@ import TelegramUIPreferences import AccountContext import AlertUI import PresentationDataUtils -import PeerInfoUI import ShareController import AvatarNode import UndoUI diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index 3a505b6c40..21b5cad557 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -238,6 +238,7 @@ private var declaredEncodables: Void = { declareEncodable(TelegramMediaTodo.Completion.self, f: { TelegramMediaTodo.Completion(decoder: $0) }) declareEncodable(SuggestedPostMessageAttribute.self, f: { SuggestedPostMessageAttribute(decoder: $0) }) declareEncodable(PublishedSuggestedPostMessageAttribute.self, f: { PublishedSuggestedPostMessageAttribute(decoder: $0) }) + declareEncodable(TelegramMediaLiveStream.self, f: { TelegramMediaLiveStream(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 0d6385478f..5ddc45e0e0 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -128,7 +128,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { switch messsage { - case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let chatPeerId = messagePeerId return chatPeerId.peerId case let .messageEmpty(_, _, peerId): @@ -144,7 +144,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { 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 var result = [peerId] @@ -279,7 +279,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: ReferencedReplyMessageIds, generalIds: [MessageId])? { switch message { - case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if let replyTo = replyTo { let peerId: PeerId = chatPeerId.peerId @@ -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) case let .messageMediaPaidMedia(starsAmount, apiExtendedMedia): 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 { convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { 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] = [] if (flags2 & (1 << 4)) != 0 { diff --git a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift index 7316f7234f..1bc4cd9091 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift @@ -192,7 +192,7 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, 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 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) } - 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) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index f1c8a7855a..c9cc63be58 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -454,7 +454,7 @@ private func sendUploadedMessageContent( 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): if bubbleUpEmojiOrStickersets { flags |= Int32(1 << 15) @@ -486,7 +486,7 @@ private func sendUploadedMessageContent( 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) case let .forward(sourceInfo): 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) { - 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) } else { 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) } - 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 in return .complete() } @@ -726,7 +726,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M 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 in return .complete() } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index e71ee5dd73..b9d50dec00 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1678,13 +1678,12 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: break } 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.flatMap { PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value($0)) }, call: call) + updatedState.updateGroupCall(peerId: channelId?.peerId, call: call) case let .updateGroupCallChainBlocks(call, subChainId, blocks, nextOffset): if case let .inputGroupCall(id, accessHash) = call { 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 { 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 case .item = 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 { 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 { @@ -5246,7 +5245,7 @@ func replayFinalState( folderIds: item.folderIds )) 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) } } } diff --git a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift index 41dd3b684c..185ed044fb 100644 --- a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift @@ -104,7 +104,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes var updatedTimestamp: Int32? if let apiMessage = apiMessage { switch apiMessage { - case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): updatedTimestamp = date case .messageEmpty: break @@ -400,7 +400,7 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage } else if let message = messages.first, let apiMessage = result.messages.first { if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { 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 } } diff --git a/submodules/TelegramCore/Sources/State/CallSessionManager.swift b/submodules/TelegramCore/Sources/State/CallSessionManager.swift index 8dc741a563..f49db11689 100644 --- a/submodules/TelegramCore/Sources/State/CallSessionManager.swift +++ b/submodules/TelegramCore/Sources/State/CallSessionManager.swift @@ -53,7 +53,7 @@ public struct CallId: Equatable { } } -public struct GroupCallReference: Equatable { +public struct GroupCallReference: Codable, Equatable { public var id: Int64 public var accessHash: Int64 diff --git a/submodules/TelegramCore/Sources/State/ContactSyncManager.swift b/submodules/TelegramCore/Sources/State/ContactSyncManager.swift index 47dae1a1fd..567aecf47a 100644 --- a/submodules/TelegramCore/Sources/State/ContactSyncManager.swift +++ b/submodules/TelegramCore/Sources/State/ContactSyncManager.swift @@ -326,7 +326,7 @@ private func pushDeviceContactData(accountPeerId: PeerId, postbox: Postbox, netw batches = batches |> mapToSignal { intermediateResult -> Signal 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) |> `catch` { _ -> Signal in diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 1b4de7dd3c..fe1d446d82 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -1122,7 +1122,7 @@ public final class PendingMessageManager { } else if let inputSourcePeerId = forwardPeerIds.first, let inputSourcePeer = transaction.getPeer(inputSourcePeerId).flatMap(apiInputPeer) { 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 { assertionFailure() sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "Invalid forward source")) @@ -1671,7 +1671,7 @@ public final class PendingMessageManager { 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): if bubbleUpEmojiOrStickersets { flags |= Int32(1 << 15) @@ -1770,7 +1770,7 @@ public final class PendingMessageManager { 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) case let .forward(sourceInfo): 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) { - 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) } else { sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal")) @@ -2060,7 +2060,7 @@ public final class PendingMessageManager { if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { isScheduled = true } - if case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage { + if case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage { if (flags2 & (1 << 4)) != 0 { isScheduled = true } @@ -2104,7 +2104,7 @@ public final class PendingMessageManager { namespace = Namespaces.Message.QuickReplyCloud } else if let apiMessage = result.messages.first, message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { 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 } } diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index 0bd6fa02de..0cb422ada2 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 216 + return 217 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift index 93a88bc9c9..716fffa529 100644 --- a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift @@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.putNext(groups) } case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod): - let generatedMessage = Api.Message.message(flags: flags, 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 groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { @@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService { let generatedPeerId = Api.Peer.peerUser(userId: userId) - let generatedMessage = Api.Message.message(flags: flags, 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 groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index 3b7de380ec..336b9c4351 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -104,7 +104,7 @@ extension Api.MessageMedia { extension Api.Message { var rawId: Int32 { switch self { - case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return id case let .messageEmpty(_, id, _): return id @@ -115,7 +115,7 @@ extension Api.Message { func id(namespace: MessageId.Namespace = Namespaces.Message.Cloud) -> MessageId? { switch self { - case let .message(_, flags2, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, flags2, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): var namespace = namespace if (flags2 & (1 << 4)) != 0 { namespace = Namespaces.Message.ScheduledCloud @@ -136,7 +136,7 @@ extension Api.Message { var peerId: PeerId? { switch self { - case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = messagePeerId.peerId return peerId case let .messageEmpty(_, _, peerId): @@ -149,7 +149,7 @@ extension Api.Message { var timestamp: Int32? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return date case let .messageService(_, _, _, _, _, _, date, _, _, _): return date @@ -160,7 +160,7 @@ extension Api.Message { var preCachedResources: [(MediaResource, Data)]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedResources default: return nil @@ -169,7 +169,7 @@ extension Api.Message { var preCachedStories: [StoryId: Api.StoryItem]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedStories default: return nil diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaLiveStream.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaLiveStream.swift new file mode 100644 index 0000000000..8862df8abc --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaLiveStream.swift @@ -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) + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index d1dd3fac7f..42859b3879 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -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 { +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 { enum InternalJoinError { case error(JoinGroupCallError) 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) - |> mapError { _ -> InternalJoinError in - return .error(.generic) + let getParticipantsRequest: Signal + if isStream { + 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( @@ -835,7 +857,7 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, case let .updateGroupCallParticipants(_, participants, _): loop: for participant in participants { 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 ssrc = UInt32(bitPattern: source) guard let peer = transaction.getPeer(peerId) else { @@ -872,7 +894,8 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, muteState: muteState, volume: volume, about: about, - joinedVideo: joinedVideo + joinedVideo: joinedVideo, + paidStarsTotal: paidStarsTotal )) } } @@ -1233,6 +1256,7 @@ public final class GroupCallParticipantsContext { public var volume: Int32? public var about: String? public var joinedVideo: Bool + public var paidStarsTotal: Int64? public init( id: Id, @@ -1248,7 +1272,8 @@ public final class GroupCallParticipantsContext { muteState: MuteState?, volume: Int32?, about: String?, - joinedVideo: Bool + joinedVideo: Bool, + paidStarsTotal: Int64? ) { self.id = id self.peer = peer @@ -1264,6 +1289,7 @@ public final class GroupCallParticipantsContext { self.volume = volume self.about = about self.joinedVideo = joinedVideo + self.paidStarsTotal = paidStarsTotal } public var description: String { @@ -1320,6 +1346,9 @@ public final class GroupCallParticipantsContext { if lhs.raiseHandRating != rhs.raiseHandRating { return false } + if lhs.paidStarsTotal != rhs.paidStarsTotal { + return false + } return true } @@ -1547,6 +1576,7 @@ public final class GroupCallParticipantsContext { public var volume: Int32? public var about: String? public var joinedVideo: Bool + public var paidStarsTotal: Int64? public var isMin: Bool init( @@ -1562,6 +1592,7 @@ public final class GroupCallParticipantsContext { volume: Int32?, about: String?, joinedVideo: Bool, + paidStarsTotal: Int64?, isMin: Bool ) { self.peerId = peerId @@ -1576,6 +1607,7 @@ public final class GroupCallParticipantsContext { self.volume = volume self.about = about self.joinedVideo = joinedVideo + self.paidStarsTotal = paidStarsTotal self.isMin = isMin } } @@ -1679,7 +1711,8 @@ public final class GroupCallParticipantsContext { muteState: nil, volume: nil, about: nil, - joinedVideo: false + joinedVideo: false, + paidStarsTotal: nil )) } } @@ -2266,7 +2299,8 @@ public final class GroupCallParticipantsContext { muteState: muteState, volume: volume, about: participantUpdate.about, - joinedVideo: participantUpdate.joinedVideo + joinedVideo: participantUpdate.joinedVideo, + paidStarsTotal: participantUpdate.paidStarsTotal ) updatedParticipants.append(participant) } @@ -2579,7 +2613,7 @@ public final class GroupCallParticipantsContext { } 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 guard let strongSelf = self else { return @@ -2594,7 +2628,7 @@ public final class GroupCallParticipantsContext { } 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 guard let strongSelf = self else { return @@ -2604,7 +2638,7 @@ public final class GroupCallParticipantsContext { } 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 guard let strongSelf = self else { return @@ -2662,7 +2696,7 @@ public final class GroupCallParticipantsContext { extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate { init(_ apiParticipant: Api.GroupCallParticipant) { 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 ssrc = UInt32(bitPattern: source) let muted = (flags & (1 << 0)) != 0 @@ -2707,6 +2741,7 @@ extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate { volume: volume, about: about, joinedVideo: joinedVideo, + paidStarsTotal: paidStarsTotal, isMin: isMin ) } @@ -3135,7 +3170,7 @@ func _internal_getVideoBroadcastPart(dataSource: AudioBroadcastDataSource, callI extension GroupCallParticipantsContext.Participant { init?(_ apiParticipant: Api.GroupCallParticipant, transaction: Transaction) { 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 ssrc = UInt32(bitPattern: source) guard let peer = transaction.getPeer(peerId) else { @@ -3173,7 +3208,8 @@ extension GroupCallParticipantsContext.Participant { muteState: muteState, volume: volume, about: about, - joinedVideo: joinedVideo + joinedVideo: joinedVideo, + paidStarsTotal: paidStarsTotal ) } } @@ -3205,7 +3241,7 @@ public enum GetGroupCallStreamCredentialsError { case generic } -func _internal_getGroupCallStreamCredentials(account: Account, peerId: PeerId, revokePreviousCredentials: Bool) -> Signal { +func _internal_getGroupCallStreamCredentials(account: Account, peerId: PeerId, isLiveStream: Bool, revokePreviousCredentials: Bool) -> Signal { return account.postbox.transaction { transaction -> Api.InputPeer? in return transaction.getPeer(peerId).flatMap(apiInputPeer) } @@ -3215,7 +3251,11 @@ func _internal_getGroupCallStreamCredentials(account: Account, peerId: PeerId, r 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 return .generic } @@ -3295,7 +3335,7 @@ public enum RevokeConferenceInviteLinkError { } func _internal_revokeConferenceInviteLink(account: Account, reference: InternalGroupCallReference, link: String) -> Signal { - 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 return .generic } @@ -3882,12 +3922,14 @@ public final class GroupCallMessagesContext { arc4random_buf(&randomId, 8) } self.sendMessageDisposables.add(self.account.network.request(Api.functions.phone.sendGroupCallMessage( + flags: 0, call: self.reference.apiInputGroupCall, randomId: randomId, message: .textWithEntities( text: text, entities: apiEntitiesFromMessageTextEntities(entities, associatedPeers: SimpleDictionary()) - ) + ), + allowPaidStars: nil )).startStrict()) } }) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift index bec1b4d9f4..93e4ede2ac 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift @@ -58,8 +58,8 @@ public extension TelegramEngine { 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 { - return _internal_joinGroupCall(account: self.account, peerId: peerId, joinAs: joinAs, callId: callId, reference: reference, preferMuted: preferMuted, joinPayload: joinPayload, peerAdminIds: peerAdminIds, inviteHash: inviteHash, generateE2E: generateE2E) + 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 { + 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 { @@ -176,8 +176,8 @@ public extension TelegramEngine { } } - public func getGroupCallStreamCredentials(peerId: EnginePeer.Id, revokePreviousCredentials: Bool) -> Signal { - return _internal_getGroupCallStreamCredentials(account: self.account, peerId: peerId, revokePreviousCredentials: revokePreviousCredentials) + public func getGroupCallStreamCredentials(peerId: EnginePeer.Id, isLiveStream: Bool, revokePreviousCredentials: Bool) -> Signal { + return _internal_getGroupCallStreamCredentials(account: self.account, peerId: peerId, isLiveStream: isLiveStream, revokePreviousCredentials: revokePreviousCredentials) } public func getGroupCallPersistentSettings(callId: Int64) -> Signal { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift index 1fe48b4db9..21e4820453 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift @@ -6,7 +6,7 @@ import SwiftSignalKit func _internal_importContact(account: Account, firstName: String, lastName: String, phoneNumber: String) -> Signal { 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])) |> map(Optional.init) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift index ce2e689455..8fa6c1e716 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift @@ -645,7 +645,7 @@ public final class EngineStoryViewListContext { folderIds: item.folderIds )) 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) } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift index 594593e546..584f8775a8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift @@ -14,7 +14,7 @@ func _internal_forwardGameWithScore(account: Account, messageId: MessageId, to p 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) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Media.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Media.swift index 5e389a6ad6..2fd085312c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Media.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Media.swift @@ -21,6 +21,7 @@ public enum EngineMedia: Equatable { case giveawayResults(TelegramMediaGiveawayResults) case paidContent(TelegramMediaPaidContent) case todo(TelegramMediaTodo) + case liveStream(TelegramMediaLiveStream) } public extension EngineMedia { @@ -62,6 +63,8 @@ public extension EngineMedia { return paidContent.id case .todo: return nil + case .liveStream: + return nil } } } @@ -105,6 +108,8 @@ public extension EngineMedia { self = .paidContent(paidContent) case let todo as TelegramMediaTodo: self = .todo(todo) + case let liveStream as TelegramMediaLiveStream: + self = .liveStream(liveStream) default: preconditionFailure() } @@ -148,6 +153,8 @@ public extension EngineMedia { return paidContent case let .todo(todo): return todo + case let .liveStream(liveStream): + return liveStream } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift index 259a7ccf17..519eac4269 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift @@ -142,7 +142,7 @@ func _internal_requestClosePoll(postbox: Postbox, network: Network, stateManager 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) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index 568830731d..a7109eb345 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -290,6 +290,10 @@ public enum Stories { public let authorId: PeerId? public let folderIds: [Int64]? + public var isLiveStream: Bool { + return self.media is TelegramMediaLiveStream + } + public init( id: 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 { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -759,6 +772,7 @@ public final class EngineStorySubscriptions: Equatable { public let peer: EnginePeer public let hasUnseen: Bool public let hasUnseenCloseFriends: Bool + public let hasLiveItems: Bool public let hasPending: Bool public let storyCount: Int public let unseenCount: Int @@ -768,6 +782,7 @@ public final class EngineStorySubscriptions: Equatable { peer: EnginePeer, hasUnseen: Bool, hasUnseenCloseFriends: Bool, + hasLiveItems: Bool, hasPending: Bool, storyCount: Int, unseenCount: Int, @@ -776,6 +791,7 @@ public final class EngineStorySubscriptions: Equatable { self.peer = peer self.hasUnseen = hasUnseen self.hasUnseenCloseFriends = hasUnseenCloseFriends + self.hasLiveItems = hasLiveItems self.hasPending = hasPending self.storyCount = storyCount self.unseenCount = unseenCount @@ -795,6 +811,9 @@ public final class EngineStorySubscriptions: Equatable { if lhs.hasUnseenCloseFriends != rhs.hasUnseenCloseFriends { return false } + if lhs.hasLiveItems != rhs.hasLiveItems { + return false + } if lhs.storyCount != rhs.storyCount { return false } @@ -1093,6 +1112,22 @@ func _internal_cancelStoryUpload(account: Account, stableId: Int32) { }).start() } +func _internal_beginStoryLivestream(account: Account) -> Signal { + 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 in + return .single(nil) + } + |> mapToSignal { updates -> Signal in + if let updates { + account.stateManager.addUpdates(updates) + } + return .complete() + } +} + private struct PendingStoryIdMappingKey: Hashable { var peerId: PeerId var stableId: Int32 @@ -1307,7 +1342,7 @@ func _internal_uploadStoryImpl( folderIds: item.folderIds ) 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) } @@ -1749,7 +1784,7 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor folderIds: item.folderIds ) 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) @@ -1946,7 +1981,7 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In folderIds: item.folderIds ) 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) @@ -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 case .item = 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 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) } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift index 87de3f373b..e629c4bc46 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift @@ -443,7 +443,7 @@ public final class StorySubscriptionsContext { updatedPeerEntries.append(previousEntry) } else { 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) } else { 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 }) } + 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) { self.items = items self.isCached = isCached @@ -2775,7 +2789,7 @@ public func _internal_pollPeerStories(postbox: Postbox, network: Network, accoun updatedPeerEntries.append(previousEntry) } else { 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)) } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 325c7542d6..29e51e8db4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -999,6 +999,7 @@ public extension TelegramEngine { peer: EnginePeer(accountPeer), hasUnseen: false, hasUnseenCloseFriends: false, + hasLiveItems: false, hasPending: accountPendingItemCount != 0, storyCount: accountPendingItemCount, unseenCount: 0, @@ -1015,8 +1016,9 @@ public extension TelegramEngine { let peerState: Stories.PeerState? = stateView.value?.get(Stories.PeerState.self) var hasUnseen = false var hasUnseenCloseFriends = false + var hasLiveItems = false var unseenCount = 0 - if let peerState = peerState { + if let peerState { hasUnseen = peerState.maxReadId < lastEntry.id for item in itemsView.items { @@ -1030,6 +1032,9 @@ public extension TelegramEngine { hasUnseenCloseFriends = true } } + if item.media is TelegramMediaLiveStream { + hasLiveItems = true + } } } } @@ -1038,6 +1043,7 @@ public extension TelegramEngine { peer: EnginePeer(accountPeer), hasUnseen: hasUnseen, hasUnseenCloseFriends: hasUnseenCloseFriends, + hasLiveItems: hasLiveItems, hasPending: accountPendingItemCount != 0, storyCount: itemsView.items.count + accountPendingItemCount, unseenCount: unseenCount, @@ -1079,6 +1085,7 @@ public extension TelegramEngine { let peerState: Stories.PeerState? = stateView.value?.get(Stories.PeerState.self) var hasUnseen = false var hasUnseenCloseFriends = false + var hasLiveItems = false var unseenCount = 0 if let peerState = peerState { hasUnseen = peerState.maxReadId < lastEntry.id @@ -1091,6 +1098,9 @@ public extension TelegramEngine { if item.isCloseFriends { hasUnseenCloseFriends = true } + if item.media is TelegramMediaLiveStream { + hasLiveItems = true + } } } } @@ -1116,6 +1126,7 @@ public extension TelegramEngine { peer: EnginePeer(peer), hasUnseen: hasUnseen, hasUnseenCloseFriends: hasUnseenCloseFriends, + hasLiveItems: hasLiveItems, hasPending: maxPendingTimestamp != nil, storyCount: itemsView.items.count, unseenCount: unseenCount, @@ -1153,6 +1164,7 @@ public extension TelegramEngine { peer: EnginePeer(peer), hasUnseen: false, hasUnseenCloseFriends: false, + hasLiveItems: false, hasPending: true, storyCount: 0, unseenCount: 0, @@ -1387,7 +1399,7 @@ public extension TelegramEngine { folderIds: item.folderIds )) 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) } + public func beginStoryLivestream() -> Signal { + return _internal_beginStoryLivestream(account: self.account) + } + public func allStoriesUploadEvents() -> Signal<(Int32, Int32), NoError> { guard let pendingStoryManager = self.account.pendingStoryManager else { return .complete() diff --git a/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift b/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift index 98a8c1e5e9..671acfb779 100644 --- a/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift @@ -30,7 +30,7 @@ public final class ChatAvatarNavigationNode: ASDisplayNode { private var avatarVideoNode: AvatarVideoNode? public private(set) var avatarStoryView: ComponentView? - public var storyData: (hasUnseen: Bool, hasUnseenCloseFriends: Bool)? + public var storyData: (hasUnseen: Bool, hasUnseenCloseFriends: Bool, hasLiveItems: Bool)? public var statusView: ComponentView private var starView: StarView? @@ -241,6 +241,7 @@ public final class ChatAvatarNavigationNode: ASDisplayNode { component: AnyComponent(AvatarStoryIndicatorComponent( hasUnseen: storyData.hasUnseen, hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends, + hasLiveItems: storyData.hasLiveItems, colors: AvatarStoryIndicatorComponent.Colors(theme: theme), activeLineWidth: 1.0, inactiveLineWidth: 1.0, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift index 3b701f3940..7a0c2f956b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift @@ -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 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 { @@ -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 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) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift index aeac8f0823..9cbaf3996a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift @@ -875,6 +875,10 @@ public final class ChatMessageDateHeaderNodeImpl: ListViewItemHeaderNode, ChatMe } } +private func avatarHeaderSize() -> CGFloat { + return 34.0 +} + public final class ChatMessageAvatarHeader: ListViewItemHeader { public struct Id: Hashable { public var peerId: PeerId @@ -923,7 +927,7 @@ public final class ChatMessageAvatarHeader: ListViewItemHeader { public let stickDirection: ListViewItemHeaderStickDirection public let stickOverInsets: Bool = false - public let height: CGFloat = 40.0 + public let height: CGFloat = avatarHeaderSize() public func combinesWith(other: ListViewItemHeader) -> Bool { 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 { self.peer = peer 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 { - 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 } 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 { - 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 { @@ -1111,7 +1115,7 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat strongSelf.avatarNode.contentNode.addSubnode(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 { 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) { - transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: leftInset + 8.0, y: 0.0), size: CGSize(width: 40.0, height: 40.0))) - let avatarFrame = CGRect(origin: CGPoint(), 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: avatarHeaderSize(), height: avatarHeaderSize())) self.avatarNode.position = avatarFrame.center self.avatarNode.bounds = CGRect(origin: CGPoint(), size: avatarFrame.size) self.avatarNode.updateSize(size: avatarFrame.size) @@ -1265,7 +1269,7 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat var avatarTransform: CATransform3D = CATransform3DIdentity if isHidden { 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) } transition.updateTransform(node: self.avatarNode, transform: avatarTransform) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift index 510bb9cae0..61fa256f8d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift @@ -64,7 +64,7 @@ private final class ChatMessageSelectionInputPanelNodeViewForOverlayContent: UIV } } -private final class GlassButtonView: HighlightTrackingButton { +private final class GlassButtonView: UIView { private struct Params: Equatable { let theme: PresentationTheme let size: CGSize @@ -86,6 +86,7 @@ private final class GlassButtonView: HighlightTrackingButton { } private let backgroundView: GlassBackgroundView + let button: HighlightTrackingButton private let iconView: GlassBackgroundView.ContentImageView private var params: Params? @@ -96,7 +97,7 @@ private final class GlassButtonView: HighlightTrackingButton { } } - override var isEnabled: Bool { + var isEnabled: Bool = true { didSet { self.updateIsEnabled() } @@ -125,19 +126,25 @@ private final class GlassButtonView: HighlightTrackingButton { self.iconView = GlassBackgroundView.ContentImageView() self.backgroundView.contentView.addSubview(self.iconView) + self.button = HighlightTrackingButton() + self.backgroundView.contentView.addSubview(self.button) + super.init(frame: frame) self.addSubview(self.backgroundView) - self.highligthedChanged = { [weak self] highlighted in - guard let self else { - return - } - if highlighted && self.isEnabled && !self.isImplicitlyDisabled { - self.backgroundView.contentView.alpha = 0.6 - } else { - self.backgroundView.contentView.alpha = 1.0 - self.backgroundView.contentView.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2) + if #available(iOS 26.0, *) { + } else { + self.button.highligthedChanged = { [weak self] highlighted in + guard let self else { + return + } + if highlighted && self.isEnabled && !self.isImplicitlyDisabled { + self.backgroundView.contentView.alpha = 0.6 + } 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) { + let isEnabled = self.isEnabled && !self.isImplicitlyDisabled + if let image = self.iconView.image { let iconFrame = image.size.centered(in: CGRect(origin: CGPoint(), size: params.size)) transition.setFrame(view: self.iconView, frame: iconFrame) } - 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)), transition: transition) + transition.setFrame(view: self.button, frame: CGRect(origin: CGPoint(), size: params.size)) + + 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.tintMask.alpha = self.iconView.alpha + + self.button.isEnabled = isEnabled } private func updateIsEnabled() { @@ -255,12 +267,12 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { self.forwardButton.isImplicitlyDisabled = true self.shareButton.isImplicitlyDisabled = true - self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: .touchUpInside) - self.reportButton.addTarget(self, action: #selector(self.reportButtonPressed), for: .touchUpInside) - self.forwardButton.addTarget(self, action: #selector(self.forwardButtonPressed), for: .touchUpInside) - self.shareButton.addTarget(self, action: #selector(self.shareButtonPressed), for: .touchUpInside) - self.tagButton.addTarget(self, action: #selector(self.tagButtonPressed), for: .touchUpInside) - self.tagEditButton.addTarget(self, action: #selector(self.tagButtonPressed), for: .touchUpInside) + self.deleteButton.button.addTarget(self, action: #selector(self.deleteButtonPressed), for: .touchUpInside) + self.reportButton.button.addTarget(self, action: #selector(self.reportButtonPressed), for: .touchUpInside) + self.forwardButton.button.addTarget(self, action: #selector(self.forwardButtonPressed), for: .touchUpInside) + self.shareButton.button.addTarget(self, action: #selector(self.shareButtonPressed), for: .touchUpInside) + self.tagButton.button.addTarget(self, action: #selector(self.tagButtonPressed), for: .touchUpInside) + self.tagEditButton.button.addTarget(self, action: #selector(self.tagButtonPressed), for: .touchUpInside) } deinit { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/Sources/ChatMessageStoryMentionContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/Sources/ChatMessageStoryMentionContentNode.swift index 22ee4a4033..3e805b7cb6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/Sources/ChatMessageStoryMentionContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/Sources/ChatMessageStoryMentionContentNode.swift @@ -286,6 +286,7 @@ public class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode { component: AnyComponent(AvatarStoryIndicatorComponent( hasUnseen: hasUnseen, hasUnseenCloseFriendsItems: hasUnseen && (story?.isCloseFriends ?? false), + hasLiveItems: false, colors: storyColors, activeLineWidth: 3.0, inactiveLineWidth: 1.0 + UIScreenPixel, diff --git a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift index 70d7a6d2da..fea1f311ee 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift @@ -273,7 +273,7 @@ public final class ChatFloatingTopicsPanel: Component { } 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 } diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift index 26889d8e14..60c23a8ece 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift @@ -231,7 +231,7 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag self.micButton.layer.allowsGroupOpacity = true self.view.addSubview(self.micButtonBackgroundView) - self.view.addSubview(self.micButton) + self.micButtonBackgroundView.contentView.addSubview(self.micButton) self.addSubnode(self.sendContainerNode) self.sendContainerNode.view.addSubview(self.sendButtonBackgroundView) diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift index 545a372a04..5757aaec60 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift @@ -2405,7 +2405,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg if useBounceAnimation, case let .animated(_, curve) = transition, case .spring = curve { 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.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)) 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 } diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift index ef2b216c7d..c0ce3d9f34 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift @@ -545,8 +545,10 @@ public final class GlassBackgroundContainerView: UIView { return result } - public func update(size: CGSize, transition: ComponentTransition) { + public func update(size: CGSize, isDark: Bool, transition: ComponentTransition) { if let nativeView = self.nativeView { + nativeView.overrideUserInterfaceStyle = isDark ? .dark : .light + transition.setFrame(view: nativeView, frame: CGRect(origin: CGPoint(), size: size)) } else if let legacyView = self.legacyView { transition.setFrame(view: legacyView, frame: CGRect(origin: CGPoint(), size: size)) diff --git a/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift b/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift index 868119c776..84c4516d54 100644 --- a/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift @@ -160,9 +160,9 @@ public final class MessageInputActionButtonComponent: Component { case up } - public enum Style { + public enum Style: Equatable { case legacy - case glass + case glass(isTinted: Bool) } public let mode: Mode @@ -519,7 +519,7 @@ public final class MessageInputActionButtonComponent: Component { microphoneAlpha = 0.4 } - if component.style == .glass, [.send, .close].contains(component.mode) { + if case let .glass(isTinted) = component.style { let backgroundView: GlassBackgroundView if let current = self.backgroundView { backgroundView = current @@ -534,8 +534,16 @@ public final class MessageInputActionButtonComponent: Component { if case .send = component.mode { 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) - 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) } diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index 93db1c9112..355641cd76 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -809,18 +809,16 @@ public final class MessageInputPanelComponent: Component { insets.left = 41.0 } if let _ = component.setMediaRecordingActive { - insets.right = 41.0 + insets.right = 40.0 + 8.0 * 2.0 } - let textFieldSideInset: CGFloat - switch component.style { - case .media, .glass: - textFieldSideInset = 8.0 - default: - textFieldSideInset = 9.0 + var textFieldSideInset: CGFloat = 8.0 + if component.bottomInset <= 32.0 && !component.forceIsEditing && !component.hideKeyboard && !self.textFieldExternalState.isEditing { + textFieldSideInset += 18.0 + insets.right += 18.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 { 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)) switch component.style { - case .glass: + case .glass, .story: if self.fieldGlassBackgroundView == nil { let fieldGlassBackgroundView = GlassBackgroundView(frame: fieldBackgroundFrame) self.insertSubview(fieldGlassBackgroundView, aboveSubview: self.fieldBackgroundView) @@ -1105,7 +1103,7 @@ public final class MessageInputPanelComponent: Component { self.fieldBackgroundTint.isHidden = true } 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) } default: @@ -1551,6 +1549,9 @@ public final class MessageInputPanelComponent: Component { inputActionButtonMode = .close } } else { + if case .story = component.style { + inputActionButtonAvailableSize = CGSize(width: 40.0, height: 40.0) + } if hasMediaEditing { inputActionButtonMode = .send } 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( transition: transition, component: AnyComponent(MessageInputActionButtonComponent( mode: inputActionButtonMode, - style: component.style == .glass ? .glass : .legacy, + style: inputActionButtonStyle, storyId: component.storyItem?.id, action: { [weak self] mode, action, sendAction in guard let self, let component = self.component else { @@ -1690,26 +1699,22 @@ public final class MessageInputPanelComponent: Component { if rightButtonsOffsetX != 0.0 { inputActionButtonOriginX = availableSize.width - 3.0 + rightButtonsOffsetX if displayLikeAction { - inputActionButtonOriginX -= 39.0 + inputActionButtonOriginX -= 40.0 + 8.0 } if component.forwardAction != nil { - inputActionButtonOriginX -= 46.0 + inputActionButtonOriginX -= 40.0 + 8.0 } } else { if component.setMediaRecordingActive != nil || isEditing || component.style == .glass { switch component.style { - case .glass: - inputActionButtonOriginX = fieldBackgroundFrame.maxX + 6.0 + case .glass, .story: + inputActionButtonOriginX = fieldBackgroundFrame.maxX + 8.0 default: inputActionButtonOriginX = fieldBackgroundFrame.maxX + floorToScreenPixels((41.0 - inputActionButtonSize.width) * 0.5) } } else { inputActionButtonOriginX = size.width } - - if hasLikeAction { - inputActionButtonOriginX += 3.0 - } } 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.setAlpha(view: inputActionButtonView, alpha: likeActionReplacesInputAction ? 0.0 : inputActionButtonAlpha) - if rightButtonsOffsetX != 0.0 { - if hasLikeAction { - inputActionButtonOriginX += 46.0 - } - } else { - if hasLikeAction { - inputActionButtonOriginX += 41.0 - } + if hasLikeAction { + inputActionButtonOriginX += 40.0 + 8.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( transition: transition, component: AnyComponent(MessageInputActionButtonComponent( mode: .like(reaction: component.myReaction?.reaction, file: component.myReaction?.file, animationFileId: component.myReaction?.animationFileId), + style: likeActionButtonStyle, storyId: component.storyItem?.id, action: { [weak self] _, action, _ in guard let self, let component = self.component else { @@ -1770,7 +1781,7 @@ public final class MessageInputPanelComponent: Component { videoRecordingStatus: nil )), environment: {}, - containerSize: CGSize(width: 33.0, height: 33.0) + containerSize: likeButtonContainerSize ) if let likeButtonView = self.likeButton.view { if likeButtonView.superview == nil { @@ -1783,7 +1794,7 @@ public final class MessageInputPanelComponent: Component { transition.setPosition(view: likeButtonView, position: likeButtonFrame.center) transition.setBounds(view: likeButtonView, bounds: CGRect(origin: CGPoint(), size: likeButtonFrame.size)) transition.setAlpha(view: likeButtonView, alpha: displayLikeAction ? 1.0 : 0.0) - inputActionButtonOriginX += 41.0 + inputActionButtonOriginX += 40.0 + 8.0 } var fieldIconNextX = fieldBackgroundFrame.maxX - 4.0 @@ -1855,7 +1866,7 @@ public final class MessageInputPanelComponent: Component { component: AnyComponent(Button( content: AnyComponent(LottieComponent( content: LottieComponent.AppBundleContent(name: animationName), - color: .white + color: defaultDarkPresentationTheme.chat.inputPanel.inputControlColor )), action: { [weak self] in guard let self else { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift index e7ce0f314a..ef18776e1b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift @@ -183,7 +183,8 @@ private enum PeerMembersListEntry: Comparable, Identifiable { return ( total: storyStats.totalCount, unseen: storyStats.unseenCount, - hasUnseenCloseFriends: storyStats.hasUnseenCloseFriends + hasUnseenCloseFriends: storyStats.hasUnseenCloseFriends, + hasLiveItems: storyStats.hasLiveItems ) }, openStories: { _, sourceNode in diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift index 1b987ae877..aaf95233c6 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift @@ -52,7 +52,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { 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? init(context: AccountContext) { @@ -146,6 +146,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { totalCount: storyData.totalCount, unseenCount: storyData.unseenCount, hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends, + hasLiveItems: storyData.hasLiveItems, progress: self.storyProgress ) } else if let storyProgress = self.storyProgress { @@ -153,6 +154,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { totalCount: 1, unseenCount: 1, hasUnseenCloseFriendsItems: false, + hasLiveItems: false, progress: storyProgress ) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index fbc78afd2f..610273851e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -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 switch item { case let .item(item): @@ -8997,7 +8997,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if channel.hasPermission(.manageCalls) { canCreateStream = true credentialsPromise = Promise() - credentialsPromise?.set(context.engine.calls.getGroupCallStreamCredentials(peerId: peerId, revokePreviousCredentials: false) |> `catch` { _ -> Signal in return .never() }) + credentialsPromise?.set(context.engine.calls.getGroupCallStreamCredentials(peerId: peerId, isLiveStream: false, revokePreviousCredentials: false) |> `catch` { _ -> Signal in return .never() }) } default: break diff --git a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift index 72ab6cdebf..8d7473a8eb 100644 --- a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift +++ b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift @@ -420,6 +420,7 @@ public final class AvatarStoryIndicatorComponent: Component { public let hasUnseen: Bool public let hasUnseenCloseFriendsItems: Bool + public let hasLiveItems: Bool public let colors: Colors public let activeLineWidth: CGFloat public let inactiveLineWidth: CGFloat @@ -430,6 +431,7 @@ public final class AvatarStoryIndicatorComponent: Component { public init( hasUnseen: Bool, hasUnseenCloseFriendsItems: Bool, + hasLiveItems: Bool, colors: Colors, activeLineWidth: CGFloat, inactiveLineWidth: CGFloat, @@ -439,6 +441,7 @@ public final class AvatarStoryIndicatorComponent: Component { ) { self.hasUnseen = hasUnseen self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems + self.hasLiveItems = hasLiveItems self.colors = colors self.activeLineWidth = activeLineWidth self.inactiveLineWidth = inactiveLineWidth @@ -454,6 +457,9 @@ public final class AvatarStoryIndicatorComponent: Component { if lhs.hasUnseenCloseFriendsItems != rhs.hasUnseenCloseFriendsItems { return false } + if lhs.hasLiveItems != rhs.hasLiveItems { + return false + } if lhs.colors != rhs.colors { return false } @@ -659,7 +665,10 @@ public final class AvatarStoryIndicatorComponent: Component { let activeColors: [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) } else { activeColors = component.colors.unseenColors.map(\.cgColor) @@ -681,7 +690,7 @@ public final class AvatarStoryIndicatorComponent: Component { 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 { let lineWidth: CGFloat = component.hasUnseen ? component.activeLineWidth : component.inactiveLineWidth context.setLineWidth(lineWidth) @@ -839,7 +848,9 @@ public final class AvatarStoryIndicatorComponent: Component { context.clip() let colors: [CGColor] - if component.hasUnseen { + if component.hasLiveItems { + colors = activeColors + } else if component.hasUnseen { colors = activeColors } else { colors = inactiveColors diff --git a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift index 357c09f126..6567c1d2a5 100644 --- a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift @@ -940,7 +940,8 @@ public final class PeerListItemComponent: Component { return AvatarNode.StoryStats( totalCount: storyStats.totalCount == 0 ? 0 : 1, unseenCount: storyStats.unseenCount == 0 ? 0 : 1, - hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends + hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends, + hasLiveItems: storyStats.hasLiveItems ) }, presentationParams: AvatarNode.StoryPresentationParams( colors: AvatarNode.Colors(theme: component.theme), diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index fa48c5791b..d6721f949a 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -101,6 +101,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController", "//submodules/DirectMediaImageCache", "//submodules/PromptUI", + "//submodules/TelegramCallsUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift index f43af6dd16..411635183b 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift @@ -23,8 +23,9 @@ final class StoryAuthorInfoComponent: Component { let timestamp: Int32 let counters: Counters? 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.strings = strings self.peer = peer @@ -33,6 +34,7 @@ final class StoryAuthorInfoComponent: Component { self.timestamp = timestamp self.counters = counters self.isEdited = isEdited + self.isLiveStream = isLiveStream } static func ==(lhs: StoryAuthorInfoComponent, rhs: StoryAuthorInfoComponent) -> Bool { @@ -59,6 +61,9 @@ final class StoryAuthorInfoComponent: Component { } if lhs.isEdited != rhs.isEdited { return false + } + if lhs.isLiveStream != rhs.isLiveStream { + return false } return true } @@ -68,6 +73,7 @@ final class StoryAuthorInfoComponent: Component { private var repostIconView: UIImageView? private var avatarNode: AvatarNode? private let subtitle = ComponentView() + private var liveBadgeView: UIImageView? private var counterLabel: ComponentView? private var component: StoryAuthorInfoComponent? @@ -238,7 +244,43 @@ final class StoryAuthorInfoComponent: Component { 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 titleView.superview == nil { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAvatarInfoComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAvatarInfoComponent.swift index f3c2fa910e..76ddc363b2 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAvatarInfoComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAvatarInfoComponent.swift @@ -10,10 +10,12 @@ import AvatarNode final class StoryAvatarInfoComponent: Component { let context: AccountContext let peer: EnginePeer + let isLiveStream: Bool - init(context: AccountContext, peer: EnginePeer) { + init(context: AccountContext, peer: EnginePeer, isLiveStream: Bool) { self.context = context self.peer = peer + self.isLiveStream = isLiveStream } static func ==(lhs: StoryAvatarInfoComponent, rhs: StoryAvatarInfoComponent) -> Bool { @@ -23,6 +25,9 @@ final class StoryAvatarInfoComponent: Component { if lhs.peer != rhs.peer { return false } + if lhs.isLiveStream != rhs.isLiveStream { + return false + } return true } @@ -57,6 +62,24 @@ final class StoryAvatarInfoComponent: Component { peer: component.peer, 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 } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift index f3b505feae..738974715e 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift @@ -649,6 +649,7 @@ public final class StoryContentContextImpl: StoryContentContext { peer: peer, hasUnseen: state.hasUnseen, hasUnseenCloseFriends: state.hasUnseenCloseFriends, + hasLiveItems: false, hasPending: false, storyCount: state.items.count, unseenCount: 0, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index f7adeea7cd..e789cd5cf6 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -14,6 +14,7 @@ import HierarchyTrackingLayer import ButtonComponent import MultilineTextComponent import TelegramPresentationData +import TelegramCallsUI final class StoryItemContentComponent: Component { typealias EnvironmentType = StoryContentItem.Environment @@ -92,6 +93,8 @@ final class StoryItemContentComponent: Component { private let imageView: StoryItemImageView private let overlaysView: StoryItemOverlaysView private var videoNode: UniversalVideoNode? + private var mediaStreamCall: PresentationGroupCallImpl? + private var mediaStream: ComponentView? private var loadingEffectView: StoryItemLoadingEffectView? private var loadingEffectAppearanceTimer: SwiftSignalKit.Timer? @@ -385,6 +388,10 @@ final class StoryItemContentComponent: Component { if !self.isSeeking { self.updateVideoPlaybackProgress() } + } else if case .liveStream = self.currentMessageMedia { + if !self.isSeeking { + self.updateVideoPlaybackProgress() + } } else { if !self.markedAsSeen { self.markedAsSeen = true @@ -599,7 +606,10 @@ final class StoryItemContentComponent: Component { let selectedMedia: 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 switch alternativeMediaValue { @@ -628,7 +638,7 @@ final class StoryItemContentComponent: Component { } var reloadMedia = false - if self.currentMessageMedia?.id != messageMedia?.id { + if self.currentMessageMedia?.id != messageMedia?.id || (self.currentMessageMedia == nil) != (messageMedia == nil) { self.currentMessageMedia = messageMedia reloadMedia = true @@ -707,47 +717,154 @@ final class StoryItemContentComponent: Component { } } - 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) - } + if case let .liveStream(liveStream) = messageMedia { + var mediaStreamTransition = transition + let mediaStream: ComponentView + if let current = self.mediaStream { + mediaStream = current + } else { + mediaStreamTransition = mediaStreamTransition.withAnimation(.none) + mediaStream = ComponentView() + 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? - switch messageMedia { - case let .image(image): - dimensions = image.representations.last?.dimensions.cgSize - case let .file(file): - dimensions = file.dimensions?.cgSize - default: - break + let mediaStreamCall: PresentationGroupCallImpl + if let current = self.mediaStreamCall { + mediaStreamCall = current + } else { + let initialCall = EngineGroupCallDescription( + id: liveStream.call.id, + accessHash: liveStream.call.accessHash, + 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): dimensions = image.representations.last?.dimensions.cgSize case let .file(file): @@ -755,28 +872,38 @@ final class StoryItemContentComponent: Component { default: break } - } - - if let dimensions { - var imageSize = dimensions.aspectFilled(availableSize) - if imageSize.width < availableSize.width && imageSize.width >= availableSize.width - 5.0 { - imageSize.width = availableSize.width + if dimensions == nil { + switch component.item.media { + case let .image(image): + dimensions = image.representations.last?.dimensions.cgSize + case let .file(file): + 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 { - 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) + if let dimensions { + var imageSize = dimensions.aspectFilled(availableSize) + if imageSize.width < availableSize.width && imageSize.width >= availableSize.width - 5.0 { + 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 { - case .image, .file: + case .image, .file, .liveStream: if let unsupportedText = self.unsupportedText { self.unsupportedText = nil unsupportedText.view?.removeFromSuperview() diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 73fec31551..d4f34faf1d 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -1688,7 +1688,11 @@ public final class StoryItemSetContainerComponent: Component { } } } 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 { displayFooter = true } 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 - 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 } else if case let .channel(channel) = component.slice.effectivePeer, channel.flags.contains(.isCreator) || component.slice.additionalPeerData.canViewStats { displayViewLists = true @@ -1941,7 +1947,9 @@ public final class StoryItemSetContainerComponent: Component { return true } else { 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 if component.slice.effectivePeer.id == component.context.account.peerId { @@ -1969,7 +1977,9 @@ public final class StoryItemSetContainerComponent: Component { } 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 if component.slice.effectivePeer.id == component.context.account.peerId { @@ -2760,7 +2770,11 @@ public final class StoryItemSetContainerComponent: Component { switch channel.info { case .broadcast: isChannel = true - showMessageInputPanel = false + if case .liveStream = component.slice.item.storyItem.media { + showMessageInputPanel = true + } else { + showMessageInputPanel = false + } case .group: if let bannedSendText = channel.hasBannedPermission(.banSendText, ignoreDefault: canBypassRestrictions) { if bannedSendText.1 || component.slice.additionalPeerData.boostsToUnrestrict == nil { @@ -2772,7 +2786,11 @@ public final class StoryItemSetContainerComponent: Component { isGroup = true } } 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 { showMessageInputPanel = false @@ -2834,6 +2852,9 @@ public final class StoryItemSetContainerComponent: Component { if let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars { let dateTimeFormat = component.context.sharedContext.currentPresentationData.with { $0 }.dateTimeFormat 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 { inputPlaceholder = .plain(isGroup ? component.strings.Story_InputPlaceholderReplyInGroup : component.strings.Story_InputPlaceholderReplyPrivately) } @@ -2859,6 +2880,8 @@ public final class StoryItemSetContainerComponent: Component { self.inputPanel.parentState = state var inputPanelSize: CGSize? + let _ = inputNodeVisible + let startTime23 = CFAbsoluteTimeGetCurrent() 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( transition: inputPanelTransition, component: AnyComponent(MessageInputPanelComponent( @@ -2949,13 +2980,13 @@ public final class StoryItemSetContainerComponent: Component { self.sendMessageContext.videoRecorderValue?.dismissVideo() self.sendMessageContext.discardMediaRecordingPreview(view: self) }, - attachmentAction: component.slice.effectivePeer.isService ? nil : { [weak self] in + attachmentAction: !displayAttachmentAction ? nil : { [weak self] in guard let self else { return } 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 var centerAnimation: TelegramMediaFile? var animationFileId: Int64? @@ -3082,7 +3113,7 @@ public final class StoryItemSetContainerComponent: Component { timeoutValue: nil, timeoutSelected: false, displayGradient: false, - bottomInset: component.inputHeight != 0.0 || inputNodeVisible ? 0.0 : bottomContentInset, + bottomInset: max(bottomContentInset, component.inputHeight), isFormattingLocked: false, hideKeyboard: self.sendMessageContext.currentInputMode == .media, customInputView: nil, @@ -3176,7 +3207,7 @@ public final class StoryItemSetContainerComponent: Component { inputPanelIsOverlay = false } else { bottomContentInset += 44.0 - inputPanelBottomInset = inputHeight - inputPanelInset + inputPanelBottomInset = inputHeight - inputPanelInset + 3.0 inputPanelIsOverlay = true } @@ -3191,7 +3222,9 @@ public final class StoryItemSetContainerComponent: Component { var validViewListIds: [StoryId] = [] 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 } else if case let .channel(channel) = component.slice.effectivePeer, channel.flags.contains(.isCreator) || component.slice.additionalPeerData.canViewStats { displayViewLists = true @@ -4028,9 +4061,14 @@ public final class StoryItemSetContainerComponent: Component { let focusedItem: StoryContentItem? = component.slice.item + var isLiveStream = false + if case .liveStream = component.slice.item.storyItem.media { + isLiveStream = true + } + var currentLeftInfoItem: InfoItem? 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 { currentLeftInfoItem = leftInfoItem } else { @@ -4066,7 +4104,8 @@ public final class StoryItemSetContainerComponent: Component { author: component.slice.item.storyItem.author, timestamp: component.slice.item.storyItem.timestamp, counters: counters, - isEdited: component.slice.item.storyItem.isEdited + isEdited: component.slice.item.storyItem.isEdited, + isLiveStream: isLiveStream )) if let centerInfoItem = self.centerInfoItem, centerInfoItem.component == centerInfoComponent { currentCenterInfoItem = centerInfoItem @@ -4176,7 +4215,13 @@ public final class StoryItemSetContainerComponent: Component { if let inputPanelSize { let inputPanelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - inputPanelSize.width) / 2.0), y: availableSize.height - inputPanelBottomInset - inputPanelSize.height), size: inputPanelSize) 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 { inputPanelAlpha *= component.visibilityFraction } @@ -4465,14 +4510,21 @@ public final class StoryItemSetContainerComponent: Component { if let current = self.reactionContextNode { reactionContextNode = current } 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 reactionContextNode = ReactionContextNode( context: component.context, animationCache: component.context.animationCache, presentationData: component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme), + style: .glass(isTinted: false), items: reactionItems.map { ReactionContextItem.reaction(item: $0, icon: .none) }, 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, alwaysAllowPremiumReactions: false, allPresetReactionsAreAvailable: false, @@ -4982,12 +5034,21 @@ public final class StoryItemSetContainerComponent: Component { } component.externalState.derivedMediaSize = contentFrame.size - 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) + + if case .liveStream = component.slice.item.storyItem.media { + if let inputPanelFrameValue { + component.externalState.derivedBottomInset = availableSize.height - min(inputPanelFrameValue.minY, contentFrame.maxY) + } else { + component.externalState.derivedBottomInset = availableSize.height - itemsContainerFrame.maxY + } } 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 { diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index 502f8a5f53..c9c1f9a1bc 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -1054,6 +1054,7 @@ public final class StoryPeerListComponent: Component { } var hasUnseenCloseFriendsItems = itemSet.hasUnseenCloseFriends + let hasLiveItems = itemSet.hasLiveItems var hasItems = true var itemRingAnimation: StoryPeerListItemComponent.RingAnimation? @@ -1139,6 +1140,7 @@ public final class StoryPeerListComponent: Component { totalCount: totalCount, unseenCount: unseenCount, hasUnseenCloseFriendsItems: hasUnseenCloseFriendsItems, + hasLiveItems: hasLiveItems, hasItems: hasItems, ringAnimation: itemRingAnimation, scale: itemScale, @@ -1278,6 +1280,7 @@ public final class StoryPeerListComponent: Component { totalCount: 1, unseenCount: itemSet.unseenCount != 0 ? 1 : 0, hasUnseenCloseFriendsItems: hasUnseenCloseFriendsItems, + hasLiveItems: itemSet.hasLiveItems, hasItems: hasItems, ringAnimation: itemRingAnimation, scale: itemScale, diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift index 2d1ca06a6e..6b1059505a 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift @@ -373,6 +373,7 @@ public final class StoryPeerListItemComponent: Component { public let totalCount: Int public let unseenCount: Int public let hasUnseenCloseFriendsItems: Bool + public let hasLiveItems: Bool public let hasItems: Bool public let ringAnimation: RingAnimation? public let scale: CGFloat @@ -393,6 +394,7 @@ public final class StoryPeerListItemComponent: Component { totalCount: Int, unseenCount: Int, hasUnseenCloseFriendsItems: Bool, + hasLiveItems: Bool, hasItems: Bool, ringAnimation: RingAnimation?, scale: CGFloat, @@ -412,6 +414,7 @@ public final class StoryPeerListItemComponent: Component { self.totalCount = totalCount self.unseenCount = unseenCount self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems + self.hasLiveItems = hasLiveItems self.hasItems = hasItems self.ringAnimation = ringAnimation self.scale = scale @@ -447,6 +450,9 @@ public final class StoryPeerListItemComponent: Component { if lhs.hasUnseenCloseFriendsItems != rhs.hasUnseenCloseFriendsItems { return false } + if lhs.hasLiveItems != rhs.hasLiveItems { + return false + } if lhs.hasItems != rhs.hasItems { return false } @@ -494,6 +500,9 @@ public final class StoryPeerListItemComponent: Component { private let avatarBackgroundView: UIImageView private var avatarNode: AvatarNode? private var avatarAddBadgeView: UIImageView? + private var avatarLiveBadgeView: UIImageView? + private var avatarLiveBadgeMaskSeenLayer: SimpleLayer? + private var avatarLiveBadgeMaskUnseenLayer: SimpleLayer? private let avatarShapeLayer: SimpleShapeLayer private let indicatorMaskSeenLayer: SimpleLayer private let indicatorMaskUnseenLayer: SimpleLayer @@ -551,6 +560,8 @@ public final class StoryPeerListItemComponent: Component { super.init(frame: frame) + self.layer.allowsGroupOpacity = true + self.extractedContainerNode.contentNode.view.addSubview(self.extractedBackgroundView) 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.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.indicatorMaskUnseenLayer.addSublayer(self.indicatorShapeUnseenLayer) 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 let baseRadius: CGFloat = 30.66 @@ -788,7 +881,10 @@ public final class StoryPeerListItemComponent: Component { let seenColors: [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] } else { 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() 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 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 { @@ -839,8 +937,8 @@ public final class StoryPeerListItemComponent: Component { } 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.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.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.hasLiveItems ? 1 : component.totalCount, unseenCount: component.hasLiveItems ? 1 : component.unseenCount, isSeen: false, segmentFraction: component.expandedAlphaFraction, rotationFraction: component.expandEffectFraction)) let titleString: String if component.peer.id == component.context.account.peerId { @@ -899,7 +997,7 @@ public final class StoryPeerListItemComponent: Component { let titleSize = self.title.update( transition: .immediate, 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 )), environment: {}, diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index f91e1991c2..7f07a96e9f 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -264,7 +264,8 @@ extension ChatControllerImpl { return AvatarNode.StoryStats( totalCount: storyStats.totalCount, unseenCount: storyStats.unseenCount, - hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends + hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends, + hasLiveItems: storyStats.hasLiveItems ) }, presentationParams: AvatarNode.StoryPresentationParams( colors: AvatarNode.Colors(theme: self.presentationData.theme), diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 512d85cc38..584843c3c4 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -2446,6 +2446,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { 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) + if containerInsets.bottom <= 32.0 { + navigateButtonsFrame.origin.x -= 18.0 + } if case .overlay = self.chatPresentationInterfaceState.mode { navigateButtonsFrame = navigateButtonsFrame.offsetBy(dx: -8.0, dy: -8.0) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 2c070e7f58..f4ced25eca 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3967,6 +3967,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { completion: completion ) } + + public func makeChannelMembersSearchController(params: ChannelMembersSearchControllerParams) -> ChannelMembersSearchController { + return ChannelMembersSearchControllerImpl(params: params) + } } private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? { diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 097135dc7d..6074aaa50a 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -726,6 +726,12 @@ public final class TelegramRootController: NavigationController, TelegramRootCon } if let media { + #if DEBUG + if "".isEmpty { + let _ = context.engine.messages.beginStoryLivestream().startStandalone() + } + #endif + let _ = (context.engine.messages.uploadStory( target: target, media: media, diff --git a/submodules/TooltipUI/Sources/TooltipScreen.swift b/submodules/TooltipUI/Sources/TooltipScreen.swift index 0be12a18fc..e31518a59b 100644 --- a/submodules/TooltipUI/Sources/TooltipScreen.swift +++ b/submodules/TooltipUI/Sources/TooltipScreen.swift @@ -930,6 +930,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { component: AnyComponent(AvatarStoryIndicatorComponent( hasUnseen: true, hasUnseenCloseFriendsItems: false, + hasLiveItems: false, colors: AvatarStoryIndicatorComponent.Colors(theme: defaultDarkPresentationTheme), activeLineWidth: 1.0 + UIScreenPixel, inactiveLineWidth: 1.0 + UIScreenPixel,