diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index a1344158eb..dd6506d277 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -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 diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index 728e56f64e..dbf823676a 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -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 diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 66a8c7f560..aaff5b68aa 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -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) } diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index d79ae1726d..53200a29ce 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -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 diff --git a/submodules/TelegramApi/Sources/Api39.swift b/submodules/TelegramApi/Sources/Api39.swift index 5212a5e7b9..1874a75d3b 100644 --- a/submodules/TelegramApi/Sources/Api39.swift +++ b/submodules/TelegramApi/Sources/Api39.swift @@ -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) { + 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) { 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) { + static func sendGroupCallMessage(flags: Int32, call: Api.InputGroupCall, randomId: Int64, message: Api.TextWithEntities, allowPaidStars: Int64?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { 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() { diff --git a/submodules/TelegramApi/Sources/Api7.swift b/submodules/TelegramApi/Sources/Api7.swift index d78b77465d..1ad2e7de78 100644 --- a/submodules/TelegramApi/Sources/Api7.swift +++ b/submodules/TelegramApi/Sources/Api7.swift @@ -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 diff --git a/submodules/TelegramCallsUI/Sources/AccountGroupCallContextImpl.swift b/submodules/TelegramCallsUI/Sources/AccountGroupCallContextImpl.swift index bbf2e28b75..5b348c93da 100644 --- a/submodules/TelegramCallsUI/Sources/AccountGroupCallContextImpl.swift +++ b/submodules/TelegramCallsUI/Sources/AccountGroupCallContextImpl.swift @@ -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, diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 8860268561..760f476a6d 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -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( diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 04e469776d..ceec57cc75 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -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 diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index d9284622d8..c81fbbf369 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -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 { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index 06bd09fe43..234cebeac0 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -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, diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift index 63405e9f01..0f3320a497 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift @@ -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() + 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 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() + 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)) } + } + } + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 064db9b593..2c246af27d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -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 { return _internal_updatePeerReactionSettings(account: account, peerId: peerId, reactionSettings: reactionSettings) } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index ec8fff1b06..601bf2830e 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -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": [], diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/BUILD b/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/BUILD new file mode 100644 index 0000000000..1ade4c0550 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/BUILD @@ -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", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatSendAsPeerListContextItem.swift b/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift similarity index 94% rename from submodules/TelegramUI/Sources/ChatSendAsPeerListContextItem.swift rename to submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift index 0dc33acc28..a6a877ccea 100644 --- a/submodules/TelegramUI/Sources/ChatSendAsPeerListContextItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatSendAsPeerTitleContextItem.swift b/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerTitleContextItem.swift similarity index 100% rename from submodules/TelegramUI/Sources/ChatSendAsPeerTitleContextItem.swift rename to submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerTitleContextItem.swift diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift index a17ba719e3..fc624f6476 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift @@ -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 diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index 7b491e959d..ec7b716d98 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -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 diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index 7333c3e16d..826101a1ff 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -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", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift index 1218816e63..ff37e7c9f0 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift @@ -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) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index aed4b736a1..295eed8349 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -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) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 0ced021abd..9bc37ea475 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -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) { - self.context = context + func setup(component: StoryItemSetContainerComponent, view: StoryItemSetContainerComponent.View, inputPanelExternalState: MessageInputPanelComponent.ExternalState, keyboardInputData: Signal) { + 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 { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index be52c49e1d..b90870ac87 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -123,6 +123,7 @@ import ChatMediaInputStickerGridItem import AdsInfoScreen import PostSuggestionsSettingsScreen import ChatSendStarsScreen +import ChatSendAsContextMenu extension ChatControllerImpl { func reloadChatLocation(chatLocation: ChatLocation, chatLocationContextHolder: Atomic, 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()