Various improvements

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

View file

@ -1134,6 +1134,51 @@ public enum StarsWithdrawalScreenSubject {
case postSuggestionModification(current: CurrencyAmount, timestamp: Int32?, completion: (CurrencyAmount, Int32?) -> Void)
}
public enum ChannelMembersSearchControllerMode {
case promote
case ban
case inviteToCall
}
public enum ChannelMembersSearchFilter {
case exclude([EnginePeer.Id])
case disable([EnginePeer.Id])
case excludeNonMembers
case excludeBots
}
public final class ChannelMembersSearchControllerParams {
public let context: AccountContext
public let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
public let peerId: EnginePeer.Id
public let forceTheme: PresentationTheme?
public let mode: ChannelMembersSearchControllerMode
public let filters: [ChannelMembersSearchFilter]
public let openPeer: (EnginePeer, RenderedChannelParticipant?) -> Void
public init(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
peerId: EnginePeer.Id,
forceTheme: PresentationTheme? = nil,
mode: ChannelMembersSearchControllerMode,
filters: [ChannelMembersSearchFilter] = [],
openPeer: @escaping (EnginePeer, RenderedChannelParticipant?) -> Void
) {
self.context = context
self.updatedPresentationData = updatedPresentationData
self.peerId = peerId
self.forceTheme = forceTheme
self.mode = mode
self.filters = filters
self.openPeer = openPeer
}
}
public protocol ChannelMembersSearchController: ViewController {
var copyInviteLink: (() -> Void)? { get set }
}
public protocol SharedAccountContext: AnyObject {
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<AccountPrivacySettings?>, 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)

View file

@ -1156,6 +1156,11 @@ public final class AvatarNode: ASDisplayNode {
public let contentNode: ContentNode
private var storyIndicator: ComponentView<Empty>?
private var contentMaskView: UIView?
private var storyIndicatorMaskView: UIView?
private var liveBadgeMaskView: UIImageView?
private var liveBadgeStoryIndicatorMaskView: UIImageView?
private var liveBadgeView: UIImageView?
public private(set) var storyPresentationParams: StoryPresentationParams?
private var loadingStatuses = Bag<Disposable>()
@ -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() {

View file

@ -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
)

View file

@ -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
)

View file

@ -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),

View file

@ -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(

View file

@ -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 {

View file

@ -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),

View file

@ -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<GroupCallStreamCredentials, NoError> in
return .never()
}

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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<PresentationData, NoError>)? = 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 {

View file

@ -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()
}

View file

@ -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)
}
}

View file

@ -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())

View file

@ -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,

View file

@ -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 {

View file

@ -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<AnyHashable>, title: String? = nil, reactionsLocked: Bool, alwaysAllowPremiumReactions: Bool, allPresetReactionsAreAvailable: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, requestUpdateOverlayWantsToBeBelowKeyboard: @escaping (ContainedViewLayoutTransition) -> Void) {
@ -508,7 +508,11 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
(self.animationRenderer as? MultiAnimationRendererImpl)?.useYuvA = context.sharedContext.immediateExperimentalUISettings.compressedEmojiCache
self.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()

View file

@ -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,

View file

@ -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,

View file

@ -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) }

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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<Api.Updates>) {
static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, quickReplyShortcutId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
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<Api.Updates>) {
static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, topMsgId: Int32?, replyTo: Api.InputReplyTo?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, videoTimestamp: Int32?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
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<Api.Updates>) {
static func sendMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
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<Api.Updates>) {
static func sendMessage(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
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<Api.phone.GroupCallStreamRtmpUrl>) {
static func getGroupCallStreamRtmpUrl(flags: Int32, peer: Api.InputPeer, revoke: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.GroupCallStreamRtmpUrl>) {
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<Api.Bool>) {
static func sendGroupCallMessage(flags: Int32, call: Api.InputGroupCall, randomId: Int64, message: Api.TextWithEntities, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
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<Api.Updates>) {
static func toggleGroupCallSettings(flags: Int32, call: Api.InputGroupCall, joinMuted: Api.Bool?, messagesEnabled: Api.Bool?, sendPaidMessagesStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
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<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-1294237155)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeString(caption!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(entities!.count))
for item in entities! {
item.serialize(buffer, true)
}}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(privacyRules.count))
for item in privacyRules {
item.serialize(buffer, true)
}
serializeInt64(randomId, buffer: buffer, boxed: false)
return (FunctionDescription(name: "stories.startLive", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("caption", String(describing: caption)), ("entities", String(describing: entities)), ("privacyRules", String(describing: privacyRules)), ("randomId", String(describing: randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.stories {
static func toggleAllStoriesHidden(hidden: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()

View file

@ -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

View file

@ -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

View file

@ -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",

View file

@ -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<GroupCallStreamCredentials>()
credentialsPromise.set(call.accountContext.engine.calls.getGroupCallStreamCredentials(peerId: peerId, revokePreviousCredentials: false) |> `catch` { _ -> Signal<GroupCallStreamCredentials, NoError> in return .never() })
credentialsPromise.set(call.accountContext.engine.calls.getGroupCallStreamCredentials(peerId: peerId, isLiveStream: false, revokePreviousCredentials: false) |> `catch` { _ -> Signal<GroupCallStreamCredentials, NoError> in return .never() })
items.append(.action(ContextMenuActionItem(id: nil, text: presentationData.strings.LiveStream_ViewCredentials, textColor: .primary, textLayout: .singleLine, textFont: .regular, badge: nil, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor, backgroundColor: nil)

View file

@ -13,7 +13,7 @@ import Postbox
import TelegramVoip
import 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<Void>
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)
}
}

View file

@ -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 {

View file

@ -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,

View file

@ -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?()

View file

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

View file

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

View file

@ -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
}()

View file

@ -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 {

View file

@ -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<Api.Updates?, NoError> in
return .single(nil)

View file

@ -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<Api.Updates, NoError> 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<Api.Updates, NoError> in
return .complete()
}

View file

@ -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)
}
}
}

View file

@ -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
}
}

View file

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

View file

@ -326,7 +326,7 @@ private func pushDeviceContactData(accountPeerId: PeerId, postbox: Postbox, netw
batches = batches
|> mapToSignal { intermediateResult -> Signal<PushDeviceContactsResult, NoError> in
return network.request(Api.functions.contacts.importContacts(contacts: zip(0 ..< batch.count, batch).map { index, item -> Api.InputContact in
return .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<Api.contacts.ImportedContacts?, NoError> in

View file

@ -1122,7 +1122,7 @@ public final class PendingMessageManager {
} else if let inputSourcePeerId = forwardPeerIds.first, let inputSourcePeer = transaction.getPeer(inputSourcePeerId).flatMap(apiInputPeer) {
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
}
}

View file

@ -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! {

View file

@ -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 {

View file

@ -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

View file

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

View file

@ -621,7 +621,7 @@ public class JoinGroupCallE2E {
}
}
func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, callId: Int64, reference: InternalGroupCallReference, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil, generateE2E: ((Data?) -> JoinGroupCallE2E?)?) -> Signal<JoinGroupCallResult, JoinGroupCallError> {
func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, callId: Int64, reference: InternalGroupCallReference, isStream: Bool, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil, generateE2E: ((Data?) -> JoinGroupCallE2E?)?) -> Signal<JoinGroupCallResult, JoinGroupCallError> {
enum InternalJoinError {
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<GroupCallParticipantsContext.State, InternalJoinError>
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<GroupCallStreamCredentials, GetGroupCallStreamCredentialsError> {
func _internal_getGroupCallStreamCredentials(account: Account, peerId: PeerId, isLiveStream: Bool, revokePreviousCredentials: Bool) -> Signal<GroupCallStreamCredentials, GetGroupCallStreamCredentialsError> {
return account.postbox.transaction { transaction -> Api.InputPeer? in
return 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<GroupCallInviteLinks, RevokeConferenceInviteLinkError> {
return account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 1, call: reference.apiInputGroupCall, joinMuted: .boolFalse, messagesEnabled: nil))
return account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 1, call: reference.apiInputGroupCall, joinMuted: .boolFalse, messagesEnabled: nil, sendPaidMessagesStars: nil))
|> mapError { _ -> RevokeConferenceInviteLinkError in
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())
}
})

View file

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

View file

@ -6,7 +6,7 @@ import SwiftSignalKit
func _internal_importContact(account: Account, firstName: String, lastName: String, phoneNumber: String) -> Signal<PeerId?, NoError> {
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)

View file

@ -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)
}
}
}

View file

@ -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<Api.Updates?, NoError> in
return .single(nil)

View file

@ -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
}
}
}

View file

@ -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<Api.Updates?, NoError> in
return .single(nil)

View file

@ -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<Never, NoError> {
var flags: Int32 = 0
flags |= 1 << 5
return account.network.request(Api.functions.stories.startLive(flags: flags, peer: .inputPeerSelf, caption: nil, entities: nil, privacyRules: [.inputPrivacyValueAllowAll], randomId: Int64.random(in: Int64.min ... Int64.max)))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { updates -> Signal<Never, NoError> in
if let updates {
account.stateManager.addUpdates(updates)
}
return .complete()
}
}
private struct PendingStoryIdMappingKey: Hashable {
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)
}
}
}

View file

@ -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))
}
}
}

View file

@ -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<Never, NoError> {
return _internal_beginStoryLivestream(account: self.account)
}
public func allStoriesUploadEvents() -> Signal<(Int32, Int32), NoError> {
guard let pendingStoryManager = self.account.pendingStoryManager else {
return .complete()

View file

@ -30,7 +30,7 @@ public final class ChatAvatarNavigationNode: ASDisplayNode {
private var avatarVideoNode: AvatarVideoNode?
public private(set) var avatarStoryView: ComponentView<Empty>?
public var storyData: (hasUnseen: Bool, hasUnseenCloseFriends: Bool)?
public var storyData: (hasUnseen: Bool, hasUnseenCloseFriends: Bool, hasLiveItems: Bool)?
public var statusView: ComponentView<Empty>
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,

View file

@ -144,7 +144,7 @@ public struct ChatMessageItemLayoutConstants {
let instantVideo = ChatMessageItemInstantVideoConstants(insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), dimensions: CGSize(width: 212.0, height: 212.0))
let 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)
}
}

View file

@ -875,6 +875,10 @@ public final class ChatMessageDateHeaderNodeImpl: ListViewItemHeaderNode, ChatMe
}
}
private func avatarHeaderSize() -> CGFloat {
return 34.0
}
public final class ChatMessageAvatarHeader: ListViewItemHeader {
public 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)

View file

@ -64,7 +64,7 @@ private final class ChatMessageSelectionInputPanelNodeViewForOverlayContent: UIV
}
}
private final class GlassButtonView: HighlightTrackingButton {
private final class GlassButtonView: UIView {
private struct Params: Equatable {
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 {

View file

@ -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,

View file

@ -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
}

View file

@ -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)

View file

@ -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
}

View file

@ -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))

View file

@ -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)
}

View file

@ -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 {

View file

@ -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

View file

@ -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
)
}

View file

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

View file

@ -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

View file

@ -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),

View file

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

View file

@ -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<Empty>()
private var liveBadgeView: UIImageView?
private var counterLabel: ComponentView<Empty>?
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 {

View file

@ -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
}

View file

@ -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,

View file

@ -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<Empty>?
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<Empty>
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()

View file

@ -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 {

View file

@ -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,

View file

@ -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: {},

View file

@ -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),

View file

@ -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)
}

View file

@ -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<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {

View file

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

View file

@ -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,