This commit is contained in:
Isaac 2025-10-28 12:41:50 +04:00
parent 284d963b07
commit aa637d96d7
24 changed files with 554 additions and 60 deletions

View file

@ -684,11 +684,12 @@ final class ContextControllerNode: ViewControllerTracingNode, ASScrollViewDelega
self.contentReady.set(.single(true))
let transitionInfo = source.transitionInfo()
if let transitionInfo = transitionInfo {
if let transitionInfo {
let referenceView = transitionInfo.referenceView
self.contentContainerNode.contentNode = .reference(view: referenceView)
self.contentAreaInScreenSpace = transitionInfo.contentAreaInScreenSpace
self.customPosition = transitionInfo.customPosition
var projectedFrame = convertFrame(referenceView.bounds, from: referenceView, to: self.view)
projectedFrame.origin.x += transitionInfo.insets.left
projectedFrame.size.width -= transitionInfo.insets.left + transitionInfo.insets.right

View file

@ -749,7 +749,7 @@ private final class ContextControllerActionsListCustomItemNode: ASDisplayNode, C
getController: self.getController,
actionSelected: { result in
switch result {
case .dismissWithoutContent:
case .default, .dismissWithoutContent:
self.requestDismiss(result)
default:
break

View file

@ -301,7 +301,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[286776671] = { return Api.GeoPoint.parse_geoPointEmpty($0) }
dict[-565420653] = { return Api.GeoPointAddress.parse_geoPointAddress($0) }
dict[-29248689] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) }
dict[-674602536] = { return Api.GroupCall.parse_groupCall($0) }
dict[-273500649] = { return Api.GroupCall.parse_groupCall($0) }
dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) }
dict[-297595771] = { return Api.GroupCallDonor.parse_groupCallDonor($0) }
dict[445316222] = { return Api.GroupCallMessage.parse_groupCallMessage($0) }
@ -588,7 +588,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1834538890] = { return Api.MessageAction.parse_messageActionGameScore($0) }
dict[-1730095465] = { return Api.MessageAction.parse_messageActionGeoProximityReached($0) }
dict[1456486804] = { return Api.MessageAction.parse_messageActionGiftCode($0) }
dict[1818391802] = { return Api.MessageAction.parse_messageActionGiftPremium($0) }
dict[1223234306] = { return Api.MessageAction.parse_messageActionGiftPremium($0) }
dict[1171632161] = { return Api.MessageAction.parse_messageActionGiftStars($0) }
dict[-1465661799] = { return Api.MessageAction.parse_messageActionGiftTon($0) }
dict[-1475391004] = { return Api.MessageAction.parse_messageActionGiveawayLaunch($0) }

View file

@ -1044,7 +1044,7 @@ public extension Api {
case messageActionGameScore(gameId: Int64, score: Int32)
case messageActionGeoProximityReached(fromId: Api.Peer, toId: Api.Peer, distance: Int32)
case messageActionGiftCode(flags: Int32, boostPeer: Api.Peer?, months: Int32, slug: String, currency: String?, amount: Int64?, cryptoCurrency: String?, cryptoAmount: Int64?, message: Api.TextWithEntities?)
case messageActionGiftPremium(flags: Int32, currency: String, amount: Int64, months: Int32, cryptoCurrency: String?, cryptoAmount: Int64?, message: Api.TextWithEntities?)
case messageActionGiftPremium(flags: Int32, currency: String, amount: Int64, days: Int32, cryptoCurrency: String?, cryptoAmount: Int64?, message: Api.TextWithEntities?)
case messageActionGiftStars(flags: Int32, currency: String, amount: Int64, stars: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?)
case messageActionGiftTon(flags: Int32, currency: String, amount: Int64, cryptoCurrency: String, cryptoAmount: Int64, transactionId: String?)
case messageActionGiveawayLaunch(flags: Int32, stars: Int64?)
@ -1235,14 +1235,14 @@ public extension Api {
if Int(flags) & Int(1 << 3) != 0 {serializeInt64(cryptoAmount!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 4) != 0 {message!.serialize(buffer, true)}
break
case .messageActionGiftPremium(let flags, let currency, let amount, let months, let cryptoCurrency, let cryptoAmount, let message):
case .messageActionGiftPremium(let flags, let currency, let amount, let days, let cryptoCurrency, let cryptoAmount, let message):
if boxed {
buffer.appendInt32(1818391802)
buffer.appendInt32(1223234306)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(amount, buffer: buffer, boxed: false)
serializeInt32(months, buffer: buffer, boxed: false)
serializeInt32(days, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(cryptoCurrency!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(cryptoAmount!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)}
@ -1627,8 +1627,8 @@ public extension Api {
return ("messageActionGeoProximityReached", [("fromId", fromId as Any), ("toId", toId as Any), ("distance", distance as Any)])
case .messageActionGiftCode(let flags, let boostPeer, let months, let slug, let currency, let amount, let cryptoCurrency, let cryptoAmount, let message):
return ("messageActionGiftCode", [("flags", flags as Any), ("boostPeer", boostPeer as Any), ("months", months as Any), ("slug", slug as Any), ("currency", currency as Any), ("amount", amount as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("message", message as Any)])
case .messageActionGiftPremium(let flags, let currency, let amount, let months, let cryptoCurrency, let cryptoAmount, let message):
return ("messageActionGiftPremium", [("flags", flags as Any), ("currency", currency as Any), ("amount", amount as Any), ("months", months as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("message", message as Any)])
case .messageActionGiftPremium(let flags, let currency, let amount, let days, let cryptoCurrency, let cryptoAmount, let message):
return ("messageActionGiftPremium", [("flags", flags as Any), ("currency", currency as Any), ("amount", amount as Any), ("days", days as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("message", message as Any)])
case .messageActionGiftStars(let flags, let currency, let amount, let stars, let cryptoCurrency, let cryptoAmount, let transactionId):
return ("messageActionGiftStars", [("flags", flags as Any), ("currency", currency as Any), ("amount", amount as Any), ("stars", stars as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("transactionId", transactionId as Any)])
case .messageActionGiftTon(let flags, let currency, let amount, let cryptoCurrency, let cryptoAmount, let transactionId):
@ -1991,7 +1991,7 @@ public extension Api {
let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.MessageAction.messageActionGiftPremium(flags: _1!, currency: _2!, amount: _3!, months: _4!, cryptoCurrency: _5, cryptoAmount: _6, message: _7)
return Api.MessageAction.messageActionGiftPremium(flags: _1!, currency: _2!, amount: _3!, days: _4!, cryptoCurrency: _5, cryptoAmount: _6, message: _7)
}
else {
return nil

View file

@ -10854,6 +10854,22 @@ public extension Api.functions.phone {
})
}
}
public extension Api.functions.phone {
static func saveDefaultSendAs(call: Api.InputGroupCall, sendAs: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(1097313745)
call.serialize(buffer, true)
sendAs.serialize(buffer, true)
return (FunctionDescription(name: "phone.saveDefaultSendAs", parameters: [("call", String(describing: call)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
}
public extension Api.functions.phone {
static func sendConferenceCallBroadcast(call: Api.InputGroupCall, block: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
@ -10887,15 +10903,16 @@ public extension Api.functions.phone {
}
}
public extension Api.functions.phone {
static func sendGroupCallMessage(flags: Int32, call: Api.InputGroupCall, randomId: Int64, message: Api.TextWithEntities, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
static func sendGroupCallMessage(flags: Int32, call: Api.InputGroupCall, randomId: Int64, message: Api.TextWithEntities, allowPaidStars: Int64?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(445465039)
buffer.appendInt32(-1311697904)
serializeInt32(flags, buffer: buffer, boxed: false)
call.serialize(buffer, true)
serializeInt64(randomId, buffer: buffer, boxed: false)
message.serialize(buffer, true)
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.Updates? in
if Int(flags) & Int(1 << 1) != 0 {sendAs!.serialize(buffer, true)}
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)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {

View file

@ -1222,14 +1222,14 @@ public extension Api {
}
public extension Api {
enum GroupCall: TypeConstructorDescription {
case groupCall(flags: Int32, id: Int64, accessHash: Int64, participantsCount: Int32, title: String?, streamDcId: Int32?, recordStartDate: Int32?, scheduleDate: Int32?, unmutedVideoCount: Int32?, unmutedVideoLimit: Int32, version: Int32, inviteLink: String?, sendPaidMessagesStars: Int64?)
case groupCall(flags: Int32, id: Int64, accessHash: Int64, participantsCount: Int32, title: String?, streamDcId: Int32?, recordStartDate: Int32?, scheduleDate: Int32?, unmutedVideoCount: Int32?, unmutedVideoLimit: Int32, version: Int32, inviteLink: String?, sendPaidMessagesStars: Int64?, defaultSendAs: Api.Peer?)
case groupCallDiscarded(id: Int64, accessHash: Int64, duration: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let unmutedVideoCount, let unmutedVideoLimit, let version, let inviteLink, let sendPaidMessagesStars):
case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let unmutedVideoCount, let unmutedVideoLimit, let version, let inviteLink, let sendPaidMessagesStars, let defaultSendAs):
if boxed {
buffer.appendInt32(-674602536)
buffer.appendInt32(-273500649)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false)
@ -1244,6 +1244,7 @@ public extension Api {
serializeInt32(version, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 16) != 0 {serializeString(inviteLink!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 20) != 0 {serializeInt64(sendPaidMessagesStars!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 21) != 0 {defaultSendAs!.serialize(buffer, true)}
break
case .groupCallDiscarded(let id, let accessHash, let duration):
if boxed {
@ -1258,8 +1259,8 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let unmutedVideoCount, let unmutedVideoLimit, let version, let inviteLink, let sendPaidMessagesStars):
return ("groupCall", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("participantsCount", participantsCount as Any), ("title", title as Any), ("streamDcId", streamDcId as Any), ("recordStartDate", recordStartDate as Any), ("scheduleDate", scheduleDate as Any), ("unmutedVideoCount", unmutedVideoCount as Any), ("unmutedVideoLimit", unmutedVideoLimit as Any), ("version", version as Any), ("inviteLink", inviteLink as Any), ("sendPaidMessagesStars", sendPaidMessagesStars as Any)])
case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let unmutedVideoCount, let unmutedVideoLimit, let version, let inviteLink, let sendPaidMessagesStars, let defaultSendAs):
return ("groupCall", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("participantsCount", participantsCount as Any), ("title", title as Any), ("streamDcId", streamDcId as Any), ("recordStartDate", recordStartDate as Any), ("scheduleDate", scheduleDate as Any), ("unmutedVideoCount", unmutedVideoCount as Any), ("unmutedVideoLimit", unmutedVideoLimit as Any), ("version", version as Any), ("inviteLink", inviteLink as Any), ("sendPaidMessagesStars", sendPaidMessagesStars as Any), ("defaultSendAs", defaultSendAs as Any)])
case .groupCallDiscarded(let id, let accessHash, let duration):
return ("groupCallDiscarded", [("id", id as Any), ("accessHash", accessHash as Any), ("duration", duration as Any)])
}
@ -1292,6 +1293,10 @@ public extension Api {
if Int(_1!) & Int(1 << 16) != 0 {_12 = parseString(reader) }
var _13: Int64?
if Int(_1!) & Int(1 << 20) != 0 {_13 = reader.readInt64() }
var _14: Api.Peer?
if Int(_1!) & Int(1 << 21) != 0 {if let signature = reader.readInt32() {
_14 = Api.parse(reader, signature: signature) as? Api.Peer
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
@ -1305,8 +1310,9 @@ public extension Api {
let _c11 = _11 != nil
let _c12 = (Int(_1!) & Int(1 << 16) == 0) || _12 != nil
let _c13 = (Int(_1!) & Int(1 << 20) == 0) || _13 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 {
return Api.GroupCall.groupCall(flags: _1!, id: _2!, accessHash: _3!, participantsCount: _4!, title: _5, streamDcId: _6, recordStartDate: _7, scheduleDate: _8, unmutedVideoCount: _9, unmutedVideoLimit: _10!, version: _11!, inviteLink: _12, sendPaidMessagesStars: _13)
let _c14 = (Int(_1!) & Int(1 << 21) == 0) || _14 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 {
return Api.GroupCall.groupCall(flags: _1!, id: _2!, accessHash: _3!, participantsCount: _4!, title: _5, streamDcId: _6, recordStartDate: _7, scheduleDate: _8, unmutedVideoCount: _9, unmutedVideoLimit: _10!, version: _11!, inviteLink: _12, sendPaidMessagesStars: _13, defaultSendAs: _14)
}
else {
return nil

View file

@ -100,7 +100,8 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
isVideoEnabled: state.isVideoEnabled,
unmutedVideoLimit: state.unmutedVideoLimit,
isStream: state.isStream,
isCreator: state.isCreator
isCreator: state.isCreator,
defaultSendAs: state.defaultSendAs
),
topParticipants: topParticipants,
participantCount: state.totalCount,

View file

@ -1689,7 +1689,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
isVideoEnabled: callInfo.isVideoEnabled,
unmutedVideoLimit: callInfo.unmutedVideoLimit,
isStream: callInfo.isStream,
isCreator: callInfo.isCreator
isCreator: callInfo.isCreator,
defaultSendAs: callInfo.defaultSendAs
)), audioSessionControl: self.audioSessionControl)
} else {
self.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo(
@ -1707,7 +1708,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
isVideoEnabled: state.isVideoEnabled,
unmutedVideoLimit: state.unmutedVideoLimit,
isStream: callInfo.isStream,
isCreator: callInfo.isCreator
isCreator: callInfo.isCreator,
defaultSendAs: callInfo.defaultSendAs
))))
self.summaryParticipantsState.set(.single(SummaryParticipantsState(
@ -1787,7 +1789,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
isVideoEnabled: false,
unmutedVideoLimit: 0,
isStream: true,
isCreator: false
isCreator: false,
defaultSendAs: nil
)
} else {
activeCallInfo = nil
@ -2691,7 +2694,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
isVideoEnabled: state.isVideoEnabled,
unmutedVideoLimit: state.unmutedVideoLimit,
isStream: callInfo.isStream,
isCreator: callInfo.isCreator
isCreator: callInfo.isCreator,
defaultSendAs: callInfo.defaultSendAs
))))
self.summaryParticipantsState.set(.single(SummaryParticipantsState(

View file

@ -4906,7 +4906,7 @@ func replayFinalState(
}
switch call {
case let .groupCall(flags, _, _, participantsCount, title, _, recordStartDate, scheduleDate, _, _, _, _, sendPaidMessagesStars):
case let .groupCall(flags, _, _, participantsCount, title, _, recordStartDate, scheduleDate, _, _, _, _, sendPaidMessagesStars, _):
let isMin = (flags & (1 << 19)) != 0
let isMuted = (flags & (1 << 1)) != 0
let canChange = (flags & (1 << 2)) != 0

View file

@ -147,6 +147,7 @@ public struct Namespaces {
public static let cachedProfileGiftsCollections: Int8 = 48
public static let cachedProfileSavedMusic: Int8 = 49
public static let cachedChatThemes: Int8 = 50
public static let cachedLiveStorySendAsPeers: Int8 = 51
}
public struct UnorderedItemList {

View file

@ -99,6 +99,7 @@ public struct GroupCallInfo: Equatable {
public var unmutedVideoLimit: Int
public var isStream: Bool
public var isCreator: Bool
public var defaultSendAs: EnginePeer.Id?
public init(
id: Int64,
@ -115,7 +116,8 @@ public struct GroupCallInfo: Equatable {
isVideoEnabled: Bool,
unmutedVideoLimit: Int,
isStream: Bool,
isCreator: Bool
isCreator: Bool,
defaultSendAs: EnginePeer.Id?
) {
self.id = id
self.accessHash = accessHash
@ -132,6 +134,7 @@ public struct GroupCallInfo: Equatable {
self.unmutedVideoLimit = unmutedVideoLimit
self.isStream = isStream
self.isCreator = isCreator
self.defaultSendAs = defaultSendAs
}
}
@ -143,7 +146,7 @@ public struct GroupCallSummary: Equatable {
extension GroupCallInfo {
init?(_ call: Api.GroupCall) {
switch call {
case let .groupCall(flags, id, accessHash, participantsCount, title, streamDcId, recordStartDate, scheduleDate, _, unmutedVideoLimit, _, _, sendPaidMessagesStars):
case let .groupCall(flags, id, accessHash, participantsCount, title, streamDcId, recordStartDate, scheduleDate, _, unmutedVideoLimit, _, _, sendPaidMessagesStars, defaultSendAs):
self.init(
id: id,
accessHash: accessHash,
@ -159,7 +162,8 @@ extension GroupCallInfo {
isVideoEnabled: (flags & (1 << 9)) != 0,
unmutedVideoLimit: Int(unmutedVideoLimit),
isStream: (flags & (1 << 12)) != 0,
isCreator: (flags & (1 << 15)) != 0
isCreator: (flags & (1 << 15)) != 0,
defaultSendAs: defaultSendAs?.peerId
)
case .groupCallDiscarded:
return nil
@ -773,7 +777,7 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
maybeParsedCall = GroupCallInfo(call)
switch call {
case let .groupCall(flags, _, _, _, title, _, recordStartDate, scheduleDate, _, unmutedVideoLimit, _, _, sendPaidMessagesStars):
case let .groupCall(flags, _, _, _, title, _, recordStartDate, scheduleDate, _, unmutedVideoLimit, _, _, sendPaidMessagesStars, defaultSendAs):
let isMin = (flags & (1 << 19)) != 0
let isMuted = (flags & (1 << 1)) != 0
let canChange = (flags & (1 << 2)) != 0
@ -787,6 +791,7 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
state.scheduleTimestamp = scheduleDate
state.isVideoEnabled = isMin ? state.isVideoEnabled : isVideoEnabled
state.unmutedVideoLimit = Int(unmutedVideoLimit)
state.defaultSendAs = defaultSendAs?.peerId
default:
break
}
@ -1429,6 +1434,7 @@ public final class GroupCallParticipantsContext {
public var unmutedVideoLimit: Int
public var isStream: Bool
public var sendPaidMessagesStars: Int64?
public var defaultSendAs: PeerId?
public var version: Int32
public mutating func mergeActivity(from other: State, myPeerId: PeerId?, previousMyPeerId: PeerId?, mergeActivityTimestamps: Bool) {
@ -3836,7 +3842,7 @@ public final class GroupCallMessagesContext {
private var messageLifeTimer: SwiftSignalKit.Timer?
private var pendingSendStars: (fromId: PeerId, messageId: Int64, amount: Int64)?
private var pendingSendStars: (fromPeer: Peer, messageId: Int64, amount: Int64)?
private var pendingSendStarsTimer: SwiftSignalKit.Timer?
init(queue: Queue, account: Account, callId: Int64, reference: InternalGroupCallReference, e2eContext: ConferenceCallE2EContext?, messageLifetime: Int32, isLiveStream: Bool) {
@ -4269,6 +4275,16 @@ public final class GroupCallMessagesContext {
if paidStars != nil {
flags |= 1 << 0
}
var sendAs: Api.InputPeer?
if fromId != self.account.peerId {
guard let fromPeer else {
return
}
sendAs = apiInputPeer(fromPeer)
}
if sendAs != nil {
flags |= 1 << 1
}
self.sendMessageDisposables.add((self.account.network.request(Api.functions.phone.sendGroupCallMessage(
flags: flags,
call: self.reference.apiInputGroupCall,
@ -4277,7 +4293,8 @@ public final class GroupCallMessagesContext {
text: text,
entities: apiEntitiesFromMessageTextEntities(entities, associatedPeers: SimpleDictionary())
),
allowPaidStars: paidStars
allowPaidStars: paidStars,
sendAs: sendAs
)) |> deliverOn(self.queue)).startStrict(next: { [weak self] updates in
guard let self else {
return
@ -4322,6 +4339,13 @@ public final class GroupCallMessagesContext {
var flags: Int32 = 0
flags |= 1 << 0
var sendAs: Api.InputPeer?
if pendingSendStars.fromPeer.id != self.account.peerId {
sendAs = apiInputPeer(pendingSendStars.fromPeer)
}
if sendAs != nil {
flags |= 1 << 1
}
self.sendMessageDisposables.add((self.account.network.request(Api.functions.phone.sendGroupCallMessage(
flags: flags,
call: self.reference.apiInputGroupCall,
@ -4330,7 +4354,8 @@ public final class GroupCallMessagesContext {
text: "",
entities: []
),
allowPaidStars: pendingSendStars.amount
allowPaidStars: pendingSendStars.amount,
sendAs: sendAs
)) |> deliverOn(self.queue)).startStrict(next: { [weak self] updates in
guard let self else {
return
@ -4347,7 +4372,7 @@ public final class GroupCallMessagesContext {
if let index = state.pinnedMessages.firstIndex(where: { $0.id == Message.Id(space: .local, id: pendingSendStars.messageId) }) {
state.pinnedMessages[index] = state.pinnedMessages[index].withId(Message.Id(space: .remote, id: Int64(id)))
}
Impl.addStateStars(state: &state, peerId: pendingSendStars.fromId, isMy: true, amount: pendingSendStars.amount)
Impl.addStateStars(state: &state, peerId: pendingSendStars.fromPeer.id, isMy: true, amount: pendingSendStars.amount)
state.pendingMyStars = 0
self.state = state
break
@ -4384,7 +4409,7 @@ public final class GroupCallMessagesContext {
return transaction.getPeer(fromId)
}
|> deliverOn(self.queue)).startStandalone(next: { [weak self] fromPeer in
guard let self else {
guard let self, let fromPeer else {
return
}
@ -4395,7 +4420,7 @@ public final class GroupCallMessagesContext {
totalAmount = pendingSendStarsValue.amount + amount
self.pendingSendStars = (
fromId: fromId,
fromPeer: fromPeer,
messageId: pendingSendStarsValue.messageId,
amount: totalAmount
)
@ -4406,7 +4431,7 @@ public final class GroupCallMessagesContext {
arc4random_buf(&randomId, 8)
self.pendingSendStars = (
fromId: fromId,
fromPeer: fromPeer,
messageId: randomId,
amount: amount
)
@ -4437,7 +4462,7 @@ public final class GroupCallMessagesContext {
state.messages.append(Message(
id: Message.Id(space: .local, id: pendingSendStarsValue.messageId),
stableId: stableId,
author: fromPeer.flatMap(EnginePeer.init),
author: EnginePeer(fromPeer),
text: "",
entities: [],
date: currentTime,
@ -4464,7 +4489,7 @@ public final class GroupCallMessagesContext {
state.pinnedMessages.append(Message(
id: Message.Id(space: .local, id: pendingSendStarsValue.messageId),
stableId: stableId,
author: fromPeer.flatMap(EnginePeer.init),
author: EnginePeer(fromPeer),
text: "",
entities: [],
date: currentTime,

View file

@ -96,7 +96,6 @@ func _internal_cachedPeerSendAsAvailablePeers(account: Account, peerId: PeerId)
}
}
func _internal_peerSendAsAvailablePeers(accountPeerId: PeerId, network: Network, postbox: Postbox, peerId: PeerId) -> Signal<[SendAsPeer], NoError> {
return postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
@ -216,3 +215,113 @@ func _internal_updatePeerSendAsPeer(account: Account, peerId: PeerId, sendAs: Pe
}
}
}
func _internal_cachedLiveStorySendAsAvailablePeers(account: Account, peerId: PeerId) -> Signal<[SendAsPeer], NoError> {
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: peerId.toInt64())
return account.postbox.transaction { transaction -> ([SendAsPeer], Int32)? in
let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedLiveStorySendAsPeers, key: key))?.get(CachedSendAsPeers.self)
if let cached = cached {
var peers: [SendAsPeer] = []
for peerId in cached.peerIds {
if let peer = transaction.getPeer(peerId) {
var subscribers: Int32?
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData {
subscribers = cachedData.participantsSummary.memberCount
}
peers.append(SendAsPeer(peer: peer, subscribers: subscribers, isPremiumRequired: cached.premiumRequiredPeerIds.contains(peer.id)))
}
}
return (peers, cached.timestamp)
} else {
return nil
}
}
|> mapToSignal { cachedPeersAndTimestamp -> Signal<[SendAsPeer], NoError> in
let initialSignal: Signal<[SendAsPeer], NoError>
if let (cachedPeers, _) = cachedPeersAndTimestamp {
initialSignal = .single(cachedPeers)
} else {
initialSignal = .complete()
}
return initialSignal
|> then(_internal_liveStorySendAsAvailablePeers(account: account, peerId: peerId)
|> mapToSignal { peers -> Signal<[SendAsPeer], NoError> in
return account.postbox.transaction { transaction -> [SendAsPeer] in
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
var premiumRequiredPeerIds = Set<PeerId>()
for peer in peers {
if peer.isPremiumRequired {
premiumRequiredPeerIds.insert(peer.peer.id)
}
}
if let entry = CodableEntry(CachedSendAsPeers(peerIds: peers.map { $0.peer.id }, premiumRequiredPeerIds: premiumRequiredPeerIds, timestamp: currentTimestamp)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedLiveStorySendAsPeers, key: key), entry: entry)
}
return peers
}
})
}
}
func _internal_liveStorySendAsAvailablePeers(account: Account, peerId: PeerId) -> Signal<[SendAsPeer], NoError> {
return account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
}
|> mapToSignal { peer -> Signal<[SendAsPeer], NoError> in
guard let peer else {
return .single([])
}
guard let inputPeer = apiInputPeer(peer) else {
return .single([])
}
return account.network.request(Api.functions.channels.getSendAs(flags: 1 << 1, peer: inputPeer))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.channels.SendAsPeers?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<[SendAsPeer], NoError> in
guard let result = result else {
return .single([])
}
switch result {
case let .sendAsPeers(sendAsPeers, chats, _):
return account.postbox.transaction { transaction -> [SendAsPeer] in
var subscribers: [PeerId: Int32] = [:]
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: [])
var premiumRequiredPeerIds = Set<PeerId>()
for sendAsPeer in sendAsPeers {
if case let .sendAsPeer(flags, peer) = sendAsPeer, (flags & (1 << 0)) != 0 {
premiumRequiredPeerIds.insert(peer.peerId)
}
}
for chat in chats {
if let groupOrChannel = parsedPeers.get(chat.peerId) {
switch chat {
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _, _, _, _, _, _, _, _, _):
if let participantsCount = participantsCount {
subscribers[groupOrChannel.id] = participantsCount
}
case let .chat(_, _, _, _, participantsCount, _, _, _, _, _):
subscribers[groupOrChannel.id] = participantsCount
default:
break
}
}
}
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
var peers: [Peer] = []
for chat in chats {
if let peer = transaction.getPeer(chat.peerId) {
peers.append(peer)
}
}
return peers.map { SendAsPeer(peer: $0, subscribers: subscribers[$0.id], isPremiumRequired: premiumRequiredPeerIds.contains($0.id)) }
}
}
}
}
}

View file

@ -1175,6 +1175,10 @@ public extension TelegramEngine {
return _internal_updatePeerSendAsPeer(account: self.account, peerId: peerId, sendAs: sendAs)
}
public func liveStorySendAsAvailablePeers(peerId: PeerId) -> Signal<[SendAsPeer], NoError> {
return _internal_cachedLiveStorySendAsAvailablePeers(account: self.account, peerId: peerId)
}
public func updatePeerReactionSettings(peerId: PeerId, reactionSettings: PeerReactionSettings) -> Signal<Never, UpdatePeerAllowedReactionsError> {
return _internal_updatePeerReactionSettings(account: account, peerId: peerId, reactionSettings: reactionSettings)
}

View file

@ -495,6 +495,7 @@ swift_library(
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/AttachmentFileController",
"//submodules/TelegramUI/Components/Contacts/NewContactScreen",
"//submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu",
] + select({
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
"//build-system:ios_sim_arm64": [],

View file

@ -0,0 +1,29 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatSendAsContextMenu",
module_name = "ChatSendAsContextMenu",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display",
"//submodules/AsyncDisplayKit",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramPresentationData",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/AppBundle",
"//submodules/ContextUI",
"//submodules/TelegramStringFormatting",
"//submodules/AvatarNode",
"//submodules/AccountContext",
"//submodules/UndoUI",
],
visibility = [
"//visibility:public",
],
)

View file

@ -13,24 +13,26 @@ import AvatarNode
import AccountContext
import UndoUI
final class ChatSendAsPeerListContextItem: ContextMenuCustomItem {
public final class ChatSendAsPeerListContextItem: ContextMenuCustomItem {
let context: AccountContext
let chatPeerId: PeerId
let peers: [SendAsPeer]
let selectedPeerId: PeerId?
let isPremium: Bool
let action: (EnginePeer) -> Void
let presentToast: (EnginePeer) -> Void
init(context: AccountContext, chatPeerId: PeerId, peers: [SendAsPeer], selectedPeerId: PeerId?, isPremium: Bool, presentToast: @escaping (EnginePeer) -> Void) {
public init(context: AccountContext, chatPeerId: PeerId, peers: [SendAsPeer], selectedPeerId: PeerId?, isPremium: Bool, action: @escaping (EnginePeer) -> Void, presentToast: @escaping (EnginePeer) -> Void) {
self.context = context
self.chatPeerId = chatPeerId
self.peers = peers
self.selectedPeerId = selectedPeerId
self.isPremium = isPremium
self.action = action
self.presentToast = presentToast
}
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
public func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
return ChatSendAsPeerListContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
}
}
@ -115,7 +117,7 @@ private final class ChatSendAsPeerListContextItemNode: ASDisplayNode, ContextMen
}
if peer.peer.id != item.selectedPeerId {
let _ = item.context.engine.peers.updatePeerSendAsPeer(peerId: item.chatPeerId, sendAs: peer.peer.id).startStandalone()
item.action(EnginePeer(peer.peer))
}
})
let actionNode = ContextActionNode(presentationData: presentationData, action: action, getController: getController, actionSelected: actionSelected, requestLayout: {}, requestUpdateAction: { _, _ in

View file

@ -103,6 +103,38 @@ public final class ChatTextInputPanelComponent: Component {
}
}
public final class SendAsConfiguration: Equatable {
public let currentPeer: EnginePeer
public let subscriberCount: Int?
public let isPremiumLocked: Bool
public let isSelecting: Bool
public let action: (UIView, ContextGesture?) -> Void
public init(currentPeer: EnginePeer, subscriberCount: Int?, isPremiumLocked: Bool, isSelecting: Bool, action: @escaping (UIView, ContextGesture?) -> Void) {
self.currentPeer = currentPeer
self.subscriberCount = subscriberCount
self.isPremiumLocked = isPremiumLocked
self.isSelecting = isSelecting
self.action = action
}
public static func ==(lhs: SendAsConfiguration, rhs: SendAsConfiguration) -> Bool {
if lhs.currentPeer != rhs.currentPeer {
return false
}
if lhs.subscriberCount != rhs.subscriberCount {
return false
}
if lhs.isPremiumLocked != rhs.isPremiumLocked {
return false
}
if lhs.isSelecting != rhs.isSelecting {
return false
}
return true
}
}
let externalState: ExternalState
let context: AccountContext
let theme: PresentationTheme
@ -111,6 +143,7 @@ public final class ChatTextInputPanelComponent: Component {
let inlineActions: [InlineAction]
let leftAction: LeftAction?
let rightAction: RightAction?
let sendAsConfiguration: SendAsConfiguration?
let placeholder: String
let paidMessagePrice: StarsAmount?
let sendColor: UIColor?
@ -131,6 +164,7 @@ public final class ChatTextInputPanelComponent: Component {
inlineActions: [InlineAction],
leftAction: LeftAction?,
rightAction: RightAction?,
sendAsConfiguration: SendAsConfiguration?,
placeholder: String,
paidMessagePrice: StarsAmount?,
sendColor: UIColor?,
@ -150,6 +184,7 @@ public final class ChatTextInputPanelComponent: Component {
self.inlineActions = inlineActions
self.leftAction = leftAction
self.rightAction = rightAction
self.sendAsConfiguration = sendAsConfiguration
self.placeholder = placeholder
self.paidMessagePrice = paidMessagePrice
self.sendColor = sendColor
@ -187,6 +222,9 @@ public final class ChatTextInputPanelComponent: Component {
if lhs.rightAction != rhs.rightAction {
return false
}
if lhs.sendAsConfiguration != rhs.sendAsConfiguration {
return false
}
if lhs.placeholder != rhs.placeholder {
return false
}
@ -582,7 +620,11 @@ public final class ChatTextInputPanelComponent: Component {
},
openInviteRequests: {
},
openSendAsPeer: { _, _ in
openSendAsPeer: { [weak self] sourceNode, gesture in
guard let self, let component = self.component, let sendAsConfiguration = component.sendAsConfiguration else {
return
}
sendAsConfiguration.action(sourceNode.view, gesture)
},
presentChatRequestAdminInfo: {
},
@ -722,6 +764,14 @@ public final class ChatTextInputPanelComponent: Component {
}
presentationInterfaceState = presentationInterfaceState.updatedSendPaidMessageStars(component.paidMessagePrice)
if let sendAsConfiguration = component.sendAsConfiguration {
presentationInterfaceState = presentationInterfaceState.updatedSendAsPeers([SendAsPeer(
peer: sendAsConfiguration.currentPeer._asPeer(),
subscribers: sendAsConfiguration.subscriberCount.flatMap(Int32.init(clamping:)),
isPremiumRequired: sendAsConfiguration.isPremiumLocked
)]).updatedShowSendAsPeers(sendAsConfiguration.isSelecting).updatedCurrentSendAsPeerId(sendAsConfiguration.currentPeer.id)
}
let panelNode: ChatTextInputPanelNode
if let current = self.panelNode {
panelNode = current

View file

@ -192,6 +192,38 @@ public final class MessageInputPanelComponent: Component {
}
}
public final class SendAsConfiguration: Equatable {
public let currentPeer: EnginePeer
public let subscriberCount: Int?
public let isPremiumLocked: Bool
public let isSelecting: Bool
public let action: (UIView, ContextGesture?) -> Void
public init(currentPeer: EnginePeer, subscriberCount: Int?, isPremiumLocked: Bool, isSelecting: Bool, action: @escaping (UIView, ContextGesture?) -> Void) {
self.currentPeer = currentPeer
self.subscriberCount = subscriberCount
self.isPremiumLocked = isPremiumLocked
self.isSelecting = isSelecting
self.action = action
}
public static func ==(lhs: SendAsConfiguration, rhs: SendAsConfiguration) -> Bool {
if lhs.currentPeer != rhs.currentPeer {
return false
}
if lhs.subscriberCount != rhs.subscriberCount {
return false
}
if lhs.isPremiumLocked != rhs.isPremiumLocked {
return false
}
if lhs.isSelecting != rhs.isSelecting {
return false
}
return true
}
}
public let externalState: ExternalState
public let context: AccountContext
public let theme: PresentationTheme
@ -255,6 +287,7 @@ public final class MessageInputPanelComponent: Component {
public let toggleLiveChatExpanded: (() -> Void)?
public let sendStarsAction: ((UIView, Bool) -> Void)?
public let starStars: StarStats?
public let sendAsConfiguration: SendAsConfiguration?
public init(
externalState: ExternalState,
@ -319,7 +352,8 @@ public final class MessageInputPanelComponent: Component {
liveChatState: LiveChatState? = nil,
toggleLiveChatExpanded: (() -> Void)? = nil,
sendStarsAction: ((UIView, Bool) -> Void)? = nil,
starStars: StarStats? = nil
starStars: StarStats? = nil,
sendAsConfiguration: SendAsConfiguration? = nil
) {
self.externalState = externalState
self.context = context
@ -384,6 +418,7 @@ public final class MessageInputPanelComponent: Component {
self.toggleLiveChatExpanded = toggleLiveChatExpanded
self.sendStarsAction = sendStarsAction
self.starStars = starStars
self.sendAsConfiguration = sendAsConfiguration
}
public static func ==(lhs: MessageInputPanelComponent, rhs: MessageInputPanelComponent) -> Bool {
@ -519,6 +554,9 @@ public final class MessageInputPanelComponent: Component {
if lhs.starStars != rhs.starStars {
return false
}
if lhs.sendAsConfiguration != rhs.sendAsConfiguration {
return false
}
return true
}
@ -968,6 +1006,16 @@ public final class MessageInputPanelComponent: Component {
}
}
let sendAsConfiguration = component.sendAsConfiguration.flatMap { value in
return ChatTextInputPanelComponent.SendAsConfiguration(
currentPeer: value.currentPeer,
subscriberCount: value.subscriberCount,
isPremiumLocked: value.isPremiumLocked,
isSelecting: value.isSelecting,
action: value.action
)
}
let inputPanelSize = inputPanel.update(
transition: transition,
component: AnyComponent(ChatTextInputPanelComponent(
@ -994,6 +1042,7 @@ public final class MessageInputPanelComponent: Component {
}
component.sendStarsAction?(sourceView, true)
}),
sendAsConfiguration: sendAsConfiguration,
placeholder: placeholder,
paidMessagePrice: component.sendPaidMessageStars,
sendColor: component.sendPaidMessageStars.flatMap { value in

View file

@ -110,6 +110,7 @@ swift_library(
"//submodules/TelegramUI/Components/StarsParticleEffect",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/TelegramUI/Components/AdminUserActionsSheet",
"//submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu",
],
visibility = [
"//visibility:public",

View file

@ -302,6 +302,7 @@ private final class PinnedBarComponent: Component {
self.isUpdating = false
}
let previousComponent = self.component
self.component = component
self.state = state
@ -325,8 +326,21 @@ private final class PinnedBarComponent: Component {
let listInsets = UIEdgeInsets(top: 0.0, left: insets.left, bottom: 0.0, right: insets.right)
let listFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
var listTransition = transition
var animateIn = false
if let previousComponent {
if previousComponent.messages.isEmpty {
listTransition = listTransition.withAnimation(.none)
animateIn = true
}
} else {
listTransition = listTransition.withAnimation(.none)
animateIn = true
}
let _ = self.list.update(
transition: transition,
transition: listTransition,
component: AnyComponent(AsyncListComponent(
externalState: self.listState,
items: listItems,
@ -343,6 +357,10 @@ private final class PinnedBarComponent: Component {
}
transition.setPosition(view: listView, position: CGRect(origin: CGPoint(), size: listFrame.size).center)
transition.setBounds(view: listView, bounds: CGRect(origin: CGPoint(), size: listFrame.size))
if animateIn {
transition.animateAlpha(view: listView, from: 0.0, to: 1.0)
}
}
transition.setFrame(view: self.listContainer, frame: listFrame)

View file

@ -2628,7 +2628,7 @@ public final class StoryItemSetContainerComponent: Component {
let isFirstTime = self.component == nil
if self.component == nil {
self.sendMessageContext.setup(context: component.context, view: self, inputPanelExternalState: self.inputPanelExternalState, keyboardInputData: component.keyboardInputData)
self.sendMessageContext.setup(component: component, view: self, inputPanelExternalState: self.inputPanelExternalState, keyboardInputData: component.keyboardInputData)
/*#if DEBUG
class Target: NSObject {
@ -2963,6 +2963,7 @@ public final class StoryItemSetContainerComponent: Component {
var liveChatState: MessageInputPanelComponent.LiveChatState?
var starStats: MessageInputPanelComponent.StarStats?
var sendAsConfiguration: MessageInputPanelComponent.SendAsConfiguration?
var sendPaidMessageStars = isLiveStream ? self.sendMessageContext.currentLiveStreamMessageStars : component.slice.additionalPeerData.sendPaidMessageStars
if let visibleItemView = self.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View {
if let liveChatStateValue = visibleItemView.liveChatState {
@ -2985,6 +2986,8 @@ public final class StoryItemSetContainerComponent: Component {
sendPaidMessageStars = StarsAmount(value: minMessagePrice, nanos: 0)
}
}
sendAsConfiguration = self.sendMessageContext.currentSendAsConfiguration
}
}
@ -3244,7 +3247,8 @@ public final class StoryItemSetContainerComponent: Component {
self.sendMessageContext.performSendStars(view: self, buttonView: sourceView, count: 1, isFromExpandedView: false)
}
} : nil,
starStars: starStats
starStars: starStats,
sendAsConfiguration: sendAsConfiguration
)),
environment: {},
containerSize: CGSize(width: inputPanelAvailableWidth, height: 200.0)

View file

@ -37,7 +37,6 @@ import TextFieldComponent
import StickerPackPreviewUI
import OpenInExternalAppUI
import SafariServices
//import MediaPasteboardUI
import WebPBinding
import ContextUI
import ChatScheduleTimeController
@ -53,6 +52,7 @@ import AudioWaveform
import ChatMessagePaymentAlertController
import ChatSendStarsScreen
import AnimatedTextComponent
import ChatSendAsContextMenu
private var ObjCKey_DeinitWatcher: Int?
@ -105,6 +105,11 @@ final class StoryItemSetContainerSendMessage {
var currentLiveStreamMessageStars: StarsAmount?
weak var currentSendStarsUndoController: UndoOverlayController?
var sendAsData: (isPremium: Bool, availablePeers: [(peer: EnginePeer, subscriberCount: Int?, isPremiumRequired: Bool)])?
var currentSendAsConfiguration: MessageInputPanelComponent.SendAsConfiguration?
var sendAsContextPeerId: EnginePeer.Id?
var sendAsDisposable: Disposable?
private(set) var isMediaRecordingLocked: Bool = false
var wasRecordingDismissed: Bool = false
@ -118,10 +123,11 @@ final class StoryItemSetContainerSendMessage {
self.resolvePeerByNameDisposable.dispose()
self.inputMediaNodeDataDisposable?.dispose()
self.currentTooltipUpdateTimer?.invalidate()
self.sendAsDisposable?.dispose()
}
func setup(context: AccountContext, view: StoryItemSetContainerComponent.View, inputPanelExternalState: MessageInputPanelComponent.ExternalState, keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>) {
self.context = context
func setup(component: StoryItemSetContainerComponent, view: StoryItemSetContainerComponent.View, inputPanelExternalState: MessageInputPanelComponent.ExternalState, keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>) {
self.context = component.context
self.inputPanelExternalState = inputPanelExternalState
self.view = view
@ -208,6 +214,73 @@ final class StoryItemSetContainerSendMessage {
}
)
self.inputMediaInteraction?.forceTheme = defaultDarkColorPresentationTheme
if let peerId = component.slice.item.peerId {
self.sendAsDisposable = combineLatest(
queue: Queue.mainQueue(),
component.context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId)
),
component.context.engine.peers.liveStorySendAsAvailablePeers(peerId: peerId)
).startStrict(next: { [weak self, weak view] accountPeer, peers in
guard let self, let view, let accountPeer else {
return
}
let isPremium = accountPeer.isPremium
var availablePeers: [(peer: EnginePeer, subscriberCount: Int?, isPremiumRequired: Bool)] = []
availablePeers.append((
peer: accountPeer,
subscriberCount: nil,
isPremiumRequired: false
))
for peer in peers {
if peer.peer.id == accountPeer.id {
continue
}
availablePeers.append((
peer: EnginePeer(peer.peer),
subscriberCount: peer.subscribers.flatMap(Int.init),
isPremiumRequired: peer.isPremiumRequired
))
}
self.sendAsData = (
isPremium: isPremium,
availablePeers: availablePeers
)
//TODO:localize
if "".isEmpty {
let sendAsConfiguration = MessageInputPanelComponent.SendAsConfiguration(
currentPeer: accountPeer,
subscriberCount: nil,
isPremiumLocked: false,
isSelecting: false,
action: { [weak self, weak view] sourceView, gesture in
guard let self, let view else {
return
}
self.openSendAsSelection(view: view, sourceView: sourceView, gesture: gesture)
}
)
if self.currentSendAsConfiguration != sendAsConfiguration {
self.currentSendAsConfiguration = sendAsConfiguration
if !view.isUpdatingComponent {
view.state?.updated(transition: .spring(duration: 0.4))
}
}
} else {
if self.currentSendAsConfiguration != nil {
self.currentSendAsConfiguration = nil
if !view.isUpdatingComponent {
view.state?.updated(transition: .spring(duration: 0.4))
}
}
}
})
}
}
func toggleInputMode() {
@ -281,7 +354,7 @@ final class StoryItemSetContainerSendMessage {
self.inputMediaNode = inputMediaNode
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
let presentationInterfaceState = ChatPresentationInterfaceState(
chatWallpaper: .builtin(WallpaperSettings()),
theme: presentationData.theme,
@ -3954,11 +4027,11 @@ final class StoryItemSetContainerSendMessage {
peerId: peerId,
reactSubject: .liveStream(peerId: peerId, storyId: focusedItem.storyItem.id, minAmount: Int(minAmount)),
topPeers: topPeers,
completion: { [weak view] amount, privacy, isBecomingTop, transitionOut in
guard let view, let component = view.component else {
completion: { [weak self, weak view] amount, _, _, _ in
guard let self, let view else {
return
}
let _ = component.context.engine.messages.sendStoryStars(peerId: component.slice.effectivePeer.id, id: component.slice.item.storyItem.id, count: Int(amount)).startStandalone()
self.performSendStars(view: view, buttonView: nil, count: Int(amount), isFromExpandedView: true)
}).get()
if let initialData {
controller.push(ChatSendStarsScreen(
@ -3970,12 +4043,17 @@ final class StoryItemSetContainerSendMessage {
}
}
func performSendStars(view: StoryItemSetContainerComponent.View, buttonView: UIView, count: Int, isFromExpandedView: Bool) {
func performSendStars(view: StoryItemSetContainerComponent.View, buttonView: UIView?, count: Int, isFromExpandedView: Bool) {
guard let component = view.component else {
return
}
if isFromExpandedView {
if let currentSendStarsUndoController = self.currentSendStarsUndoController {
self.currentSendStarsUndoController = nil
currentSendStarsUndoController.dismiss()
}
self.commitSendStars(view: view, count: count, delay: false)
} else {
Task { @MainActor [weak view] in
@ -4009,7 +4087,7 @@ final class StoryItemSetContainerSendMessage {
}
}
if let reactionItem {
if let reactionItem, let buttonView {
let targetFrame = buttonView.convert(buttonView.bounds, to: view)
let targetView = UIView(frame: targetFrame)
@ -4145,6 +4223,94 @@ final class StoryItemSetContainerSendMessage {
call.sendStars(amount: Int64(count), delay: delay)
}
private func openSendAsSelection(view: StoryItemSetContainerComponent.View, sourceView: UIView, gesture: ContextGesture?) {
guard let component = view.component, let sendAsData = self.sendAsData, let currentSendAsConfiguration = self.currentSendAsConfiguration, let controller = component.controller() else {
return
}
let focusedItem = component.slice.item
guard let peerId = focusedItem.peerId else {
return
}
let isPremium = sendAsData.isPremium
let mappedPeers = sendAsData.availablePeers.map { peer in
return SendAsPeer(
peer: peer.peer._asPeer(),
subscribers: peer.subscriberCount.flatMap(Int32.init(clamping:)),
isPremiumRequired: peer.isPremiumRequired
)
}
var items: [ContextMenuItem] = []
items.append(.custom(ChatSendAsPeerTitleContextItem(text: component.strings.Conversation_SendMesageAs.uppercased()), false))
items.append(.custom(ChatSendAsPeerListContextItem(
context: component.context,
chatPeerId: peerId,
peers: mappedPeers,
selectedPeerId: currentSendAsConfiguration.currentPeer.id,
isPremium: isPremium,
action: { [weak self] peer in
guard let self else {
return
}
let _ = self
},
presentToast: { [weak view] peer in
guard let view, let component = view.component, let controller = component.controller() else {
return
}
HapticFeedback().impact()
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
controller.present(UndoOverlayController(presentationData: presentationData, content: .invitedToVoiceChat(context: component.context, peer: peer, title: nil, text: presentationData.strings.Conversation_SendMesageAsPremiumInfo, action: presentationData.strings.EmojiInput_PremiumEmojiToast_Action, duration: 3), elevatedLayout: false, action: { [weak view] action in
guard let view, let component = view.component, let controller = component.controller() else {
return true
}
if case .undo = action {
view.endEditing(true)
let introScreen = PremiumIntroScreen(context: component.context, source: .settings)
controller.push(introScreen)
}
return true
}), in: .current)
}),
false
))
final class ReferenceSource: ContextReferenceContentSource {
let controller: ViewController
let sourceView: UIView
let insets: UIEdgeInsets
let contentInsets: UIEdgeInsets
init(controller: ViewController, sourceView: UIView, insets: UIEdgeInsets, contentInsets: UIEdgeInsets = UIEdgeInsets()) {
self.controller = controller
self.sourceView = sourceView
self.insets = insets
self.contentInsets = contentInsets
}
func transitionInfo() -> ContextControllerReferenceViewInfo? {
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds.inset(by: self.insets), insets: self.contentInsets, actionsPosition: .top)
}
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
let contextController = ContextController(presentationData: presentationData, source: .reference(ReferenceSource(controller: controller, sourceView: sourceView, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), contentInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0))), items: .single(ContextController.Items(content: .list(items))), gesture: gesture, workaroundUseLegacyImplementation: false)
contextController.dismissed = { [weak self, weak view] in
guard let self, let view else {
return
}
let _ = self
view.state?.updated(transition: .spring(duration: 0.4))
}
controller.presentInGlobalOverlay(contextController)
view.state?.updated(transition: .spring(duration: 0.4))
}
}
public class StoryProgressPauseContext {

View file

@ -123,6 +123,7 @@ import ChatMediaInputStickerGridItem
import AdsInfoScreen
import PostSuggestionsSettingsScreen
import ChatSendStarsScreen
import ChatSendAsContextMenu
extension ChatControllerImpl {
func reloadChatLocation(chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, historyNode: ChatHistoryListNodeImpl, apply: @escaping ((ContainedViewLayoutTransition?) -> Void) -> Void) {
@ -3896,7 +3897,12 @@ extension ChatControllerImpl {
var items: [ContextMenuItem] = []
items.append(.custom(ChatSendAsPeerTitleContextItem(text: strongSelf.presentationInterfaceState.strings.Conversation_SendMesageAs.uppercased()), false))
items.append(.custom(ChatSendAsPeerListContextItem(context: strongSelf.context, chatPeerId: peerId, peers: peers, selectedPeerId: myPeerId, isPremium: isPremium, presentToast: { [weak self] peer in
items.append(.custom(ChatSendAsPeerListContextItem(context: strongSelf.context, chatPeerId: peerId, peers: peers, selectedPeerId: myPeerId, isPremium: isPremium, action: { [weak self] peer in
guard let self else {
return
}
let _ = self.context.engine.peers.updatePeerSendAsPeer(peerId: peerId, sendAs: peer.id).startStandalone()
}, presentToast: { [weak self] peer in
if let strongSelf = self {
HapticFeedback().impact()