Various fixes

This commit is contained in:
Ilya Laktyushin 2025-02-24 17:11:08 +04:00
parent 1a89986990
commit b2351194d4
45 changed files with 959 additions and 503 deletions

View File

@ -13819,12 +13819,12 @@ Sorry for the inconvenience.";
"GroupInfo.Permissions.ChargeForMessages" = "Charge for Messages"; "GroupInfo.Permissions.ChargeForMessages" = "Charge for Messages";
"GroupInfo.Permissions.ChargeForMessagesInfo" = "If you turn this on, regular members of the group will have to pay Stars to send messages."; "GroupInfo.Permissions.ChargeForMessagesInfo" = "If you turn this on, regular members of the group will have to pay Stars to send messages.";
"GroupInfo.Permissions.MessagePrice" = "SET YOUR PRICE PER MESSAGE"; "GroupInfo.Permissions.MessagePrice" = "SET YOUR PRICE PER MESSAGE";
"GroupInfo.Permissions.MessagePriceInfo" = "Your group will receive 85% of the selected fee (%1$@) for each incoming message."; "GroupInfo.Permissions.MessagePriceInfo" = "Your group will receive %1$@% of the selected fee (%2$@) for each incoming message.";
"Privacy.Messages.ChargeForMessages" = "Charge for Messages"; "Privacy.Messages.ChargeForMessages" = "Charge for Messages";
"Privacy.Messages.ChargeForMessagesInfo" = "Charge a fee for messages from people outide your contacts or those you haven't messaged first."; "Privacy.Messages.ChargeForMessagesInfo" = "Charge a fee for messages from people outide your contacts or those you haven't messaged first.";
"Privacy.Messages.MessagePrice" = "SET YOUR PRICE PER MESSAGE"; "Privacy.Messages.MessagePrice" = "SET YOUR PRICE PER MESSAGE";
"Privacy.Messages.MessagePriceInfo" = "Your will receive 85% of the selected fee (%1$@) for each incoming message."; "Privacy.Messages.MessagePriceInfo" = "Your will receive %1$@% of the selected fee (%2$@) for each incoming message.";
"Privacy.Messages.RemoveFeeHeader" = "EXCEPTIONS"; "Privacy.Messages.RemoveFeeHeader" = "EXCEPTIONS";
"Privacy.Messages.RemoveFee" = "Remove Fee"; "Privacy.Messages.RemoveFee" = "Remove Fee";
@ -13874,3 +13874,5 @@ Sorry for the inconvenience.";
"Gift.Send.PayWithStars.Info" = "Your balance is **%@**. [Get More Stars >]()"; "Gift.Send.PayWithStars.Info" = "Your balance is **%@**. [Get More Stars >]()";
"Chat.PanelCustomStatusShortInfo" = "%@ is a mark for [Premium subscribers >]()"; "Chat.PanelCustomStatusShortInfo" = "%@ is a mark for [Premium subscribers >]()";
"Chat.InputTextPaidMessagePlaceholder" = "Message for %@";

View File

@ -1353,20 +1353,43 @@ public struct StickersSearchConfiguration {
public struct StarsSubscriptionConfiguration { public struct StarsSubscriptionConfiguration {
static var defaultValue: StarsSubscriptionConfiguration { static var defaultValue: StarsSubscriptionConfiguration {
return StarsSubscriptionConfiguration(maxFee: 2500, usdWithdrawRate: 1200) return StarsSubscriptionConfiguration(
maxFee: 2500,
usdWithdrawRate: 1200,
paidMessageMaxAmount: 10000,
paidMessageCommissionPermille: 850
)
} }
public let maxFee: Int64? public let maxFee: Int64
public let usdWithdrawRate: Int64? public let usdWithdrawRate: Int64
public let paidMessageMaxAmount: Int64
public let paidMessageCommissionPermille: Int32
fileprivate init(maxFee: Int64?, usdWithdrawRate: Int64?) { fileprivate init(
maxFee: Int64,
usdWithdrawRate: Int64,
paidMessageMaxAmount: Int64,
paidMessageCommissionPermille: Int32
) {
self.maxFee = maxFee self.maxFee = maxFee
self.usdWithdrawRate = usdWithdrawRate self.usdWithdrawRate = usdWithdrawRate
self.paidMessageMaxAmount = paidMessageMaxAmount
self.paidMessageCommissionPermille = paidMessageCommissionPermille
} }
public static func with(appConfiguration: AppConfiguration) -> StarsSubscriptionConfiguration { public static func with(appConfiguration: AppConfiguration) -> StarsSubscriptionConfiguration {
if let data = appConfiguration.data, let value = data["stars_subscription_amount_max"] as? Double, let usdRate = data["stars_usd_withdraw_rate_x1000"] as? Double { if let data = appConfiguration.data {
return StarsSubscriptionConfiguration(maxFee: Int64(value), usdWithdrawRate: Int64(usdRate)) let maxFee = (data["stars_subscription_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.maxFee
let usdWithdrawRate = (data["stars_usd_withdraw_rate_x1000"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.usdWithdrawRate
let paidMessageMaxAmount = (data["stars_paid_message_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageMaxAmount
let paidMessageCommissionPermille = (data["stars_paid_message_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageCommissionPermille
return StarsSubscriptionConfiguration(
maxFee: maxFee,
usdWithdrawRate: usdWithdrawRate,
paidMessageMaxAmount: paidMessageMaxAmount,
paidMessageCommissionPermille: paidMessageCommissionPermille
)
} else { } else {
return .defaultValue return .defaultValue
} }

View File

@ -76,5 +76,5 @@ public enum ShareControllerSubject {
case image([ImageRepresentationWithReference]) case image([ImageRepresentationWithReference])
case media(AnyMediaReference, MediaParameters?) case media(AnyMediaReference, MediaParameters?)
case mapMedia(TelegramMediaMap) case mapMedia(TelegramMediaMap)
case fromExternal(([PeerId], [PeerId: Int64], String, ShareControllerAccountContext, Bool) -> Signal<ShareControllerExternalStatus, ShareControllerError>) case fromExternal(Int, ([PeerId], [PeerId: Int64], [PeerId: StarsAmount], String, ShareControllerAccountContext, Bool) -> Signal<ShareControllerExternalStatus, ShareControllerError>)
} }

View File

@ -144,7 +144,7 @@ final class GameControllerNode: ViewControllerTracingNode {
if eventName == "share_game" || eventName == "share_score" { if eventName == "share_game" || eventName == "share_score" {
if let (botPeer, gameName) = self.shareData(), let addressName = botPeer.addressName, !addressName.isEmpty, !gameName.isEmpty { if let (botPeer, gameName) = self.shareData(), let addressName = botPeer.addressName, !addressName.isEmpty, !gameName.isEmpty {
if eventName == "share_score" { if eventName == "share_score" {
self.present(ShareController(context: self.context, subject: .fromExternal({ [weak self] peerIds, threadIds, text, account, _ in self.present(ShareController(context: self.context, subject: .fromExternal(1, { [weak self] peerIds, threadIds, requireStars, text, account, _ in
if let strongSelf = self, let message = strongSelf.message, let account = account as? ShareControllerAppAccountContext { if let strongSelf = self, let message = strongSelf.message, let account = account as? ShareControllerAppAccountContext {
let signals = peerIds.map { TelegramEngine(account: account.context.account).messages.forwardGameWithScore(messageId: message.id, to: $0, threadId: threadIds[$0], as: nil) } let signals = peerIds.map { TelegramEngine(account: account.context.account).messages.forwardGameWithScore(messageId: message.id, to: $0, threadId: threadIds[$0], as: nil) }
return .single(.preparing(false)) return .single(.preparing(false))

View File

@ -493,13 +493,10 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state:
if state.subscriptionEnabled { if state.subscriptionEnabled {
var label: String = "" var label: String = ""
if let subscriptionFee = state.subscriptionFee, subscriptionFee > StarsAmount.zero { if let subscriptionFee = state.subscriptionFee, subscriptionFee > StarsAmount.zero {
var usdRate = 0.012 let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0
if let usdWithdrawRate = configuration.usdWithdrawRate {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
}
label = presentationData.strings.InviteLink_Create_FeePerMonth("\(formatTonUsdValue(subscriptionFee.value, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))").string label = presentationData.strings.InviteLink_Create_FeePerMonth("\(formatTonUsdValue(subscriptionFee.value, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))").string
} }
entries.append(.subscriptionFee(presentationData.theme, presentationData.strings.InviteLink_Create_FeePlaceholder, isEditingEnabled, state.subscriptionFee, label, configuration.maxFee.flatMap({ StarsAmount(value: $0, nanos: 0) }))) entries.append(.subscriptionFee(presentationData.theme, presentationData.strings.InviteLink_Create_FeePlaceholder, isEditingEnabled, state.subscriptionFee, label, StarsAmount(value: configuration.maxFee, nanos: 0)))
} }
let infoText: String let infoText: String
if let _ = invite, state.subscriptionEnabled { if let _ = invite, state.subscriptionEnabled {

View File

@ -594,10 +594,7 @@ public final class InviteLinkViewController: ViewController {
guard let peer else { guard let peer else {
return return
} }
var usdRate = 0.012 let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0
if let usdWithdrawRate = configuration.usdWithdrawRate {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
}
let subscriptionController = context.sharedContext.makeStarsSubscriptionScreen(context: context, peer: peer, pricing: pricing, importer: importer, usdRate: usdRate) let subscriptionController = context.sharedContext.makeStarsSubscriptionScreen(context: context, peer: peer, pricing: pricing, importer: importer, usdRate: usdRate)
self?.controller?.push(subscriptionController) self?.controller?.push(subscriptionController)
}) })
@ -834,10 +831,7 @@ public final class InviteLinkViewController: ViewController {
context.account.postbox.loadedPeerWithId(adminId) context.account.postbox.loadedPeerWithId(adminId)
) |> deliverOnMainQueue).start(next: { [weak self] presentationData, state, requestsState, creatorPeer in ) |> deliverOnMainQueue).start(next: { [weak self] presentationData, state, requestsState, creatorPeer in
if let strongSelf = self { if let strongSelf = self {
var usdRate = 0.012 let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0
if let usdWithdrawRate = configuration.usdWithdrawRate {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
}
var entries: [InviteLinkViewEntry] = [] var entries: [InviteLinkViewEntry] = []

View File

@ -720,7 +720,7 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen
entries.append(.conversionInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_BroadcastConvertInfo(presentationStringsFormattedNumber(participantsLimit, presentationData.dateTimeFormat.groupingSeparator)).string)) entries.append(.conversionInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_BroadcastConvertInfo(presentationStringsFormattedNumber(participantsLimit, presentationData.dateTimeFormat.groupingSeparator)).string))
} }
if channel.hasPermission(.banMembers) { if cachedData.flags.contains(.paidMessagesAvailable) && channel.hasPermission(.banMembers) {
let sendPaidMessageStars = state.modifiedStarsAmount?.value ?? (cachedData.sendPaidMessageStars?.value ?? 0) let sendPaidMessageStars = state.modifiedStarsAmount?.value ?? (cachedData.sendPaidMessageStars?.value ?? 0)
let chargeEnabled = sendPaidMessageStars > 0 let chargeEnabled = sendPaidMessageStars > 0
entries.append(.chargeForMessages(presentationData.theme, presentationData.strings.GroupInfo_Permissions_ChargeForMessages, chargeEnabled)) entries.append(.chargeForMessages(presentationData.theme, presentationData.strings.GroupInfo_Permissions_ChargeForMessages, chargeEnabled))
@ -728,15 +728,13 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen
if chargeEnabled { if chargeEnabled {
var price: String = "" var price: String = ""
var usdRate = 0.012 let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0
if let usdWithdrawRate = configuration.usdWithdrawRate {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
}
price = "\(formatTonUsdValue(sendPaidMessageStars, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))" price = "\(formatTonUsdValue(sendPaidMessageStars, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))"
entries.append(.messagePriceHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePrice)) entries.append(.messagePriceHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePrice))
entries.append(.messagePrice(presentationData.theme, sendPaidMessageStars, price)) entries.append(.messagePrice(presentationData.theme, sendPaidMessageStars, price))
entries.append(.messagePriceInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePriceInfo(price).string)) entries.append(.messagePriceInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePriceInfo("\(configuration.paidMessageCommissionPermille / 10)", price).string))
} }
} }

View File

@ -50,7 +50,7 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry {
case footer(value: GlobalPrivacySettings.NonContactChatsPrivacy) case footer(value: GlobalPrivacySettings.NonContactChatsPrivacy)
case priceHeader case priceHeader
case price(value: Int64, price: String) case price(value: Int64, price: String)
case priceInfo(value: String) case priceInfo(commission: Int32, value: String)
case exceptionsHeader case exceptionsHeader
case exceptions(count: Int) case exceptions(count: Int)
case exceptionsInfo case exceptionsInfo
@ -153,8 +153,8 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry {
return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, minValue: 1, maxValue: 10000, value: value, price: price, sectionId: self.section, updated: { value in return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, minValue: 1, maxValue: 10000, value: value, price: price, sectionId: self.section, updated: { value in
arguments.updateValue(.paidMessages(StarsAmount(value: value, nanos: 0))) arguments.updateValue(.paidMessages(StarsAmount(value: value, nanos: 0)))
}) })
case let .priceInfo(value): case let .priceInfo(commission, value):
return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.Privacy_Messages_MessagePriceInfo(value).string), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.Privacy_Messages_MessagePriceInfo("\(commission)", value).string), sectionId: self.section)
case .exceptionsHeader: case .exceptionsHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.Privacy_Messages_RemoveFeeHeader, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.Privacy_Messages_RemoveFeeHeader, sectionId: self.section)
case let .exceptions(count): case let .exceptions(count):
@ -184,14 +184,12 @@ private func incomingMessagePrivacyScreenEntries(presentationData: PresentationD
entries.append(.footer(value: state.updatedValue)) entries.append(.footer(value: state.updatedValue))
entries.append(.priceHeader) entries.append(.priceHeader)
var usdRate = 0.012 let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0
if let usdWithdrawRate = configuration.usdWithdrawRate {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
}
let price = "\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))" let price = "\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))"
entries.append(.price(value: amount.value, price: price)) entries.append(.price(value: amount.value, price: price))
entries.append(.priceInfo(value: price)) entries.append(.priceInfo(commission: configuration.paidMessageCommissionPermille / 10, value: price))
entries.append(.exceptionsHeader) entries.append(.exceptionsHeader)
entries.append(.exceptions(count: state.disableFor.count)) entries.append(.exceptions(count: state.disableFor.count))
entries.append(.exceptionsInfo) entries.append(.exceptionsInfo)

View File

@ -676,6 +676,8 @@ public final class ShareController: ViewController {
messageCount = messages.count messageCount = messages.count
} else if case let .image(images) = self.subject { } else if case let .image(images) = self.subject {
messageCount = images.count messageCount = images.count
} else if case let .fromExternal(count, _) = self.subject {
messageCount = count
} }
var mediaParameters: ShareControllerSubject.MediaParameters? var mediaParameters: ShareControllerSubject.MediaParameters?
@ -1253,18 +1255,18 @@ public final class ShareController: ViewController {
} }
) )
|> take(1) |> take(1)
|> map { views -> ([EnginePeer.Id: EnginePeer?], [EnginePeer.Id: Int64]) in |> map { views -> ([EnginePeer.Id: EnginePeer?], [EnginePeer.Id: StarsAmount]) in
var result: [EnginePeer.Id: EnginePeer?] = [:] var result: [EnginePeer.Id: EnginePeer?] = [:]
var requiresStars: [EnginePeer.Id: Int64] = [:] var requiresStars: [EnginePeer.Id: StarsAmount] = [:]
for peerId in peerIds { for peerId in peerIds {
if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView, let peer = view.peer { if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView, let peer = view.peer {
result[peerId] = EnginePeer(peer) result[peerId] = EnginePeer(peer)
if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView { if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView {
if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData { if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData {
requiresStars[peerId] = cachedData.sendPaidMessageStars?.value requiresStars[peerId] = cachedData.sendPaidMessageStars
} }
} else if let channel = peer as? TelegramChannel { } else if let channel = peer as? TelegramChannel {
requiresStars[peerId] = channel.sendPaidMessageStars?.value requiresStars[peerId] = channel.sendPaidMessageStars
} }
} }
} }
@ -1284,7 +1286,7 @@ public final class ShareController: ViewController {
subject = selectedValue.subject subject = selectedValue.subject
} }
func transformMessages(_ messages: [StandaloneSendEnqueueMessage], showNames: Bool, silently: Bool, sendPaidMessageStars: Int64?) -> [StandaloneSendEnqueueMessage] { func transformMessages(_ messages: [StandaloneSendEnqueueMessage], showNames: Bool, silently: Bool, sendPaidMessageStars: StarsAmount?) -> [StandaloneSendEnqueueMessage] {
return messages.map { message in return messages.map { message in
var message = message var message = message
if !showNames { if !showNames {
@ -1296,7 +1298,7 @@ public final class ShareController: ViewController {
if silently { if silently {
message.isSilent = true message.isSilent = true
} }
message.sendPaidMessageStars = sendPaidMessageStars.flatMap { StarsAmount(value: $0, nanos: 0) } message.sendPaidMessageStars = sendPaidMessageStars
return message return message
} }
} }
@ -1839,8 +1841,8 @@ public final class ShareController: ViewController {
messages: messagesToEnqueue messages: messagesToEnqueue
)) ))
} }
case let .fromExternal(f): case let .fromExternal(_, f):
return f(peerIds, topicIds, text, strongSelf.currentContext, silently) return f(peerIds, topicIds, requiresStars, text, strongSelf.currentContext, silently)
|> map { state -> ShareState in |> map { state -> ShareState in
switch state { switch state {
case let .preparing(long): case let .preparing(long):
@ -1907,18 +1909,18 @@ public final class ShareController: ViewController {
} }
) )
|> take(1) |> take(1)
|> map { views -> ([EnginePeer.Id: EnginePeer?], [EnginePeer.Id: Int64]) in |> map { views -> ([EnginePeer.Id: EnginePeer?], [EnginePeer.Id: StarsAmount]) in
var result: [EnginePeer.Id: EnginePeer?] = [:] var result: [EnginePeer.Id: EnginePeer?] = [:]
var requiresStars: [EnginePeer.Id: Int64] = [:] var requiresStars: [EnginePeer.Id: StarsAmount] = [:]
for peerId in peerIds { for peerId in peerIds {
if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView, let peer = view.peer { if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView, let peer = view.peer {
result[peerId] = EnginePeer(peer) result[peerId] = EnginePeer(peer)
if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView { if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView {
if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData { if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData {
requiresStars[peerId] = cachedData.sendPaidMessageStars?.value requiresStars[peerId] = cachedData.sendPaidMessageStars
} }
} else if let channel = peer as? TelegramChannel { } else if let channel = peer as? TelegramChannel {
requiresStars[peerId] = channel.sendPaidMessageStars?.value requiresStars[peerId] = channel.sendPaidMessageStars
} }
} }
} }
@ -1938,7 +1940,7 @@ public final class ShareController: ViewController {
subject = selectedValue.subject subject = selectedValue.subject
} }
func transformMessages(_ messages: [EnqueueMessage], showNames: Bool, silently: Bool, sendPaidMessageStars: Int64?) -> [EnqueueMessage] { func transformMessages(_ messages: [EnqueueMessage], showNames: Bool, silently: Bool, sendPaidMessageStars: StarsAmount?) -> [EnqueueMessage] {
return messages.map { message in return messages.map { message in
return message.withUpdatedAttributes({ attributes in return message.withUpdatedAttributes({ attributes in
var attributes = attributes var attributes = attributes
@ -1949,7 +1951,7 @@ public final class ShareController: ViewController {
attributes.append(NotificationInfoMessageAttribute(flags: .muted)) attributes.append(NotificationInfoMessageAttribute(flags: .muted))
} }
if let sendPaidMessageStars { if let sendPaidMessageStars {
attributes.append(PaidStarsMessageAttribute(stars: StarsAmount(value: sendPaidMessageStars, nanos: 0), postponeSending: false)) attributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
} }
return attributes return attributes
}) })
@ -2321,8 +2323,8 @@ public final class ShareController: ViewController {
messagesToEnqueue = transformMessages(messagesToEnqueue, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId]) messagesToEnqueue = transformMessages(messagesToEnqueue, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(enqueueMessages(account: currentContext.context.account, peerId: peerId, messages: messagesToEnqueue)) shareSignals.append(enqueueMessages(account: currentContext.context.account, peerId: peerId, messages: messagesToEnqueue))
} }
case let .fromExternal(f): case let .fromExternal(_, f):
return f(peerIds, topicIds, text, currentContext, silently) return f(peerIds, topicIds, requiresStars, text, currentContext, silently)
|> map { state -> ShareState in |> map { state -> ShareState in
switch state { switch state {
case let .preparing(long): case let .preparing(long):

View File

@ -409,7 +409,7 @@ public func preparedShareItems(postbox: Postbox, network: Network, to peerId: Pe
}) })
} }
public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, auxiliaryMethods: AccountAuxiliaryMethods, to peerIds: [PeerId], threadIds: [PeerId: Int64], items: [PreparedShareItemContent], silently: Bool, additionalText: String) -> Signal<Float, Void> { public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, auxiliaryMethods: AccountAuxiliaryMethods, to peerIds: [PeerId], threadIds: [PeerId: Int64], requireStars: [PeerId: StarsAmount], items: [PreparedShareItemContent], silently: Bool, additionalText: String) -> Signal<Float, Void> {
var messages: [StandaloneSendEnqueueMessage] = [] var messages: [StandaloneSendEnqueueMessage] = []
var groupingKey: Int64? var groupingKey: Int64?
var mediaTypes: (photo: Int, video: Int, music: Int, other: Int) = (0, 0, 0, 0) var mediaTypes: (photo: Int, video: Int, music: Int, other: Int) = (0, 0, 0, 0)
@ -498,6 +498,16 @@ public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Net
var peerSignals: Signal<Float, StandaloneSendMessagesError> = .single(0.0) var peerSignals: Signal<Float, StandaloneSendMessagesError> = .single(0.0)
for peerId in peerIds { for peerId in peerIds {
var peerMessages = messages
if let amount = requireStars[peerId] {
var updatedMessages: [StandaloneSendEnqueueMessage] = []
for message in peerMessages {
var message = message
message.sendPaidMessageStars = amount
updatedMessages.append(message)
}
peerMessages = updatedMessages
}
peerSignals = peerSignals |> then(standaloneSendEnqueueMessages( peerSignals = peerSignals |> then(standaloneSendEnqueueMessages(
accountPeerId: accountPeerId, accountPeerId: accountPeerId,
postbox: postbox, postbox: postbox,
@ -506,7 +516,7 @@ public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Net
auxiliaryMethods: auxiliaryMethods, auxiliaryMethods: auxiliaryMethods,
peerId: peerId, peerId: peerId,
threadId: threadIds[peerId], threadId: threadIds[peerId],
messages: messages messages: peerMessages
) )
|> mapToSignal { status -> Signal<Float, StandaloneSendMessagesError> in |> mapToSignal { status -> Signal<Float, StandaloneSendMessagesError> in
switch status { switch status {

View File

@ -1390,7 +1390,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1314881805] = { return Api.payments.PaymentResult.parse_paymentResult($0) } dict[1314881805] = { return Api.payments.PaymentResult.parse_paymentResult($0) }
dict[-666824391] = { return Api.payments.PaymentResult.parse_paymentVerificationNeeded($0) } dict[-666824391] = { return Api.payments.PaymentResult.parse_paymentVerificationNeeded($0) }
dict[-74456004] = { return Api.payments.SavedInfo.parse_savedInfo($0) } dict[-74456004] = { return Api.payments.SavedInfo.parse_savedInfo($0) }
dict[-1779201615] = { return Api.payments.SavedStarGifts.parse_savedStarGifts($0) } dict[-418915641] = { return Api.payments.SavedStarGifts.parse_savedStarGifts($0) }
dict[377215243] = { return Api.payments.StarGiftUpgradePreview.parse_starGiftUpgradePreview($0) } dict[377215243] = { return Api.payments.StarGiftUpgradePreview.parse_starGiftUpgradePreview($0) }
dict[-2069218660] = { return Api.payments.StarGiftWithdrawalUrl.parse_starGiftWithdrawalUrl($0) } dict[-2069218660] = { return Api.payments.StarGiftWithdrawalUrl.parse_starGiftWithdrawalUrl($0) }
dict[-1877571094] = { return Api.payments.StarGifts.parse_starGifts($0) } dict[-1877571094] = { return Api.payments.StarGifts.parse_starGifts($0) }

View File

@ -1,16 +1,21 @@
public extension Api.payments { public extension Api.payments {
enum SavedStarGifts: TypeConstructorDescription { enum SavedStarGifts: TypeConstructorDescription {
case savedStarGifts(flags: Int32, count: Int32, chatNotificationsEnabled: Api.Bool?, gifts: [Api.SavedStarGift], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) case savedStarGifts(flags: Int32, count: Int32, chatNotificationsEnabled: Api.Bool?, pinnedToTop: [Int64]?, gifts: [Api.SavedStarGift], nextOffset: String?, chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let gifts, let nextOffset, let chats, let users): case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let pinnedToTop, let gifts, let nextOffset, let chats, let users):
if boxed { if boxed {
buffer.appendInt32(-1779201615) buffer.appendInt32(-418915641)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false) serializeInt32(count, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {chatNotificationsEnabled!.serialize(buffer, true)} if Int(flags) & Int(1 << 1) != 0 {chatNotificationsEnabled!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(pinnedToTop!.count))
for item in pinnedToTop! {
serializeInt64(item, buffer: buffer, boxed: false)
}}
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
buffer.appendInt32(Int32(gifts.count)) buffer.appendInt32(Int32(gifts.count))
for item in gifts { for item in gifts {
@ -33,8 +38,8 @@ public extension Api.payments {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let gifts, let nextOffset, let chats, let users): case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let pinnedToTop, let gifts, let nextOffset, let chats, let users):
return ("savedStarGifts", [("flags", flags as Any), ("count", count as Any), ("chatNotificationsEnabled", chatNotificationsEnabled as Any), ("gifts", gifts as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)]) return ("savedStarGifts", [("flags", flags as Any), ("count", count as Any), ("chatNotificationsEnabled", chatNotificationsEnabled as Any), ("pinnedToTop", pinnedToTop as Any), ("gifts", gifts as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)])
} }
} }
@ -47,29 +52,34 @@ public extension Api.payments {
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.Bool _3 = Api.parse(reader, signature: signature) as? Api.Bool
} } } }
var _4: [Api.SavedStarGift]? var _4: [Int64]?
if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self)
} }
var _5: [Api.SavedStarGift]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedStarGift.self) _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedStarGift.self)
} }
var _5: String? var _6: String?
if Int(_1!) & Int(1 << 0) != 0 {_5 = parseString(reader) } if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) }
var _6: [Api.Chat]? var _7: [Api.Chat]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
} }
var _7: [Api.User]? var _8: [Api.User]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = _4 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c5 = _5 != nil
let _c6 = _6 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
let _c7 = _7 != nil let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { let _c8 = _8 != nil
return Api.payments.SavedStarGifts.savedStarGifts(flags: _1!, count: _2!, chatNotificationsEnabled: _3, gifts: _4!, nextOffset: _5, chats: _6!, users: _7!) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
return Api.payments.SavedStarGifts.savedStarGifts(flags: _1!, count: _2!, chatNotificationsEnabled: _3, pinnedToTop: _4, gifts: _5!, nextOffset: _6, chats: _7!, users: _8!)
} }
else { else {
return nil return nil

View File

@ -9717,6 +9717,26 @@ public extension Api.functions.payments {
}) })
} }
} }
public extension Api.functions.payments {
static func toggleStarGiftsPinnedToTop(peer: Api.InputPeer, stargift: [Api.InputSavedStarGift]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(353626032)
peer.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(stargift.count))
for item in stargift {
item.serialize(buffer, true)
}
return (FunctionDescription(name: "payments.toggleStarGiftsPinnedToTop", parameters: [("peer", String(describing: peer)), ("stargift", String(describing: stargift))]), 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.payments { public extension Api.functions.payments {
static func transferStarGift(stargift: Api.InputSavedStarGift, toId: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func transferStarGift(stargift: Api.InputSavedStarGift, toId: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()

View File

@ -129,6 +129,7 @@ public func standaloneSendEnqueueMessages(
struct MessageResult { struct MessageResult {
var result: PendingMessageUploadedContentResult var result: PendingMessageUploadedContentResult
var media: [Media] var media: [Media]
var attributes: [MessageAttribute]
} }
let signals: [Signal<MessageResult, PendingMessageUploadError>] = messages.map { message in let signals: [Signal<MessageResult, PendingMessageUploadError>] = messages.map { message in
@ -178,6 +179,9 @@ public func standaloneSendEnqueueMessages(
if message.isSilent { if message.isSilent {
attributes.append(NotificationInfoMessageAttribute(flags: .muted)) attributes.append(NotificationInfoMessageAttribute(flags: .muted))
} }
if let sendPaidMessageStars = message.sendPaidMessageStars {
attributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
}
let content = messageContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: { _, _, _, _ in let content = messageContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: { _, _, _, _ in
return .single(nil) return .single(nil)
@ -191,7 +195,7 @@ public func standaloneSendEnqueueMessages(
} }
return contentResult return contentResult
|> map { contentResult in |> map { contentResult in
return MessageResult(result: contentResult, media: media) return MessageResult(result: contentResult, media: media, attributes: attributes)
} }
} }
@ -201,7 +205,7 @@ public func standaloneSendEnqueueMessages(
} }
|> mapToSignal { contentResults -> Signal<StandaloneSendMessageStatus, StandaloneSendMessagesError> in |> mapToSignal { contentResults -> Signal<StandaloneSendMessageStatus, StandaloneSendMessagesError> in
var progressSum: Float = 0.0 var progressSum: Float = 0.0
var allResults: [(result: PendingMessageUploadedContentAndReuploadInfo, media: [Media])] = [] var allResults: [(result: PendingMessageUploadedContentAndReuploadInfo, media: [Media], attributes: [MessageAttribute])] = []
var allDone = true var allDone = true
for result in contentResults { for result in contentResults {
switch result.result { switch result.result {
@ -209,13 +213,13 @@ public func standaloneSendEnqueueMessages(
allDone = false allDone = false
progressSum += value.progress progressSum += value.progress
case let .content(content): case let .content(content):
allResults.append((content, result.media)) allResults.append((content, result.media, result.attributes))
} }
} }
if allDone { if allDone {
var sendSignals: [Signal<Never, StandaloneSendMessagesError>] = [] var sendSignals: [Signal<Never, StandaloneSendMessagesError>] = []
for (content, media) in allResults { for (content, media, attributes) in allResults {
var text: String = "" var text: String = ""
switch content.content { switch content.content {
case let .text(textValue): case let .text(textValue):
@ -235,7 +239,7 @@ public func standaloneSendEnqueueMessages(
peerId: peerId, peerId: peerId,
content: content, content: content,
text: text, text: text,
attributes: [], attributes: attributes,
media: media, media: media,
threadId: threadId threadId: threadId
)) ))
@ -328,6 +332,7 @@ private func sendUploadedMessageContent(
var videoTimestamp: Int32? var videoTimestamp: Int32?
var sendAsPeerId: PeerId? var sendAsPeerId: PeerId?
var bubbleUpEmojiOrStickersets = false var bubbleUpEmojiOrStickersets = false
var allowPaidStars: Int64?
var flags: Int32 = 0 var flags: Int32 = 0
@ -365,6 +370,8 @@ private func sendUploadedMessageContent(
} else if let attribute = attribute as? ForwardVideoTimestampAttribute { } else if let attribute = attribute as? ForwardVideoTimestampAttribute {
flags |= Int32(1 << 20) flags |= Int32(1 << 20)
videoTimestamp = attribute.timestamp videoTimestamp = attribute.timestamp
} else if let attribute = attribute as? PaidStarsMessageAttribute {
allowPaidStars = attribute.stars.value
} }
} }
@ -390,6 +397,11 @@ private func sendUploadedMessageContent(
flags |= (1 << 13) flags |= (1 << 13)
} }
if let _ = allowPaidStars {
flags |= 1 << 21
}
let dependencyTag: PendingMessageRequestDependencyTag? = nil//(messageId: messageId) let dependencyTag: PendingMessageRequestDependencyTag? = nil//(messageId: messageId)
let sendMessageRequest: Signal<NetworkRequestResult<Api.Updates>, MTRpcError> let sendMessageRequest: Signal<NetworkRequestResult<Api.Updates>, MTRpcError>
@ -415,7 +427,7 @@ private func sendUploadedMessageContent(
} }
} }
sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: nil), info: .acknowledgement, tag: dependencyTag) sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars), info: .acknowledgement, tag: dependencyTag)
case let .media(inputMedia, text): case let .media(inputMedia, text):
if bubbleUpEmojiOrStickersets { if bubbleUpEmojiOrStickersets {
flags |= Int32(1 << 15) flags |= Int32(1 << 15)
@ -437,7 +449,7 @@ private func sendUploadedMessageContent(
} }
} }
sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: nil), tag: dependencyTag) sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars), tag: dependencyTag)
|> map(NetworkRequestResult.result) |> map(NetworkRequestResult.result)
case let .forward(sourceInfo): case let .forward(sourceInfo):
var topMsgId: Int32? var topMsgId: Int32?
@ -447,7 +459,7 @@ private func sendUploadedMessageContent(
} }
if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) { if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) {
sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: videoTimestamp, allowPaidStars: nil), tag: dependencyTag) sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars), tag: dependencyTag)
|> map(NetworkRequestResult.result) |> map(NetworkRequestResult.result)
} else { } else {
sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal")) sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal"))
@ -473,7 +485,7 @@ private func sendUploadedMessageContent(
} }
} }
sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyTo: replyTo, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, allowPaidStars: nil)) sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyTo: replyTo, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, allowPaidStars: allowPaidStars))
|> map(NetworkRequestResult.result) |> map(NetworkRequestResult.result)
case .messageScreenshot: case .messageScreenshot:
let replyTo: Api.InputReplyTo let replyTo: Api.InputReplyTo
@ -585,6 +597,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
var replyToStoryId: StoryId? var replyToStoryId: StoryId?
var scheduleTime: Int32? var scheduleTime: Int32?
var sendAsPeerId: PeerId? var sendAsPeerId: PeerId?
var allowPaidStars: Int64?
var flags: Int32 = 0 var flags: Int32 = 0
flags |= (1 << 7) flags |= (1 << 7)
@ -609,6 +622,8 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
scheduleTime = attribute.scheduleTime scheduleTime = attribute.scheduleTime
} else if let attribute = attribute as? SendAsMessageAttribute { } else if let attribute = attribute as? SendAsMessageAttribute {
sendAsPeerId = attribute.peerId sendAsPeerId = attribute.peerId
} else if let attribute = attribute as? PaidStarsMessageAttribute {
allowPaidStars = attribute.stars.value
} }
} }
@ -622,6 +637,11 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
flags |= (1 << 13) flags |= (1 << 13)
} }
if let _ = allowPaidStars {
flags |= 1 << 21
}
let sendMessageRequest: Signal<Api.Updates, NoError> let sendMessageRequest: Signal<Api.Updates, NoError>
switch content { switch content {
case let .text(text): case let .text(text):
@ -641,7 +661,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil) replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil)
} }
sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: nil)) sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars))
|> `catch` { _ -> Signal<Api.Updates, NoError> in |> `catch` { _ -> Signal<Api.Updates, NoError> in
return .complete() return .complete()
} }
@ -662,7 +682,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil) replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil)
} }
sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: nil)) sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars))
|> `catch` { _ -> Signal<Api.Updates, NoError> in |> `catch` { _ -> Signal<Api.Updates, NoError> in
return .complete() return .complete()
} }

View File

@ -25,6 +25,7 @@ public struct CachedChannelFlags: OptionSet {
public static let paidMediaAllowed = CachedChannelFlags(rawValue: 1 << 11) public static let paidMediaAllowed = CachedChannelFlags(rawValue: 1 << 11)
public static let canViewStarsRevenue = CachedChannelFlags(rawValue: 1 << 12) public static let canViewStarsRevenue = CachedChannelFlags(rawValue: 1 << 12)
public static let starGiftsAvailable = CachedChannelFlags(rawValue: 1 << 13) public static let starGiftsAvailable = CachedChannelFlags(rawValue: 1 << 13)
public static let paidMessagesAvailable = CachedChannelFlags(rawValue: 1 << 14)
} }
public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable { public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable {

View File

@ -375,22 +375,18 @@ public extension TelegramEngine.EngineData.Item {
} }
var key: PostboxViewKey { var key: PostboxViewKey {
return .cachedPeerData(peerId: self.id) return .peer(peerId: self.id, components: [.cachedData])
} }
func extract(view: PostboxView) -> Result { func extract(view: PostboxView) -> Result {
guard let view = view as? CachedPeerDataView else { guard let view = view as? PeerView else {
preconditionFailure() preconditionFailure()
} }
guard let cachedPeerData = view.cachedPeerData else { if let cachedPeerData = view.cachedData as? CachedUserData {
return nil return cachedPeerData.sendPaidMessageStars
} } else if let channel = peerViewMainPeer(view) as? TelegramChannel {
switch cachedPeerData {
case let user as CachedUserData:
return user.sendPaidMessageStars
case let channel as CachedChannelData:
return channel.sendPaidMessageStars return channel.sendPaidMessageStars
default: } else {
return nil return nil
} }
} }

View File

@ -2,15 +2,15 @@ import Foundation
import Postbox import Postbox
import SwiftSignalKit import SwiftSignalKit
func _internal_enqueueOutgoingMessageWithChatContextResult(account: Account, to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject?, replyToStoryId: StoryId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, correlationId: Int64?) -> Bool { func _internal_enqueueOutgoingMessageWithChatContextResult(account: Account, to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject?, replyToStoryId: StoryId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, sendPaidMessageStars: StarsAmount?, postpone: Bool, correlationId: Int64?) -> Bool {
guard let message = _internal_outgoingMessageWithChatContextResult(to: peerId, threadId: threadId, botId: botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) else { guard let message = _internal_outgoingMessageWithChatContextResult(to: peerId, threadId: threadId, botId: botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, sendPaidMessageStars: sendPaidMessageStars, postpone: postpone, correlationId: correlationId) else {
return false return false
} }
let _ = enqueueMessages(account: account, peerId: peerId, messages: [message]).start() let _ = enqueueMessages(account: account, peerId: peerId, messages: [message]).start()
return true return true
} }
func _internal_outgoingMessageWithChatContextResult(to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject?, replyToStoryId: StoryId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, correlationId: Int64?) -> EnqueueMessage? { func _internal_outgoingMessageWithChatContextResult(to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject?, replyToStoryId: StoryId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, sendPaidMessageStars: StarsAmount?, postpone: Bool, correlationId: Int64?) -> EnqueueMessage? {
var replyToMessageId = replyToMessageId var replyToMessageId = replyToMessageId
if replyToMessageId == nil, let threadId = threadId { if replyToMessageId == nil, let threadId = threadId {
replyToMessageId = EngineMessageReplySubject(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: MessageId.Id(clamping: threadId)), quote: nil) replyToMessageId = EngineMessageReplySubject(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: MessageId.Id(clamping: threadId)), quote: nil)
@ -32,6 +32,9 @@ func _internal_outgoingMessageWithChatContextResult(to peerId: PeerId, threadId:
if silentPosting { if silentPosting {
attributes.append(NotificationInfoMessageAttribute(flags: .muted)) attributes.append(NotificationInfoMessageAttribute(flags: .muted))
} }
if let sendPaidMessageStars {
attributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: postpone))
}
switch result.message { switch result.message {
case let .auto(caption, entities, replyMarkup): case let .auto(caption, entities, replyMarkup):
if let entities = entities { if let entities = entities {

View File

@ -247,13 +247,14 @@ public extension TelegramEngine {
storyId: StoryId? = nil, storyId: StoryId? = nil,
content: EngineOutgoingMessageContent, content: EngineOutgoingMessageContent,
silentPosting: Bool = false, silentPosting: Bool = false,
scheduleTime: Int32? = nil scheduleTime: Int32? = nil,
sendPaidMessageStars: StarsAmount? = nil
) -> Signal<[MessageId?], NoError> { ) -> Signal<[MessageId?], NoError> {
var message: EnqueueMessage? var message: EnqueueMessage?
if case let .preparedInlineMessage(preparedInlineMessage) = content { if case let .preparedInlineMessage(preparedInlineMessage) = content {
message = self.outgoingMessageWithChatContextResult(to: peerId, threadId: nil, botId: preparedInlineMessage.botId, result: preparedInlineMessage.result, replyToMessageId: replyToMessageId, replyToStoryId: storyId, hideVia: true, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: nil) message = self.outgoingMessageWithChatContextResult(to: peerId, threadId: nil, botId: preparedInlineMessage.botId, result: preparedInlineMessage.result, replyToMessageId: replyToMessageId, replyToStoryId: storyId, hideVia: true, silentPosting: silentPosting, scheduleTime: scheduleTime, sendPaidMessageStars: sendPaidMessageStars, postpone: false, correlationId: nil)
} else if case let .contextResult(results, result) = content { } else if case let .contextResult(results, result) = content {
message = self.outgoingMessageWithChatContextResult(to: peerId, threadId: nil, botId: results.botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: storyId, hideVia: true, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: nil) message = self.outgoingMessageWithChatContextResult(to: peerId, threadId: nil, botId: results.botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: storyId, hideVia: true, silentPosting: silentPosting, scheduleTime: scheduleTime, sendPaidMessageStars: sendPaidMessageStars, postpone: false, correlationId: nil)
} else { } else {
var attributes: [MessageAttribute] = [] var attributes: [MessageAttribute] = []
if silentPosting { if silentPosting {
@ -262,6 +263,9 @@ public extension TelegramEngine {
if let scheduleTime = scheduleTime { if let scheduleTime = scheduleTime {
attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime)) attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime))
} }
if let sendPaidMessageStars {
attributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
}
var text: String = "" var text: String = ""
var mediaReference: AnyMediaReference? var mediaReference: AnyMediaReference?
@ -301,12 +305,12 @@ public extension TelegramEngine {
) )
} }
public func enqueueOutgoingMessageWithChatContextResult(to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject? = nil, replyToStoryId: StoryId? = nil, hideVia: Bool = false, silentPosting: Bool = false, scheduleTime: Int32? = nil, correlationId: Int64? = nil) -> Bool { public func enqueueOutgoingMessageWithChatContextResult(to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject? = nil, replyToStoryId: StoryId? = nil, hideVia: Bool = false, silentPosting: Bool = false, scheduleTime: Int32? = nil, sendPaidMessageStars: StarsAmount?, postpone: Bool = false, correlationId: Int64? = nil) -> Bool {
return _internal_enqueueOutgoingMessageWithChatContextResult(account: self.account, to: peerId, threadId: threadId, botId: botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) return _internal_enqueueOutgoingMessageWithChatContextResult(account: self.account, to: peerId, threadId: threadId, botId: botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, sendPaidMessageStars: sendPaidMessageStars, postpone: postpone, correlationId: correlationId)
} }
public func outgoingMessageWithChatContextResult(to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject?, replyToStoryId: StoryId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, correlationId: Int64?) -> EnqueueMessage? { public func outgoingMessageWithChatContextResult(to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject?, replyToStoryId: StoryId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, sendPaidMessageStars: StarsAmount?, postpone: Bool, correlationId: Int64?) -> EnqueueMessage? {
return _internal_outgoingMessageWithChatContextResult(to: peerId, threadId: threadId, botId: botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) return _internal_outgoingMessageWithChatContextResult(to: peerId, threadId: threadId, botId: botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, sendPaidMessageStars: sendPaidMessageStars, postpone: postpone, correlationId: correlationId)
} }
public func setMessageReactions( public func setMessageReactions(

View File

@ -1064,7 +1064,8 @@ private final class ProfileGiftsContextImpl {
} }
return postbox.transaction { transaction -> ([ProfileGiftsContext.State.StarGift], Int32, String?, Bool?) in return postbox.transaction { transaction -> ([ProfileGiftsContext.State.StarGift], Int32, String?, Bool?) in
switch result { switch result {
case let .savedStarGifts(_, count, apiNotificationsEnabled, apiGifts, nextOffset, chats, users): case let .savedStarGifts(_, count, apiNotificationsEnabled, pinnedToTop, apiGifts, nextOffset, chats, users):
let _ = pinnedToTop
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)

View File

@ -100,7 +100,7 @@ func _internal_addGroupMember(account: Account, peerId: PeerId, memberId: PeerId
}) })
} }
return TelegramInvitePeersResult(forbiddenPeers: missingInviteesValue.compactMap { invitee -> TelegramForbiddenInvitePeer? in let result = TelegramInvitePeersResult(forbiddenPeers: missingInviteesValue.compactMap { invitee -> TelegramForbiddenInvitePeer? in
switch invitee { switch invitee {
case let .missingInvitee(flags, userId): case let .missingInvitee(flags, userId):
guard let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) else { guard let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) else {
@ -113,6 +113,10 @@ func _internal_addGroupMember(account: Account, peerId: PeerId, memberId: PeerId
) )
} }
}) })
let _ = _internal_updateIsPremiumRequiredToContact(account: account, peerIds: result.forbiddenPeers.map { $0.peer.id }).startStandalone()
return result
} }
|> mapError { _ -> AddGroupMemberError in } |> mapError { _ -> AddGroupMemberError in }
|> mapToSignal { result -> Signal<Void, AddGroupMemberError> in |> mapToSignal { result -> Signal<Void, AddGroupMemberError> in
@ -186,6 +190,8 @@ func _internal_addChannelMember(account: Account, peerId: PeerId, memberId: Peer
switch result { switch result {
case let .invitedUsers(updates, missingInvitees): case let .invitedUsers(updates, missingInvitees):
if case let .missingInvitee(flags, _) = missingInvitees.first { if case let .missingInvitee(flags, _) = missingInvitees.first {
let _ = _internal_updateIsPremiumRequiredToContact(account: account, peerIds: [memberPeer.id]).startStandalone()
return .fail(.restricted(TelegramForbiddenInvitePeer( return .fail(.restricted(TelegramForbiddenInvitePeer(
peer: EnginePeer(memberPeer), peer: EnginePeer(memberPeer),
canInviteWithPremium: (flags & (1 << 0)) != 0, canInviteWithPremium: (flags & (1 << 0)) != 0,
@ -302,7 +308,7 @@ func _internal_addChannelMembers(account: Account, peerId: PeerId, memberIds: [P
account.viewTracker.forceUpdateCachedPeerData(peerId: peerId) account.viewTracker.forceUpdateCachedPeerData(peerId: peerId)
return account.postbox.transaction { transaction -> TelegramInvitePeersResult in return account.postbox.transaction { transaction -> TelegramInvitePeersResult in
return TelegramInvitePeersResult(forbiddenPeers: missingInviteesValue.compactMap { invitee -> TelegramForbiddenInvitePeer? in let result = TelegramInvitePeersResult(forbiddenPeers: missingInviteesValue.compactMap { invitee -> TelegramForbiddenInvitePeer? in
switch invitee { switch invitee {
case let .missingInvitee(flags, userId): case let .missingInvitee(flags, userId):
guard let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) else { guard let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) else {
@ -315,6 +321,8 @@ func _internal_addChannelMembers(account: Account, peerId: PeerId, memberIds: [P
) )
} }
}) })
let _ = _internal_updateIsPremiumRequiredToContact(account: account, peerIds: result.forbiddenPeers.map { $0.peer.id }).startStandalone()
return result
} }
|> castError(AddChannelMemberError.self) |> castError(AddChannelMemberError.self)
} }

View File

@ -620,6 +620,10 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
if (flags2 & Int32(1 << 19)) != 0 { if (flags2 & Int32(1 << 19)) != 0 {
channelFlags.insert(.starGiftsAvailable) channelFlags.insert(.starGiftsAvailable)
} }
if (flags2 & Int32(1 << 20)) != 0 {
channelFlags.insert(.paidMessagesAvailable)
}
let sendAsPeerId = defaultSendAs?.peerId let sendAsPeerId = defaultSendAs?.peerId
let linkedDiscussionPeerId: PeerId? let linkedDiscussionPeerId: PeerId?

View File

@ -2877,7 +2877,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
bottomBubbleAttributes = contentPropertiesAndLayouts[i + 1].3 bottomBubbleAttributes = contentPropertiesAndLayouts[i + 1].3
} }
if i == 0 { if i == 0 || (i == 1 && contentPropertiesAndLayouts[0].1.isDetached) {
topPosition = firstNodeTopPosition topPosition = firstNodeTopPosition
} else { } else {
topPosition = .Neighbour(topBubbleAttributes.isAttachment, topBubbleAttributes.neighborType, topBubbleAttributes.neighborSpacing) topPosition = .Neighbour(topBubbleAttributes.isAttachment, topBubbleAttributes.neighborType, topBubbleAttributes.neighborSpacing)
@ -3002,11 +3002,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
} }
let (size, apply) = finalize(maxContentWidth) let (size, apply) = finalize(maxContentWidth)
var containerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentNodeOriginY), size: size) let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentNodeOriginY), size: size)
if size.height == 33.0 && detachedContentNodesHeight > 0.0 {
//TODO:unmock
containerFrame = containerFrame.offsetBy(dx: 0.0, dy: 2.0)
}
contentNodeFramesPropertiesAndApply.append((containerFrame, properties, contentGroupId == nil, apply)) contentNodeFramesPropertiesAndApply.append((containerFrame, properties, contentGroupId == nil, apply))
if contentProperties.neighborType == .media && unlockButtonPosition == nil { if contentProperties.neighborType == .media && unlockButtonPosition == nil {

View File

@ -330,21 +330,32 @@ private class ChatMessagePaymentAlertController: AlertController {
self.parentNavigationController = navigationController self.parentNavigationController = navigationController
super.init(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) super.init(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
self.willDismiss = { [weak self] in
guard let self else {
return
}
self.animateOut()
}
} }
required public init(coder aDecoder: NSCoder) { required public init(coder aDecoder: NSCoder) {
preconditionFailure() preconditionFailure()
} }
override func dismissAnimated() { private func animateOut() {
super.dismissAnimated()
if let view = self.balance.view { if let view = self.balance.view {
view.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4, removeOnCompletion: false) view.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4, removeOnCompletion: false)
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
} }
} }
override func dismissAnimated() {
super.dismissAnimated()
self.animateOut()
}
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition) super.containerLayoutUpdated(layout, transition: transition)

View File

@ -251,6 +251,9 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
let infoConstrainedSize = CGSize(width: constrainedWidth * 0.7, height: CGFloat.greatestFiniteMagnitude) let infoConstrainedSize = CGSize(width: constrainedWidth * 0.7, height: CGFloat.greatestFiniteMagnitude)
var maxTitleWidth: CGFloat = 0.0
var maxValueWidth: CGFloat = 0.0
var registrationDateText: String? var registrationDateText: String?
let registrationDateTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)? let registrationDateTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)?
let registrationDateValueLayoutAndApply: (TextNodeLayout, () -> TextNode)? let registrationDateValueLayoutAndApply: (TextNodeLayout, () -> TextNode)?
@ -272,7 +275,8 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
backgroundSize.height += verticalSpacing backgroundSize.height += verticalSpacing
backgroundSize.height += registrationDateValueLayoutAndApply?.0.size.height ?? 0 backgroundSize.height += registrationDateValueLayoutAndApply?.0.size.height ?? 0
backgroundSize.width = max(backgroundSize.width, horizontalContentInset * 2.0 + (registrationDateTitleLayoutAndApply?.0.size.width ?? 0) + attributeSpacing + (registrationDateValueLayoutAndApply?.0.size.width ?? 0)) maxTitleWidth = max(maxTitleWidth, (registrationDateTitleLayoutAndApply?.0.size.width ?? 0))
maxValueWidth = max(maxValueWidth, (registrationDateValueLayoutAndApply?.0.size.width ?? 0))
} else { } else {
registrationDateTitleLayoutAndApply = nil registrationDateTitleLayoutAndApply = nil
registrationDateValueLayoutAndApply = nil registrationDateValueLayoutAndApply = nil
@ -297,7 +301,8 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
backgroundSize.height += verticalSpacing backgroundSize.height += verticalSpacing
backgroundSize.height += phoneCountryValueLayoutAndApply?.0.size.height ?? 0 backgroundSize.height += phoneCountryValueLayoutAndApply?.0.size.height ?? 0
backgroundSize.width = max(backgroundSize.width, horizontalContentInset * 2.0 + (phoneCountryTitleLayoutAndApply?.0.size.width ?? 0) + attributeSpacing + (phoneCountryValueLayoutAndApply?.0.size.width ?? 0)) maxTitleWidth = max(maxTitleWidth, (phoneCountryTitleLayoutAndApply?.0.size.width ?? 0))
maxValueWidth = max(maxValueWidth, (phoneCountryValueLayoutAndApply?.0.size.width ?? 0))
} else { } else {
phoneCountryTitleLayoutAndApply = nil phoneCountryTitleLayoutAndApply = nil
phoneCountryValueLayoutAndApply = nil phoneCountryValueLayoutAndApply = nil
@ -322,12 +327,15 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
backgroundSize.height += verticalSpacing backgroundSize.height += verticalSpacing
backgroundSize.height += locationCountryValueLayoutAndApply?.0.size.height ?? 0 backgroundSize.height += locationCountryValueLayoutAndApply?.0.size.height ?? 0
backgroundSize.width = max(backgroundSize.width, horizontalContentInset * 2.0 + (locationCountryTitleLayoutAndApply?.0.size.width ?? 0) + attributeSpacing + (locationCountryValueLayoutAndApply?.0.size.width ?? 0)) maxTitleWidth = max(maxTitleWidth, (locationCountryTitleLayoutAndApply?.0.size.width ?? 0))
maxValueWidth = max(maxValueWidth, (locationCountryValueLayoutAndApply?.0.size.width ?? 0))
} else { } else {
locationCountryTitleLayoutAndApply = nil locationCountryTitleLayoutAndApply = nil
locationCountryValueLayoutAndApply = nil locationCountryValueLayoutAndApply = nil
} }
backgroundSize.width = horizontalContentInset * 3.0 + maxTitleWidth + attributeSpacing + maxValueWidth
let (groupsLayout, groupsApply) = makeGroupsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "No groups in common", font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (groupsLayout, groupsApply) = makeGroupsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "No groups in common", font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
backgroundSize.height += verticalSpacing * 2.0 + paragraphSpacing backgroundSize.height += verticalSpacing * 2.0 + paragraphSpacing
backgroundSize.height += groupsLayout.size.height backgroundSize.height += groupsLayout.size.height

View File

@ -502,9 +502,7 @@ final class GiftOptionsScreenComponent: Component {
return return
} }
//TODO:unmock
let context = component.context let context = component.context
let alertController = giftTransferAlertController( let alertController = giftTransferAlertController(
context: context, context: context,
gift: transferGift, gift: transferGift,
@ -517,9 +515,18 @@ final class GiftOptionsScreenComponent: Component {
guard let controller, let navigationController = controller.navigationController as? NavigationController else { guard let controller, let navigationController = controller.navigationController as? NavigationController else {
return return
} }
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is ContactSelectionController) && !($0 is GiftOptionsScreen) }
if peer.id.namespace == Namespaces.Peer.CloudChannel { if peer.id.namespace == Namespaces.Peer.CloudChannel {
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) }
var foundController = false
for controller in controllers.reversed() {
if let controller = controller as? PeerInfoScreen, controller.peerId == component.peerId {
foundController = true
break
}
}
if !foundController {
if let controller = context.sharedContext.makePeerInfoController( if let controller = context.sharedContext.makePeerInfoController(
context: context, context: context,
updatedPresentationData: nil, updatedPresentationData: nil,
@ -531,23 +538,31 @@ final class GiftOptionsScreenComponent: Component {
) { ) {
controllers.append(controller) controllers.append(controller)
} }
}
navigationController.setViewControllers(controllers, animated: true)
} else { } else {
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) }
var foundController = false var foundController = false
for controller in controllers.reversed() { for controller in controllers.reversed() {
if let chatController = controller as? ChatController, case .peer(id: peer.id) = chatController.chatLocation { if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation {
chatController.hintPlayNextOutgoingGift() chatController.hintPlayNextOutgoingGift()
foundController = true foundController = true
break break
} }
} }
if !foundController { if !foundController {
let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.default), params: nil) let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil)
chatController.hintPlayNextOutgoingGift() chatController.hintPlayNextOutgoingGift()
controllers.append(chatController) controllers.append(chatController)
} }
}
navigationController.setViewControllers(controllers, animated: true) navigationController.setViewControllers(controllers, animated: true)
} }
if let completion = component.completion {
completion()
}
}
) )
controller.present(alertController, in: .window(.root)) controller.present(alertController, in: .window(.root))
} }

View File

@ -253,7 +253,7 @@ public func presentedLegacyShortcutCamera(context: AccountContext, saveCapturedM
nativeGenerator(_1, _2, _3, nil) nativeGenerator(_1, _2, _3, nil)
}) })
if let parentController = parentController { if let parentController = parentController {
parentController.present(ShareController(context: context, subject: .fromExternal({ peerIds, _, text, account, silently in parentController.present(ShareController(context: context, subject: .fromExternal(1, { peerIds, _, _, text, account, silently in
guard let account = account as? ShareControllerAppAccountContext else { guard let account = account as? ShareControllerAppAccountContext else {
return .single(.done) return .single(.done)
} }

View File

@ -214,6 +214,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
strings: presentationData.strings, strings: presentationData.strings,
style: .media, style: .media,
placeholder: .plain(presentationData.strings.MediaPicker_AddCaption), placeholder: .plain(presentationData.strings.MediaPicker_AddCaption),
sendPaidMessageStars: nil,
maxLength: Int(self.context.userLimits.maxCaptionLength), maxLength: Int(self.context.userLimits.maxCaptionLength),
queryTypes: [.mention, .hashtag], queryTypes: [.mention, .hashtag],
alwaysDarkWhenHasText: false, alwaysDarkWhenHasText: false,

View File

@ -1365,6 +1365,7 @@ final class MediaEditorScreenComponent: Component {
strings: environment.strings, strings: environment.strings,
style: .editor, style: .editor,
placeholder: .plain(environment.strings.Story_Editor_InputPlaceholderAddCaption), placeholder: .plain(environment.strings.Story_Editor_InputPlaceholderAddCaption),
sendPaidMessageStars: nil,
maxLength: Int(component.context.userLimits.maxStoryCaptionLength), maxLength: Int(component.context.userLimits.maxStoryCaptionLength),
queryTypes: [.mention, .hashtag], queryTypes: [.mention, .hashtag],
alwaysDarkWhenHasText: false, alwaysDarkWhenHasText: false,

View File

@ -251,6 +251,7 @@ final class StoryPreviewComponent: Component {
strings: presentationData.strings, strings: presentationData.strings,
style: .story, style: .story,
placeholder: .plain(presentationData.strings.Story_InputPlaceholderReplyPrivately), placeholder: .plain(presentationData.strings.Story_InputPlaceholderReplyPrivately),
sendPaidMessageStars: nil,
maxLength: nil, maxLength: nil,
queryTypes: [], queryTypes: [],
alwaysDarkWhenHasText: false, alwaysDarkWhenHasText: false,

View File

@ -160,6 +160,7 @@ public final class MessageInputPanelComponent: Component {
public let strings: PresentationStrings public let strings: PresentationStrings
public let style: Style public let style: Style
public let placeholder: Placeholder public let placeholder: Placeholder
public let sendPaidMessageStars: StarsAmount?
public let maxLength: Int? public let maxLength: Int?
public let queryTypes: ContextQueryTypes public let queryTypes: ContextQueryTypes
public let alwaysDarkWhenHasText: Bool public let alwaysDarkWhenHasText: Bool
@ -218,6 +219,7 @@ public final class MessageInputPanelComponent: Component {
strings: PresentationStrings, strings: PresentationStrings,
style: Style, style: Style,
placeholder: Placeholder, placeholder: Placeholder,
sendPaidMessageStars: StarsAmount?,
maxLength: Int?, maxLength: Int?,
queryTypes: ContextQueryTypes, queryTypes: ContextQueryTypes,
alwaysDarkWhenHasText: Bool, alwaysDarkWhenHasText: Bool,
@ -276,6 +278,7 @@ public final class MessageInputPanelComponent: Component {
self.style = style self.style = style
self.nextInputMode = nextInputMode self.nextInputMode = nextInputMode
self.placeholder = placeholder self.placeholder = placeholder
self.sendPaidMessageStars = sendPaidMessageStars
self.maxLength = maxLength self.maxLength = maxLength
self.queryTypes = queryTypes self.queryTypes = queryTypes
self.alwaysDarkWhenHasText = alwaysDarkWhenHasText self.alwaysDarkWhenHasText = alwaysDarkWhenHasText
@ -346,6 +349,9 @@ public final class MessageInputPanelComponent: Component {
if lhs.placeholder != rhs.placeholder { if lhs.placeholder != rhs.placeholder {
return false return false
} }
if lhs.sendPaidMessageStars != rhs.sendPaidMessageStars {
return false
}
if lhs.maxLength != rhs.maxLength { if lhs.maxLength != rhs.maxLength {
return false return false
} }
@ -849,6 +855,37 @@ public final class MessageInputPanelComponent: Component {
) )
let isEditing = self.textFieldExternalState.isEditing || component.forceIsEditing let isEditing = self.textFieldExternalState.isEditing || component.forceIsEditing
let placeholderTransition: ComponentTransition = (previousPlaceholder != nil && previousPlaceholder != component.placeholder) ? ComponentTransition(animation: .curve(duration: 0.3, curve: .spring)) : .immediate
let placeholderSize: CGSize
if case let .plain(string) = component.placeholder, string.contains("#") {
let attributedPlaceholder = NSMutableAttributedString(string: string, font:Font.regular(17.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.3))
if let range = attributedPlaceholder.string.range(of: "#") {
attributedPlaceholder.addAttribute(.attachment, value: PresentationResourcesChat.chatPlaceholderStarIcon(component.theme)!, range: NSRange(range, in: attributedPlaceholder.string))
attributedPlaceholder.addAttribute(.foregroundColor, value: UIColor(rgb: 0xffffff, alpha: 0.3), range: NSRange(range, in: attributedPlaceholder.string))
attributedPlaceholder.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedPlaceholder.string))
}
placeholderSize = self.placeholder.update(
transition: placeholderTransition,
component: AnyComponent(MultilineTextComponent(text: .plain(attributedPlaceholder))),
environment: {},
containerSize: availableTextFieldSize
)
let vibrancyAttributedPlaceholder = NSMutableAttributedString(string: string, font:Font.regular(17.0), textColor: UIColor.black)
if let range = vibrancyAttributedPlaceholder.string.range(of: "#") {
vibrancyAttributedPlaceholder.addAttribute(.attachment, value: PresentationResourcesChat.chatPlaceholderStarIcon(component.theme)!, range: NSRange(range, in: vibrancyAttributedPlaceholder.string))
vibrancyAttributedPlaceholder.addAttribute(.foregroundColor, value: UIColor.black, range: NSRange(range, in: vibrancyAttributedPlaceholder.string))
vibrancyAttributedPlaceholder.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: vibrancyAttributedPlaceholder.string))
}
let _ = self.vibrancyPlaceholder.update(
transition: placeholderTransition,
component: AnyComponent(MultilineTextComponent(text: .plain(attributedPlaceholder))),
environment: {},
containerSize: availableTextFieldSize
)
} else {
var placeholderItems: [AnimatedTextComponent.Item] = [] var placeholderItems: [AnimatedTextComponent.Item] = []
switch component.placeholder { switch component.placeholder {
case let .plain(string): case let .plain(string):
@ -864,8 +901,7 @@ public final class MessageInputPanelComponent: Component {
} }
} }
let placeholderTransition: ComponentTransition = (previousPlaceholder != nil && previousPlaceholder != component.placeholder) ? ComponentTransition(animation: .curve(duration: 0.3, curve: .spring)) : .immediate placeholderSize = self.placeholder.update(
let placeholderSize = self.placeholder.update(
transition: placeholderTransition, transition: placeholderTransition,
component: AnyComponent(AnimatedTextComponent( component: AnyComponent(AnimatedTextComponent(
font: Font.regular(17.0), font: Font.regular(17.0),
@ -886,6 +922,8 @@ public final class MessageInputPanelComponent: Component {
environment: {}, environment: {},
containerSize: availableTextFieldSize containerSize: availableTextFieldSize
) )
}
if !isEditing && component.setMediaRecordingActive == nil { if !isEditing && component.setMediaRecordingActive == nil {
insets.right = defaultInsets.left insets.right = defaultInsets.left
} }

View File

@ -31,6 +31,7 @@ swift_library(
"//submodules/PeerPresenceStatusManager", "//submodules/PeerPresenceStatusManager",
"//submodules/UndoUI", "//submodules/UndoUI",
"//submodules/AnimatedAvatarSetNode", "//submodules/AnimatedAvatarSetNode",
"//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -17,6 +17,7 @@ import UndoUI
import AnimatedAvatarSetNode import AnimatedAvatarSetNode
import AvatarNode import AvatarNode
import TelegramStringFormatting import TelegramStringFormatting
import ChatMessagePaymentAlertController
private final class SendInviteLinkScreenComponent: Component { private final class SendInviteLinkScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -26,19 +27,22 @@ private final class SendInviteLinkScreenComponent: Component {
let link: String? let link: String?
let peers: [TelegramForbiddenInvitePeer] let peers: [TelegramForbiddenInvitePeer]
let peerPresences: [EnginePeer.Id: EnginePeer.Presence] let peerPresences: [EnginePeer.Id: EnginePeer.Presence]
let sendPaidMessageStars: [EnginePeer.Id: StarsAmount]
init( init(
context: AccountContext, context: AccountContext,
peer: EnginePeer, peer: EnginePeer,
link: String?, link: String?,
peers: [TelegramForbiddenInvitePeer], peers: [TelegramForbiddenInvitePeer],
peerPresences: [EnginePeer.Id: EnginePeer.Presence] peerPresences: [EnginePeer.Id: EnginePeer.Presence],
sendPaidMessageStars: [EnginePeer.Id: StarsAmount]
) { ) {
self.context = context self.context = context
self.peer = peer self.peer = peer
self.link = link self.link = link
self.peers = peers self.peers = peers
self.peerPresences = peerPresences self.peerPresences = peerPresences
self.sendPaidMessageStars = sendPaidMessageStars
} }
static func ==(lhs: SendInviteLinkScreenComponent, rhs: SendInviteLinkScreenComponent) -> Bool { static func ==(lhs: SendInviteLinkScreenComponent, rhs: SendInviteLinkScreenComponent) -> Bool {
@ -54,6 +58,9 @@ private final class SendInviteLinkScreenComponent: Component {
if lhs.peerPresences != rhs.peerPresences { if lhs.peerPresences != rhs.peerPresences {
return false return false
} }
if lhs.sendPaidMessageStars != rhs.sendPaidMessageStars {
return false
}
return true return true
} }
@ -266,6 +273,38 @@ private final class SendInviteLinkScreenComponent: Component {
} }
} }
private func presentPaidMessageAlertIfNeeded(peers: [EnginePeer], requiresStars: [EnginePeer.Id: StarsAmount], completion: @escaping () -> Void) {
guard let component = self.component else {
completion()
return
}
var totalAmount: StarsAmount = .zero
for peer in peers {
if let amount = requiresStars[peer.id] {
totalAmount = totalAmount + amount
}
}
if totalAmount.value > 0 {
let controller = chatMessagePaymentAlertController(
context: component.context,
presentationData: component.context.sharedContext.currentPresentationData.with { $0 },
updatedPresentationData: nil,
peers: peers,
count: 1,
amount: totalAmount,
totalAmount: totalAmount,
hasCheck: false,
navigationController: self.environment?.controller()?.navigationController as? NavigationController,
completion: { _ in
completion()
}
)
self.environment?.controller()?.present(controller, in: .window(.root))
} else {
completion()
}
}
func update(component: SendInviteLinkScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize { func update(component: SendInviteLinkScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
let environment = environment[ViewControllerComponentContainer.Environment.self].value let environment = environment[ViewControllerComponentContainer.Environment.self].value
let themeUpdated = self.environment?.theme !== environment.theme let themeUpdated = self.environment?.theme !== environment.theme
@ -851,7 +890,22 @@ private final class SendInviteLinkScreenComponent: Component {
} else if let link = component.link { } else if let link = component.link {
let selectedPeers = component.peers.filter { self.selectedItems.contains($0.peer.id) } let selectedPeers = component.peers.filter { self.selectedItems.contains($0.peer.id) }
let _ = enqueueMessagesToMultiplePeers(account: component.context.account, peerIds: Array(self.selectedItems), threadIds: [:], messages: [.message(text: link, attributes: [], inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).start() self.presentPaidMessageAlertIfNeeded(
peers: selectedPeers.map { $0.peer },
requiresStars: component.sendPaidMessageStars,
completion: { [weak self] in
guard let self, let component = self.component, let controller = self.environment?.controller() else {
return
}
for peerId in Array(self.selectedItems) {
var messageAttributes: [EngineMessage.Attribute] = []
if let sendPaidMessageStars = component.sendPaidMessageStars[peerId] {
messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
}
let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: [.message(text: link, attributes: messageAttributes, inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).startStandalone()
}
let text: String let text: String
if selectedPeers.count == 1 { if selectedPeers.count == 1 {
text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string
@ -865,6 +919,8 @@ private final class SendInviteLinkScreenComponent: Component {
controller.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: false, text: text), elevatedLayout: false, action: { _ in return false }), in: .window(.root)) controller.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: false, text: text), elevatedLayout: false, action: { _ in return false }), in: .window(.root))
controller.dismiss() controller.dismiss()
}
)
} else { } else {
controller.dismiss() controller.dismiss()
} }
@ -1083,16 +1139,21 @@ public class SendInviteLinkScreen: ViewControllerComponentContainer {
self.link = link self.link = link
self.peers = peers self.peers = peers
super.init(context: context, component: SendInviteLinkScreenComponent(context: context, peer: peer, link: link, peers: peers, peerPresences: [:]), navigationBarAppearance: .none) super.init(context: context, component: SendInviteLinkScreenComponent(context: context, peer: peer, link: link, peers: peers, peerPresences: [:], sendPaidMessageStars: [:]), navigationBarAppearance: .none)
self.statusBar.statusBarStyle = .Ignore self.statusBar.statusBarStyle = .Ignore
self.navigationPresentation = .flatModal self.navigationPresentation = .flatModal
self.blocksBackgroundWhenInOverlay = true self.blocksBackgroundWhenInOverlay = true
self.presenceDisposable = (context.engine.data.subscribe(EngineDataMap( self.presenceDisposable = (context.engine.data.subscribe(
EngineDataMap(
peers.map(\.peer.id).map(TelegramEngine.EngineData.Item.Peer.Presence.init(id:)) peers.map(\.peer.id).map(TelegramEngine.EngineData.Item.Peer.Presence.init(id:))
)) ),
|> deliverOnMainQueue).start(next: { [weak self] presences in EngineDataMap(
peers.map(\.peer.id).map(TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars.init(id:))
)
)
|> deliverOnMainQueue).start(next: { [weak self] presences, sendPaidMessageStars in
guard let self else { guard let self else {
return return
} }
@ -1102,7 +1163,13 @@ public class SendInviteLinkScreen: ViewControllerComponentContainer {
parsedPresences[id] = presence parsedPresences[id] = presence
} }
} }
self.updateComponent(component: AnyComponent(SendInviteLinkScreenComponent(context: context, peer: peer, link: link, peers: peers, peerPresences: parsedPresences)), transition: .immediate) var parsedSendPaidMessageStars: [EnginePeer.Id: StarsAmount] = [:]
for (id, sendPaidMessageStars) in sendPaidMessageStars {
if let sendPaidMessageStars {
parsedSendPaidMessageStars[id] = sendPaidMessageStars
}
}
self.updateComponent(component: AnyComponent(SendInviteLinkScreenComponent(context: context, peer: peer, link: link, peers: peers, peerPresences: parsedPresences, sendPaidMessageStars: parsedSendPaidMessageStars)), transition: .immediate)
}) })
} }

View File

@ -525,8 +525,8 @@ public class ShareRootControllerImpl {
} |> runOn(Queue.mainQueue()) } |> runOn(Queue.mainQueue())
} }
let sentItems: ([PeerId], [PeerId: Int64], [PreparedShareItemContent], ShareControllerAccountContext, Bool, String) -> Signal<ShareControllerExternalStatus, NoError> = { peerIds, threadIds, contents, account, silently, additionalText in let sentItems: ([PeerId], [PeerId: Int64], [PeerId: StarsAmount], [PreparedShareItemContent], ShareControllerAccountContext, Bool, String) -> Signal<ShareControllerExternalStatus, NoError> = { peerIds, threadIds, requireStars, contents, account, silently, additionalText in
let sentItems = sentShareItems(accountPeerId: account.accountPeerId, postbox: account.stateManager.postbox, network: account.stateManager.network, stateManager: account.stateManager, auxiliaryMethods: makeTelegramAccountAuxiliaryMethods(uploadInBackground: nil), to: peerIds, threadIds: threadIds, items: contents, silently: silently, additionalText: additionalText) let sentItems = sentShareItems(accountPeerId: account.accountPeerId, postbox: account.stateManager.postbox, network: account.stateManager.network, stateManager: account.stateManager, auxiliaryMethods: makeTelegramAccountAuxiliaryMethods(uploadInBackground: nil), to: peerIds, threadIds: threadIds, requireStars: requireStars, items: contents, silently: silently, additionalText: additionalText)
|> `catch` { _ -> Signal< |> `catch` { _ -> Signal<
Float, NoError> in Float, NoError> in
return .complete() return .complete()
@ -538,7 +538,19 @@ public class ShareRootControllerImpl {
|> then(.single(.done)) |> then(.single(.done))
} }
let shareController = ShareController(environment: environment, currentContext: context, subject: .fromExternal({ peerIds, threadIds, additionalText, account, silently in var itemCount = 1
if let extensionItems = self?.getExtensionContext()?.inputItems as? [NSExtensionItem] {
for item in extensionItems {
if let attachments = item.attachments {
itemCount = 0
for _ in attachments {
itemCount += 1
}
}
}
}
let shareController = ShareController(environment: environment, currentContext: context, subject: .fromExternal(itemCount, { peerIds, threadIds, requireStars, additionalText, account, silently in
if let strongSelf = self, let inputItems = strongSelf.getExtensionContext()?.inputItems, !inputItems.isEmpty, !peerIds.isEmpty { if let strongSelf = self, let inputItems = strongSelf.getExtensionContext()?.inputItems, !inputItems.isEmpty, !peerIds.isEmpty {
let rawSignals = TGItemProviderSignals.itemSignals(forInputItems: inputItems)! let rawSignals = TGItemProviderSignals.itemSignals(forInputItems: inputItems)!
return preparedShareItems(postbox: account.stateManager.postbox, network: account.stateManager.network, to: peerIds[0], dataItems: rawSignals) return preparedShareItems(postbox: account.stateManager.postbox, network: account.stateManager.network, to: peerIds[0], dataItems: rawSignals)
@ -564,11 +576,11 @@ public class ShareRootControllerImpl {
return requestUserInteraction(value) return requestUserInteraction(value)
|> castError(ShareControllerError.self) |> castError(ShareControllerError.self)
|> mapToSignal { contents -> Signal<ShareControllerExternalStatus, ShareControllerError> in |> mapToSignal { contents -> Signal<ShareControllerExternalStatus, ShareControllerError> in
return sentItems(peerIds, threadIds, contents, account, silently, additionalText) return sentItems(peerIds, threadIds, requireStars, contents, account, silently, additionalText)
|> castError(ShareControllerError.self) |> castError(ShareControllerError.self)
} }
case let .done(contents): case let .done(contents):
return sentItems(peerIds, threadIds, contents, account, silently, additionalText) return sentItems(peerIds, threadIds, requireStars, contents, account, silently, additionalText)
|> castError(ShareControllerError.self) |> castError(ShareControllerError.self)
} }
} }

View File

@ -124,9 +124,9 @@ private final class SheetContent: CombinedComponent {
minAmount = StarsAmount(value: 1, nanos: 0) minAmount = StarsAmount(value: 1, nanos: 0)
maxAmount = configuration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) } maxAmount = configuration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
var usdRate = 0.012
if let usdWithdrawRate = configuration.usdWithdrawRate, let amount = state.amount, amount > StarsAmount.zero { if let usdWithdrawRate = configuration.usdWithdrawRate, let amount = state.amount, amount > StarsAmount.zero {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0 let usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
amountLabel = "\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))" amountLabel = "\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))"
} else { } else {
amountLabel = nil amountLabel = nil

View File

@ -98,6 +98,7 @@ swift_library(
"//submodules/TelegramUI/Components/SliderContextItem", "//submodules/TelegramUI/Components/SliderContextItem",
"//submodules/TelegramUI/Components/InteractiveTextComponent", "//submodules/TelegramUI/Components/InteractiveTextComponent",
"//submodules/TelegramUI/Components/SaveProgressScreen", "//submodules/TelegramUI/Components/SaveProgressScreen",
"//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -219,7 +219,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories, preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil, boostsToUnrestrict: nil,
appliedBoosts: nil appliedBoosts: nil,
sendPaidMessageStars: cachedUserData.sendPaidMessageStars
) )
} else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData { } else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData {
additionalPeerData = StoryContentContextState.AdditionalPeerData( additionalPeerData = StoryContentContextState.AdditionalPeerData(
@ -230,7 +231,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories, preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: cachedChannelData.boostsToUnrestrict, boostsToUnrestrict: cachedChannelData.boostsToUnrestrict,
appliedBoosts: cachedChannelData.appliedBoosts appliedBoosts: cachedChannelData.appliedBoosts,
sendPaidMessageStars: cachedChannelData.sendPaidMessageStars
) )
} else { } else {
additionalPeerData = StoryContentContextState.AdditionalPeerData( additionalPeerData = StoryContentContextState.AdditionalPeerData(
@ -241,7 +243,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories, preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil, boostsToUnrestrict: nil,
appliedBoosts: nil appliedBoosts: nil,
sendPaidMessageStars: nil
) )
} }
} else { } else {
@ -253,7 +256,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories, preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil, boostsToUnrestrict: nil,
appliedBoosts: nil appliedBoosts: nil,
sendPaidMessageStars: nil
) )
} }
let state = stateView.value?.get(Stories.PeerState.self) let state = stateView.value?.get(Stories.PeerState.self)
@ -1182,7 +1186,8 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
TelegramEngine.EngineData.Item.NotificationSettings.Global(), TelegramEngine.EngineData.Item.NotificationSettings.Global(),
TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(id: storyId.peerId), TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(id: storyId.peerId),
TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict(id: storyId.peerId), TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict(id: storyId.peerId),
TelegramEngine.EngineData.Item.Peer.AppliedBoosts(id: storyId.peerId) TelegramEngine.EngineData.Item.Peer.AppliedBoosts(id: storyId.peerId),
TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: storyId.peerId)
), ),
item |> mapToSignal { item -> Signal<(Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?]), NoError> in item |> mapToSignal { item -> Signal<(Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?]), NoError> in
return context.account.postbox.transaction { transaction -> (Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?]) in return context.account.postbox.transaction { transaction -> (Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?]) in
@ -1253,7 +1258,7 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
return return
} }
let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings, isPremiumRequiredForMessaging, boostsToUnrestrict, appliedBoosts) = data let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings, isPremiumRequiredForMessaging, boostsToUnrestrict, appliedBoosts, sendPaidMessageStars) = data
let (item, peers, allEntityFiles, forwardInfoStories) = itemAndPeers let (item, peers, allEntityFiles, forwardInfoStories) = itemAndPeers
guard let peer else { guard let peer else {
@ -1270,7 +1275,8 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories, preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: boostsToUnrestrict, boostsToUnrestrict: boostsToUnrestrict,
appliedBoosts: appliedBoosts appliedBoosts: appliedBoosts,
sendPaidMessageStars: sendPaidMessageStars
) )
for (storyId, story) in forwardInfoStories { for (storyId, story) in forwardInfoStories {
@ -1436,9 +1442,11 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
TelegramEngine.EngineData.Item.NotificationSettings.Global.Result, TelegramEngine.EngineData.Item.NotificationSettings.Global.Result,
TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging.Result, TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging.Result,
TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict.Result, TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict.Result,
TelegramEngine.EngineData.Item.Peer.AppliedBoosts.Result) TelegramEngine.EngineData.Item.Peer.AppliedBoosts.Result,
TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars.Result
)
init(data: (TelegramEngine.EngineData.Item.Peer.Peer.Result, TelegramEngine.EngineData.Item.Peer.Presence.Result, TelegramEngine.EngineData.Item.Peer.AreVoiceMessagesAvailable.Result, TelegramEngine.EngineData.Item.Peer.CanViewStats.Result, TelegramEngine.EngineData.Item.Peer.NotificationSettings.Result, TelegramEngine.EngineData.Item.NotificationSettings.Global.Result, TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging.Result, TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict.Result, TelegramEngine.EngineData.Item.Peer.AppliedBoosts.Result)) { init(data: (TelegramEngine.EngineData.Item.Peer.Peer.Result, TelegramEngine.EngineData.Item.Peer.Presence.Result, TelegramEngine.EngineData.Item.Peer.AreVoiceMessagesAvailable.Result, TelegramEngine.EngineData.Item.Peer.CanViewStats.Result, TelegramEngine.EngineData.Item.Peer.NotificationSettings.Result, TelegramEngine.EngineData.Item.NotificationSettings.Global.Result, TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging.Result, TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict.Result, TelegramEngine.EngineData.Item.Peer.AppliedBoosts.Result, TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars.Result)) {
self.data = data self.data = data
} }
} }
@ -1544,7 +1552,8 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
TelegramEngine.EngineData.Item.NotificationSettings.Global(), TelegramEngine.EngineData.Item.NotificationSettings.Global(),
TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(id: peerId), TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(id: peerId),
TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict(id: peerId), TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict(id: peerId),
TelegramEngine.EngineData.Item.Peer.AppliedBoosts(id: peerId) TelegramEngine.EngineData.Item.Peer.AppliedBoosts(id: peerId),
TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: peerId)
) |> map { PeerData(data: $0) }) ) |> map { PeerData(data: $0) })
self.currentPeerData = currentPeerData self.currentPeerData = currentPeerData
@ -1563,7 +1572,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
self.listState = state self.listState = state
let stateValue: StoryContentContextState let stateValue: StoryContentContextState
if let focusedIndex, let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings, isPremiumRequiredForMessaging, boostsToUnrestrict, appliedBoosts) = data?.data, let peer { if let focusedIndex, let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings, isPremiumRequiredForMessaging, boostsToUnrestrict, appliedBoosts, sendPaidMessageStars) = data?.data, let peer {
let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: []) let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: [])
let additionalPeerData = StoryContentContextState.AdditionalPeerData( let additionalPeerData = StoryContentContextState.AdditionalPeerData(
isMuted: isMuted, isMuted: isMuted,
@ -1573,7 +1582,8 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories, preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: boostsToUnrestrict, boostsToUnrestrict: boostsToUnrestrict,
appliedBoosts: appliedBoosts appliedBoosts: appliedBoosts,
sendPaidMessageStars: sendPaidMessageStars
) )
let item = state.items[focusedIndex] let item = state.items[focusedIndex]
@ -2462,7 +2472,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories, preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil, boostsToUnrestrict: nil,
appliedBoosts: nil appliedBoosts: nil,
sendPaidMessageStars: cachedUserData.sendPaidMessageStars
) )
} else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData { } else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData {
additionalPeerData = StoryContentContextState.AdditionalPeerData( additionalPeerData = StoryContentContextState.AdditionalPeerData(
@ -2473,7 +2484,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories, preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: cachedChannelData.boostsToUnrestrict, boostsToUnrestrict: cachedChannelData.boostsToUnrestrict,
appliedBoosts: cachedChannelData.appliedBoosts appliedBoosts: cachedChannelData.appliedBoosts,
sendPaidMessageStars: cachedChannelData.sendPaidMessageStars
) )
} else { } else {
additionalPeerData = StoryContentContextState.AdditionalPeerData( additionalPeerData = StoryContentContextState.AdditionalPeerData(
@ -2484,7 +2496,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories, preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil, boostsToUnrestrict: nil,
appliedBoosts: nil appliedBoosts: nil,
sendPaidMessageStars: nil
) )
} }
} }
@ -2497,7 +2510,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories, preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil, boostsToUnrestrict: nil,
appliedBoosts: nil appliedBoosts: nil,
sendPaidMessageStars: nil
) )
} }

View File

@ -161,6 +161,7 @@ public final class StoryContentContextState {
public let preferHighQualityStories: Bool public let preferHighQualityStories: Bool
public let boostsToUnrestrict: Int32? public let boostsToUnrestrict: Int32?
public let appliedBoosts: Int32? public let appliedBoosts: Int32?
public let sendPaidMessageStars: StarsAmount?
public init( public init(
isMuted: Bool, isMuted: Bool,
@ -170,7 +171,8 @@ public final class StoryContentContextState {
isPremiumRequiredForMessaging: Bool, isPremiumRequiredForMessaging: Bool,
preferHighQualityStories: Bool, preferHighQualityStories: Bool,
boostsToUnrestrict: Int32?, boostsToUnrestrict: Int32?,
appliedBoosts: Int32? appliedBoosts: Int32?,
sendPaidMessageStars: StarsAmount?
) { ) {
self.isMuted = isMuted self.isMuted = isMuted
self.areVoiceMessagesAvailable = areVoiceMessagesAvailable self.areVoiceMessagesAvailable = areVoiceMessagesAvailable
@ -180,6 +182,7 @@ public final class StoryContentContextState {
self.preferHighQualityStories = preferHighQualityStories self.preferHighQualityStories = preferHighQualityStories
self.boostsToUnrestrict = boostsToUnrestrict self.boostsToUnrestrict = boostsToUnrestrict
self.appliedBoosts = appliedBoosts self.appliedBoosts = appliedBoosts
self.sendPaidMessageStars = sendPaidMessageStars
} }
public static func == (lhs: StoryContentContextState.AdditionalPeerData, rhs: StoryContentContextState.AdditionalPeerData) -> Bool { public static func == (lhs: StoryContentContextState.AdditionalPeerData, rhs: StoryContentContextState.AdditionalPeerData) -> Bool {
@ -207,6 +210,9 @@ public final class StoryContentContextState {
if lhs.appliedBoosts != rhs.appliedBoosts { if lhs.appliedBoosts != rhs.appliedBoosts {
return false return false
} }
if lhs.sendPaidMessageStars != rhs.sendPaidMessageStars {
return false
}
return true return true
} }
} }

View File

@ -2822,9 +2822,14 @@ public final class StoryItemSetContainerComponent: Component {
} }
inputPlaceholder = .counter(items) inputPlaceholder = .counter(items)
} else {
if let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars {
let dateTimeFormat = component.context.sharedContext.currentPresentationData.with { $0 }.dateTimeFormat
inputPlaceholder = .plain(component.strings.Chat_InputTextPaidMessagePlaceholder(" # \(presentationStringsFormattedNumber(Int32(sendPaidMessageStars.value), dateTimeFormat.groupingSeparator))").string)
} else { } else {
inputPlaceholder = .plain(isGroup ? component.strings.Story_InputPlaceholderReplyInGroup : component.strings.Story_InputPlaceholderReplyPrivately) inputPlaceholder = .plain(isGroup ? component.strings.Story_InputPlaceholderReplyInGroup : component.strings.Story_InputPlaceholderReplyPrivately)
} }
}
let startTime22 = CFAbsoluteTimeGetCurrent() let startTime22 = CFAbsoluteTimeGetCurrent()
@ -2867,6 +2872,7 @@ public final class StoryItemSetContainerComponent: Component {
strings: component.strings, strings: component.strings,
style: .story, style: .story,
placeholder: inputPlaceholder, placeholder: inputPlaceholder,
sendPaidMessageStars: component.slice.additionalPeerData.sendPaidMessageStars,
maxLength: 4096, maxLength: 4096,
queryTypes: [.mention, .hashtag, .emoji], queryTypes: [.mention, .hashtag, .emoji],
alwaysDarkWhenHasText: component.metrics.widthClass == .regular, alwaysDarkWhenHasText: component.metrics.widthClass == .regular,
@ -4648,6 +4654,10 @@ public final class StoryItemSetContainerComponent: Component {
break break
} }
if let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars {
messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
}
let message: EnqueueMessage = .message( let message: EnqueueMessage = .message(
text: text, text: text,
attributes: messageAttributes, attributes: messageAttributes,
@ -4693,7 +4703,6 @@ public final class StoryItemSetContainerComponent: Component {
}) })
} }
} }
if self.displayLikeReactions { if self.displayLikeReactions {
if component.slice.item.storyItem.myReaction == updateReaction.reaction { if component.slice.item.storyItem.myReaction == updateReaction.reaction {
action() action()
@ -4704,8 +4713,10 @@ public final class StoryItemSetContainerComponent: Component {
} }
} else { } else {
self.sendMessageContext.performWithPossibleStealthModeConfirmation(view: self, action: { self.sendMessageContext.performWithPossibleStealthModeConfirmation(view: self, action: {
self.sendMessageContext.presentPaidMessageAlertIfNeeded(view: self, completion: {
action() action()
}) })
})
} }
} }

View File

@ -50,6 +50,7 @@ import LocationUI
import ReactionSelectionNode import ReactionSelectionNode
import StoryQualityUpgradeSheetScreen import StoryQualityUpgradeSheetScreen
import AudioWaveform import AudioWaveform
import ChatMessagePaymentAlertController
private var ObjCKey_DeinitWatcher: Int? private var ObjCKey_DeinitWatcher: Int?
@ -491,6 +492,30 @@ final class StoryItemSetContainerSendMessage {
view.updateIsProgressPaused() view.updateIsProgressPaused()
} }
func presentPaidMessageAlertIfNeeded(view: StoryItemSetContainerComponent.View, completion: @escaping () -> Void) {
guard let component = view.component, let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars else {
completion()
return
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
let controller = chatMessagePaymentAlertController(
context: component.context,
presentationData: presentationData,
updatedPresentationData: nil,
peers: [component.slice.effectivePeer],
count: 1,
amount: sendPaidMessageStars,
totalAmount: nil,
hasCheck: false,
navigationController: component.controller()?.navigationController as? NavigationController,
completion: { _ in
completion()
}
)
component.controller()?.present(controller, in: .window(.root))
}
func performWithPossibleStealthModeConfirmation(view: StoryItemSetContainerComponent.View, action: @escaping () -> Void) { func performWithPossibleStealthModeConfirmation(view: StoryItemSetContainerComponent.View, action: @escaping () -> Void) {
guard let component = view.component, component.stealthModeTimeout != nil else { guard let component = view.component, component.stealthModeTimeout != nil else {
action() action()
@ -512,7 +537,6 @@ final class StoryItemSetContainerSendMessage {
let timestamp = Int32(Date().timeIntervalSince1970) let timestamp = Int32(Date().timeIntervalSince1970)
if noticeCount < 1, let activeUntilTimestamp = config.stealthModeState.actualizedNow().activeUntilTimestamp, activeUntilTimestamp > timestamp { if noticeCount < 1, let activeUntilTimestamp = config.stealthModeState.actualizedNow().activeUntilTimestamp, activeUntilTimestamp > timestamp {
let theme = component.theme let theme = component.theme
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) })
@ -575,12 +599,21 @@ final class StoryItemSetContainerSendMessage {
let controller = component.controller() as? StoryContainerScreen let controller = component.controller() as? StoryContainerScreen
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in
guard let self, let view else {
return
}
if let recordedAudioPreview = self.recordedAudioPreview, case let .audio(audio) = recordedAudioPreview { if let recordedAudioPreview = self.recordedAudioPreview, case let .audio(audio) = recordedAudioPreview {
self.recordedAudioPreview = nil self.recordedAudioPreview = nil
let waveformBuffer = audio.waveform.makeBitstream() let waveformBuffer = audio.waveform.makeBitstream()
let messages: [EnqueueMessage] = [.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: audio.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(audio.fileSize), attributes: [.Audio(isVoice: true, duration: Int(audio.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])] var messageAttributes: [MessageAttribute] = []
if let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars {
messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
}
let messages: [EnqueueMessage] = [.message(text: "", attributes: messageAttributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: audio.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(audio.fileSize), attributes: [.Audio(isVoice: true, duration: Int(audio.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]
let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: messages).start() let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: messages).start()
@ -601,7 +634,8 @@ final class StoryItemSetContainerSendMessage {
storyId: focusedStoryId, storyId: focusedStoryId,
content: .text(text.string, entities), content: .text(text.string, entities),
silentPosting: silentPosting, silentPosting: silentPosting,
scheduleTime: scheduleTime scheduleTime: scheduleTime,
sendPaidMessageStars: component.slice.additionalPeerData.sendPaidMessageStars
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in ) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
Queue.mainQueue().after(0.3) { Queue.mainQueue().after(0.3) {
if let self, let view { if let self, let view {
@ -623,6 +657,7 @@ final class StoryItemSetContainerSendMessage {
} }
} }
}) })
})
} }
func performSendStickerAction(view: StoryItemSetContainerComponent.View, fileReference: FileMediaReference) { func performSendStickerAction(view: StoryItemSetContainerComponent.View, fileReference: FileMediaReference) {
@ -660,11 +695,16 @@ final class StoryItemSetContainerSendMessage {
}) })
} }
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in
guard let self, let view else {
return
}
let _ = (component.context.engine.messages.enqueueOutgoingMessage( let _ = (component.context.engine.messages.enqueueOutgoingMessage(
to: peerId, to: peerId,
replyTo: nil, replyTo: nil,
storyId: focusedStoryId, storyId: focusedStoryId,
content: .file(fileReference) content: .file(fileReference),
sendPaidMessageStars: component.slice.additionalPeerData.sendPaidMessageStars
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in ) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
Queue.mainQueue().after(0.3) { Queue.mainQueue().after(0.3) {
if let self, let view { if let self, let view {
@ -681,6 +721,7 @@ final class StoryItemSetContainerSendMessage {
} }
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
}) })
})
} }
func performSendContextResultAction(view: StoryItemSetContainerComponent.View, results: ChatContextResultCollection, result: ChatContextResult) { func performSendContextResultAction(view: StoryItemSetContainerComponent.View, results: ChatContextResultCollection, result: ChatContextResult) {
@ -714,11 +755,16 @@ final class StoryItemSetContainerSendMessage {
}) })
} }
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in
guard let self, let view else {
return
}
let _ = (component.context.engine.messages.enqueueOutgoingMessage( let _ = (component.context.engine.messages.enqueueOutgoingMessage(
to: peerId, to: peerId,
replyTo: nil, replyTo: nil,
storyId: focusedStoryId, storyId: focusedStoryId,
content: .contextResult(results, result) content: .contextResult(results, result),
sendPaidMessageStars: component.slice.additionalPeerData.sendPaidMessageStars
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in ) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
Queue.mainQueue().after(0.3) { Queue.mainQueue().after(0.3) {
if let self, let view { if let self, let view {
@ -734,18 +780,24 @@ final class StoryItemSetContainerSendMessage {
view.state?.updated(transition: .spring(duration: 0.3)) view.state?.updated(transition: .spring(duration: 0.3))
} }
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
})
} }
func enqueueGifData(view: StoryItemSetContainerComponent.View, data: Data) { func enqueueGifData(view: StoryItemSetContainerComponent.View, data: Data) {
guard let component = view.component else { guard let component = view.component else {
return return
} }
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in
guard let self else {
return
}
let peer = component.slice.effectivePeer let peer = component.slice.effectivePeer
let _ = (legacyEnqueueGifMessage(account: component.context.account, data: data) |> deliverOnMainQueue).start(next: { [weak self, weak view] message in let _ = (legacyEnqueueGifMessage(account: component.context.account, data: data) |> deliverOnMainQueue).start(next: { [weak self, weak view] message in
if let self, let view { if let self, let view {
self.sendMessages(view: view, peer: peer, messages: [message]) self.sendMessages(view: view, peer: peer, messages: [message])
} }
}) })
})
} }
func enqueueStickerImage(view: StoryItemSetContainerComponent.View, image: UIImage, isMemoji: Bool) { func enqueueStickerImage(view: StoryItemSetContainerComponent.View, image: UIImage, isMemoji: Bool) {
@ -794,7 +846,12 @@ final class StoryItemSetContainerSendMessage {
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: Int64(data.count), attributes: fileAttributes, alternativeRepresentations: []) let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: Int64(data.count), attributes: fileAttributes, alternativeRepresentations: [])
let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in
guard let self else {
return
}
self.sendMessages(view: view, peer: peer, messages: [message], silentPosting: false) self.sendMessages(view: view, peer: peer, messages: [message], silentPosting: false)
})
} }
}) })
} }
@ -843,11 +900,16 @@ final class StoryItemSetContainerSendMessage {
self.videoRecorder.set(.single(nil)) self.videoRecorder.set(.single(nil))
self.performWithPossibleStealthModeConfirmation(view: view, action: { [weak self, weak view] in self.performWithPossibleStealthModeConfirmation(view: view, action: { [weak self, weak view] in
guard let self, let view else {
return
}
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in
guard let self, let view else { guard let self, let view else {
return return
} }
self.sendMessages(view: view, peer: peer, messages: [updatedMessage]) self.sendMessages(view: view, peer: peer, messages: [updatedMessage])
}) })
})
}, displaySlowmodeTooltip: { [weak self] view, rect in }, displaySlowmodeTooltip: { [weak self] view, rect in
//self?.interfaceInteraction?.displaySlowmodeTooltip(view, rect) //self?.interfaceInteraction?.displaySlowmodeTooltip(view, rect)
let _ = self let _ = self
@ -893,6 +955,10 @@ final class StoryItemSetContainerSendMessage {
let waveformBuffer: Data? = data.waveform let waveformBuffer: Data? = data.waveform
self.performWithPossibleStealthModeConfirmation(view: view, action: { [weak self, weak view] in self.performWithPossibleStealthModeConfirmation(view: view, action: { [weak self, weak view] in
guard let self, let view else {
return
}
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in
guard let self, let view else { guard let self, let view else {
return return
} }
@ -900,6 +966,7 @@ final class StoryItemSetContainerSendMessage {
HapticFeedback().tap() HapticFeedback().tap()
}) })
})
} }
}) })
} else if let videoRecorderValue = self.videoRecorderValue { } else if let videoRecorderValue = self.videoRecorderValue {
@ -1593,7 +1660,11 @@ final class StoryItemSetContainerSendMessage {
guard let view, let component = view.component else { guard let view, let component = view.component else {
return return
} }
let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: mediaReference, threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) var messageAttributes: [MessageAttribute] = []
if let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars {
messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
}
let message: EnqueueMessage = .message(text: "", attributes: messageAttributes, inlineStickers: [:], mediaReference: mediaReference, threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
let _ = (enqueueMessages(account: component.context.account, peerId: peer.id, messages: [message.withUpdatedReplyToMessageId(nil)]) let _ = (enqueueMessages(account: component.context.account, peerId: peer.id, messages: [message.withUpdatedReplyToMessageId(nil)])
|> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
if let self, let view { if let self, let view {
@ -1636,8 +1707,13 @@ final class StoryItemSetContainerSendMessage {
return return
} }
let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: location), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: location), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in
guard let self else {
return
}
self.sendMessages(view: view, peer: peer, messages: [message]) self.sendMessages(view: view, peer: peer, messages: [message])
}) })
})
completion(controller, controller.mediaPickerContext) completion(controller, controller.mediaPickerContext)
let _ = currentLocationController.swap(controller) let _ = currentLocationController.swap(controller)
@ -1705,7 +1781,12 @@ final class StoryItemSetContainerSendMessage {
} }
} }
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in
guard let self else {
return
}
self.sendMessages(view: view, peer: peer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime) self.sendMessages(view: view, peer: peer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)
})
} else if let peer = peers.first { } else if let peer = peers.first {
let dataSignal: Signal<(EnginePeer?, DeviceContactExtendedData?), NoError> let dataSignal: Signal<(EnginePeer?, DeviceContactExtendedData?), NoError>
switch peer { switch peer {
@ -1760,7 +1841,12 @@ final class StoryItemSetContainerSendMessage {
} }
enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in
guard let self else {
return
}
self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime) self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)
})
} else { } else {
let contactController = component.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: component.context), environment: ShareControllerAppEnvironment(sharedContext: component.context.sharedContext), subject: .filter(peer: peerAndContactData.0?._asPeer(), contactId: nil, contactData: contactData, completion: { [weak self, weak view] peer, contactData in let contactController = component.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: component.context), environment: ShareControllerAppEnvironment(sharedContext: component.context.sharedContext), subject: .filter(peer: peerAndContactData.0?._asPeer(), contactId: nil, contactData: contactData, completion: { [weak self, weak view] peer, contactData in
guard let self, let view else { guard let self, let view else {
@ -1779,7 +1865,12 @@ final class StoryItemSetContainerSendMessage {
} }
enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in
guard let self else {
return
}
self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime) self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)
})
} }
}), completed: nil, cancelled: nil) }), completed: nil, cancelled: nil)
component.controller()?.push(contactController) component.controller()?.push(contactController)
@ -2187,7 +2278,12 @@ final class StoryItemSetContainerSendMessage {
} }
if !messages.isEmpty { if !messages.isEmpty {
strongSelf.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.sendMessages(view: view, peer: peer, messages: messages) strongSelf.sendMessages(view: view, peer: peer, messages: messages)
})
} }
} }
})) }))
@ -2248,7 +2344,12 @@ final class StoryItemSetContainerSendMessage {
if !inputText.string.isEmpty { if !inputText.string.isEmpty {
self.clearInputText(view: view) self.clearInputText(view: view)
} }
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in
guard let self else {
return
}
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
})
} }
) )
} }
@ -2268,7 +2369,19 @@ final class StoryItemSetContainerSendMessage {
guard let self, let view, let component = view.component else { guard let self, let view, let component = view.component else {
return return
} }
if component.context.engine.messages.enqueueOutgoingMessageWithChatContextResult(to: peer.id, threadId: nil, botId: results.botId, result: result, replyToMessageId: replyMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil) }, replyToStoryId: storyId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime) { if component.context.engine.messages.enqueueOutgoingMessageWithChatContextResult(
to: peer.id,
threadId: nil,
botId: results.botId,
result: result,
replyToMessageId: replyMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil) },
replyToStoryId: storyId,
hideVia: hideVia,
silentPosting: silentPosting,
scheduleTime: scheduleTime,
sendPaidMessageStars: component.slice.additionalPeerData.sendPaidMessageStars,
postpone: false
) {
} }
if let attachmentController = self.attachmentController { if let attachmentController = self.attachmentController {
@ -2413,6 +2526,10 @@ final class StoryItemSetContainerSendMessage {
let storeCapturedMedia = peer.id.namespace != Namespaces.Peer.SecretChat let storeCapturedMedia = peer.id.namespace != Namespaces.Peer.SecretChat
presentedLegacyCamera(context: component.context, peer: peer._asPeer(), chatLocation: .peer(id: peer.id), cameraView: cameraView, menuController: nil, parentController: parentController, attachmentController: self.attachmentController, editingMedia: false, saveCapturedPhotos: storeCapturedMedia, mediaGrouping: true, initialCaption: inputText, hasSchedule: peer.id.namespace != Namespaces.Peer.SecretChat, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self, weak view] signals, silentPosting, scheduleTime, parameters in presentedLegacyCamera(context: component.context, peer: peer._asPeer(), chatLocation: .peer(id: peer.id), cameraView: cameraView, menuController: nil, parentController: parentController, attachmentController: self.attachmentController, editingMedia: false, saveCapturedPhotos: storeCapturedMedia, mediaGrouping: true, initialCaption: inputText, hasSchedule: peer.id.namespace != Namespaces.Peer.SecretChat, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self, weak view] signals, silentPosting, scheduleTime, parameters in
guard let self, let view else {
return
}
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in
guard let self, let view else { guard let self, let view else {
return return
} }
@ -2420,6 +2537,7 @@ final class StoryItemSetContainerSendMessage {
if !inputText.string.isEmpty { if !inputText.string.isEmpty {
self.clearInputText(view: view) self.clearInputText(view: view)
} }
})
}, recognizedQRCode: { _ in }, recognizedQRCode: { _ in
}, presentSchedulePicker: { [weak self, weak view] _, done in }, presentSchedulePicker: { [weak self, weak view] _, done in
guard let self, let view else { guard let self, let view else {
@ -2545,6 +2663,10 @@ final class StoryItemSetContainerSendMessage {
attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime)) attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime))
} }
} }
var messageAttributes: [MessageAttribute] = []
if let component = view.component, let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars {
messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
}
return attributes return attributes
} }
} }

View File

@ -2604,8 +2604,12 @@ extension ChatControllerImpl {
strongSelf.interfaceInteraction?.displaySlowmodeTooltip(node.view, rect) strongSelf.interfaceInteraction?.displaySlowmodeTooltip(node.view, rect)
return false return false
} }
strongSelf.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
strongSelf.enqueueChatContextResult(results, result) guard let strongSelf = self else {
return
}
strongSelf.enqueueChatContextResult(results, result, postpone: postpone)
})
return true return true
}, sendBotCommand: { [weak self] botPeer, command in }, sendBotCommand: { [weak self] botPeer, command in
if let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) { if let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) {

View File

@ -250,10 +250,8 @@ extension ChatControllerImpl {
} }
var sendImmediately = false var sendImmediately = false
if let _ = self.presentationInterfaceState.sendPaidMessageStars { if let _ = self.presentationInterfaceState.sendPaidMessageStars, case .send = action {
if case .send = action {
updatedAction = .preview updatedAction = .preview
}
sendImmediately = true sendImmediately = true
} }

View File

@ -23,10 +23,10 @@ extension ChatControllerImpl {
if let sendPaidMessageStars = self.presentationInterfaceState.sendPaidMessageStars { if let sendPaidMessageStars = self.presentationInterfaceState.sendPaidMessageStars {
let _ = (ApplicationSpecificNotice.dismissedPaidMessageWarningNamespace(accountManager: self.context.sharedContext.accountManager, peerId: peer.id) let _ = (ApplicationSpecificNotice.dismissedPaidMessageWarningNamespace(accountManager: self.context.sharedContext.accountManager, peerId: peer.id)
|> deliverOnMainQueue).start(next: { [weak self] dismissedAmount in |> deliverOnMainQueue).start(next: { [weak self] dismissedAmount in
guard let self else { guard let self, let starsContext = self.context.starsContext else {
return return
} }
if let dismissedAmount, dismissedAmount == sendPaidMessageStars.value { if let dismissedAmount, dismissedAmount == sendPaidMessageStars.value, let currentState = starsContext.currentState, currentState.balance > sendPaidMessageStars {
completion(true) completion(true)
self.displayPaidMessageUndo(count: count, amount: sendPaidMessageStars) self.displayPaidMessageUndo(count: count, amount: sendPaidMessageStars)
} else { } else {
@ -37,14 +37,14 @@ extension ChatControllerImpl {
let controller = chatMessagePaymentAlertController( let controller = chatMessagePaymentAlertController(
context: self.context, context: self.context,
presentationData: presentationData, presentationData: presentationData,
updatedPresentationData: nil,//self.updatedPresentationData, updatedPresentationData: nil,
peers: [peer], peers: [peer],
count: count, count: count,
amount: sendPaidMessageStars, amount: sendPaidMessageStars,
totalAmount: nil, totalAmount: nil,
navigationController: self.navigationController as? NavigationController, navigationController: self.navigationController as? NavigationController,
completion: { [weak self] dontAskAgain in completion: { [weak self] dontAskAgain in
guard let self, let starsContext = self.context.starsContext else { guard let self else {
return return
} }

View File

@ -9515,7 +9515,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})) }))
} }
func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false, resetTextInputState: Bool = true) { func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false, resetTextInputState: Bool = true, postpone: Bool = false) {
if !canSendMessagesToChat(self.presentationInterfaceState) { if !canSendMessagesToChat(self.presentationInterfaceState) {
return return
} }
@ -9534,7 +9534,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
let replyMessageSubject = self.presentationInterfaceState.interfaceState.replyMessageSubject let replyMessageSubject = self.presentationInterfaceState.interfaceState.replyMessageSubject
if self.context.engine.messages.enqueueOutgoingMessageWithChatContextResult(to: peerId, threadId: self.chatLocation.threadId, botId: results.botId, result: result, replyToMessageId: replyMessageSubject?.subjectModel, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime) {
let sendPaidMessageStars = self.presentationInterfaceState.sendPaidMessageStars
if self.context.engine.messages.enqueueOutgoingMessageWithChatContextResult(to: peerId, threadId: self.chatLocation.threadId, botId: results.botId, result: result, replyToMessageId: replyMessageSubject?.subjectModel, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, sendPaidMessageStars: sendPaidMessageStars, postpone: postpone) {
self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in
if let strongSelf = self { if let strongSelf = self {
strongSelf.chatDisplayNode.collapseInput() strongSelf.chatDisplayNode.collapseInput()

View File

@ -13,6 +13,7 @@ import ChatInterfaceState
import PremiumUI import PremiumUI
import ReactionSelectionNode import ReactionSelectionNode
import TopMessageReactions import TopMessageReactions
import ChatMessagePaymentAlertController
extension ChatControllerImpl { extension ChatControllerImpl {
func forwardMessages(messageIds: [MessageId], options: ChatInterfaceForwardOptionsState? = nil, resetCurrent: Bool = false) { func forwardMessages(messageIds: [MessageId], options: ChatInterfaceForwardOptionsState? = nil, resetCurrent: Bool = false) {
@ -94,9 +95,33 @@ extension ChatControllerImpl {
} }
} }
controller.multiplePeersSelected = { [weak self, weak controller] peers, peerMap, messageText, mode, forwardOptions, _ in controller.multiplePeersSelected = { [weak self, weak controller] peers, peerMap, messageText, mode, forwardOptions, _ in
let peerIds = peers.map { $0.id }
let _ = (context.engine.data.get(
EngineDataMap(
peerIds.map(TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars.init(id:))
)
)
|> deliverOnMainQueue).start(next: { [weak self, weak controller] sendPaidMessageStars in
guard let strongSelf = self else {
return
}
var count: Int32 = Int32(messages.count)
if messageText.string.count > 0 {
count += 1
}
var totalAmount: StarsAmount = .zero
for peer in peers {
if let maybeAmount = sendPaidMessageStars[peer.id], let amount = maybeAmount {
totalAmount = totalAmount + amount
}
}
let proceed = { [weak self, weak controller] in
guard let strongSelf = self, let strongController = controller else { guard let strongSelf = self, let strongController = controller else {
return return
} }
strongController.dismiss() strongController.dismiss()
var result: [EnqueueMessage] = [] var result: [EnqueueMessage] = []
@ -166,6 +191,16 @@ extension ChatControllerImpl {
} }
} }
if let maybeAmount = sendPaidMessageStars[peer.id], let amount = maybeAmount {
peerMessages = peerMessages.map { message -> EnqueueMessage in
return message.withUpdatedAttributes { attributes in
var attributes = attributes
attributes.append(PaidStarsMessageAttribute(stars: amount, postponeSending: false))
return attributes
}
}
}
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peer.id, messages: peerMessages) let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peer.id, messages: peerMessages)
|> deliverOnMainQueue).startStandalone(next: { messageIds in |> deliverOnMainQueue).startStandalone(next: { messageIds in
if let strongSelf = self { if let strongSelf = self {
@ -279,6 +314,28 @@ extension ChatControllerImpl {
commit(transformedMessages) commit(transformedMessages)
} }
} }
if totalAmount.value > 0 {
let controller = chatMessagePaymentAlertController(
context: nil,
presentationData: strongSelf.presentationData,
updatedPresentationData: nil,
peers: peers,
count: count,
amount: totalAmount,
totalAmount: totalAmount,
hasCheck: false,
navigationController: strongSelf.navigationController as? NavigationController,
completion: { _ in
proceed()
}
)
strongSelf.present(controller, in: .window(.root))
} else {
proceed()
}
})
}
controller.peerSelected = { [weak self, weak controller] peer, threadId in controller.peerSelected = { [weak self, weak controller] peer, threadId in
guard let strongSelf = self, let strongController = controller else { guard let strongSelf = self, let strongController = controller else {
return return

View File

@ -1927,8 +1927,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
} }
} else { } else {
if let sendPaidMessageStars = interfaceState.sendPaidMessageStars { if let sendPaidMessageStars = interfaceState.sendPaidMessageStars {
//TODO:localize placeholder = interfaceState.strings.Chat_InputTextPaidMessagePlaceholder(" # \(presentationStringsFormattedNumber(Int32(sendPaidMessageStars.value), interfaceState.dateTimeFormat.groupingSeparator))").string
placeholder = "Message for # \(presentationStringsFormattedNumber(Int32(sendPaidMessageStars.value), interfaceState.dateTimeFormat.groupingSeparator))"
placeholderHasStar = true placeholderHasStar = true
} else { } else {
placeholder = interfaceState.strings.Conversation_InputTextPlaceholder placeholder = interfaceState.strings.Conversation_InputTextPlaceholder