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.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.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.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.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.RemoveFee" = "Remove Fee";
@ -13874,3 +13874,5 @@ Sorry for the inconvenience.";
"Gift.Send.PayWithStars.Info" = "Your balance is **%@**. [Get More Stars >]()";
"Chat.PanelCustomStatusShortInfo" = "%@ is a mark for [Premium subscribers >]()";
"Chat.InputTextPaidMessagePlaceholder" = "Message for %@";

View File

@ -1353,20 +1353,43 @@ public struct StickersSearchConfiguration {
public struct 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 usdWithdrawRate: Int64?
public let maxFee: 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.usdWithdrawRate = usdWithdrawRate
self.paidMessageMaxAmount = paidMessageMaxAmount
self.paidMessageCommissionPermille = paidMessageCommissionPermille
}
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 {
return StarsSubscriptionConfiguration(maxFee: Int64(value), usdWithdrawRate: Int64(usdRate))
if let data = appConfiguration.data {
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 {
return .defaultValue
}

View File

@ -76,5 +76,5 @@ public enum ShareControllerSubject {
case image([ImageRepresentationWithReference])
case media(AnyMediaReference, MediaParameters?)
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 let (botPeer, gameName) = self.shareData(), let addressName = botPeer.addressName, !addressName.isEmpty, !gameName.isEmpty {
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 {
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))

View File

@ -493,13 +493,10 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state:
if state.subscriptionEnabled {
var label: String = ""
if let subscriptionFee = state.subscriptionFee, subscriptionFee > StarsAmount.zero {
var usdRate = 0.012
if let usdWithdrawRate = configuration.usdWithdrawRate {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
}
let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0
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
if let _ = invite, state.subscriptionEnabled {

View File

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

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))
}
if channel.hasPermission(.banMembers) {
if cachedData.flags.contains(.paidMessagesAvailable) && channel.hasPermission(.banMembers) {
let sendPaidMessageStars = state.modifiedStarsAmount?.value ?? (cachedData.sendPaidMessageStars?.value ?? 0)
let chargeEnabled = sendPaidMessageStars > 0
entries.append(.chargeForMessages(presentationData.theme, presentationData.strings.GroupInfo_Permissions_ChargeForMessages, chargeEnabled))
@ -728,15 +728,13 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen
if chargeEnabled {
var price: String = ""
var usdRate = 0.012
if let usdWithdrawRate = configuration.usdWithdrawRate {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
}
let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0
price = "\(formatTonUsdValue(sendPaidMessageStars, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))"
entries.append(.messagePriceHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePrice))
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 priceHeader
case price(value: Int64, price: String)
case priceInfo(value: String)
case priceInfo(commission: Int32, value: String)
case exceptionsHeader
case exceptions(count: Int)
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
arguments.updateValue(.paidMessages(StarsAmount(value: value, nanos: 0)))
})
case let .priceInfo(value):
return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.Privacy_Messages_MessagePriceInfo(value).string), sectionId: self.section)
case let .priceInfo(commission, value):
return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.Privacy_Messages_MessagePriceInfo("\(commission)", value).string), sectionId: self.section)
case .exceptionsHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.Privacy_Messages_RemoveFeeHeader, sectionId: self.section)
case let .exceptions(count):
@ -184,14 +184,12 @@ private func incomingMessagePrivacyScreenEntries(presentationData: PresentationD
entries.append(.footer(value: state.updatedValue))
entries.append(.priceHeader)
var usdRate = 0.012
if let usdWithdrawRate = configuration.usdWithdrawRate {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
}
let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0
let price = "\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))"
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(.exceptions(count: state.disableFor.count))
entries.append(.exceptionsInfo)

View File

@ -676,6 +676,8 @@ public final class ShareController: ViewController {
messageCount = messages.count
} else if case let .image(images) = self.subject {
messageCount = images.count
} else if case let .fromExternal(count, _) = self.subject {
messageCount = count
}
var mediaParameters: ShareControllerSubject.MediaParameters?
@ -1253,18 +1255,18 @@ public final class ShareController: ViewController {
}
)
|> 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 requiresStars: [EnginePeer.Id: Int64] = [:]
var requiresStars: [EnginePeer.Id: StarsAmount] = [:]
for peerId in peerIds {
if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView, let peer = view.peer {
result[peerId] = EnginePeer(peer)
if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView {
if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData {
requiresStars[peerId] = cachedData.sendPaidMessageStars?.value
requiresStars[peerId] = cachedData.sendPaidMessageStars
}
} 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
}
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
var message = message
if !showNames {
@ -1296,7 +1298,7 @@ public final class ShareController: ViewController {
if silently {
message.isSilent = true
}
message.sendPaidMessageStars = sendPaidMessageStars.flatMap { StarsAmount(value: $0, nanos: 0) }
message.sendPaidMessageStars = sendPaidMessageStars
return message
}
}
@ -1839,8 +1841,8 @@ public final class ShareController: ViewController {
messages: messagesToEnqueue
))
}
case let .fromExternal(f):
return f(peerIds, topicIds, text, strongSelf.currentContext, silently)
case let .fromExternal(_, f):
return f(peerIds, topicIds, requiresStars, text, strongSelf.currentContext, silently)
|> map { state -> ShareState in
switch state {
case let .preparing(long):
@ -1907,18 +1909,18 @@ public final class ShareController: ViewController {
}
)
|> 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 requiresStars: [EnginePeer.Id: Int64] = [:]
var requiresStars: [EnginePeer.Id: StarsAmount] = [:]
for peerId in peerIds {
if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView, let peer = view.peer {
result[peerId] = EnginePeer(peer)
if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView {
if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData {
requiresStars[peerId] = cachedData.sendPaidMessageStars?.value
requiresStars[peerId] = cachedData.sendPaidMessageStars
}
} 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
}
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 message.withUpdatedAttributes({ attributes in
var attributes = attributes
@ -1949,7 +1951,7 @@ public final class ShareController: ViewController {
attributes.append(NotificationInfoMessageAttribute(flags: .muted))
}
if let sendPaidMessageStars {
attributes.append(PaidStarsMessageAttribute(stars: StarsAmount(value: sendPaidMessageStars, nanos: 0), postponeSending: false))
attributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
}
return attributes
})
@ -2321,8 +2323,8 @@ public final class ShareController: ViewController {
messagesToEnqueue = transformMessages(messagesToEnqueue, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(enqueueMessages(account: currentContext.context.account, peerId: peerId, messages: messagesToEnqueue))
}
case let .fromExternal(f):
return f(peerIds, topicIds, text, currentContext, silently)
case let .fromExternal(_, f):
return f(peerIds, topicIds, requiresStars, text, currentContext, silently)
|> map { state -> ShareState in
switch state {
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 groupingKey: Int64?
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)
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(
accountPeerId: accountPeerId,
postbox: postbox,
@ -506,7 +516,7 @@ public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Net
auxiliaryMethods: auxiliaryMethods,
peerId: peerId,
threadId: threadIds[peerId],
messages: messages
messages: peerMessages
)
|> mapToSignal { status -> Signal<Float, StandaloneSendMessagesError> in
switch status {

View File

@ -1390,7 +1390,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1314881805] = { return Api.payments.PaymentResult.parse_paymentResult($0) }
dict[-666824391] = { return Api.payments.PaymentResult.parse_paymentVerificationNeeded($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[-2069218660] = { return Api.payments.StarGiftWithdrawalUrl.parse_starGiftWithdrawalUrl($0) }
dict[-1877571094] = { return Api.payments.StarGifts.parse_starGifts($0) }

View File

@ -1,16 +1,21 @@
public extension Api.payments {
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) {
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 {
buffer.appendInt32(-1779201615)
buffer.appendInt32(-418915641)
}
serializeInt32(flags, 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 << 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(Int32(gifts.count))
for item in gifts {
@ -33,8 +38,8 @@ public extension Api.payments {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, 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)])
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), ("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() {
_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() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedStarGift.self)
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedStarGift.self)
}
var _5: String?
if Int(_1!) & Int(1 << 0) != 0 {_5 = parseString(reader) }
var _6: [Api.Chat]?
var _6: String?
if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) }
var _7: [Api.Chat]?
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() {
_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 _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
let _c6 = _6 != nil
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
let _c5 = _5 != nil
let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.payments.SavedStarGifts.savedStarGifts(flags: _1!, count: _2!, chatNotificationsEnabled: _3, gifts: _4!, nextOffset: _5, chats: _6!, users: _7!)
let _c8 = _8 != nil
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 {
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 {
static func transferStarGift(stargift: Api.InputSavedStarGift, toId: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()

View File

@ -129,6 +129,7 @@ public func standaloneSendEnqueueMessages(
struct MessageResult {
var result: PendingMessageUploadedContentResult
var media: [Media]
var attributes: [MessageAttribute]
}
let signals: [Signal<MessageResult, PendingMessageUploadError>] = messages.map { message in
@ -178,7 +179,10 @@ public func standaloneSendEnqueueMessages(
if message.isSilent {
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
return .single(nil)
}, messageMediaPreuploadManager: MessageMediaPreuploadManager(), revalidationContext: MediaReferenceRevalidationContext(), forceReupload: false, isGrouped: false, passFetchProgress: true, forceNoBigParts: false, peerId: peerId, messageId: nil, attributes: attributes, text: text, media: media)
@ -191,7 +195,7 @@ public func standaloneSendEnqueueMessages(
}
return contentResult
|> 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
var progressSum: Float = 0.0
var allResults: [(result: PendingMessageUploadedContentAndReuploadInfo, media: [Media])] = []
var allResults: [(result: PendingMessageUploadedContentAndReuploadInfo, media: [Media], attributes: [MessageAttribute])] = []
var allDone = true
for result in contentResults {
switch result.result {
@ -209,13 +213,13 @@ public func standaloneSendEnqueueMessages(
allDone = false
progressSum += value.progress
case let .content(content):
allResults.append((content, result.media))
allResults.append((content, result.media, result.attributes))
}
}
if allDone {
var sendSignals: [Signal<Never, StandaloneSendMessagesError>] = []
for (content, media) in allResults {
for (content, media, attributes) in allResults {
var text: String = ""
switch content.content {
case let .text(textValue):
@ -235,7 +239,7 @@ public func standaloneSendEnqueueMessages(
peerId: peerId,
content: content,
text: text,
attributes: [],
attributes: attributes,
media: media,
threadId: threadId
))
@ -328,6 +332,7 @@ private func sendUploadedMessageContent(
var videoTimestamp: Int32?
var sendAsPeerId: PeerId?
var bubbleUpEmojiOrStickersets = false
var allowPaidStars: Int64?
var flags: Int32 = 0
@ -365,6 +370,8 @@ private func sendUploadedMessageContent(
} else if let attribute = attribute as? ForwardVideoTimestampAttribute {
flags |= Int32(1 << 20)
videoTimestamp = attribute.timestamp
} else if let attribute = attribute as? PaidStarsMessageAttribute {
allowPaidStars = attribute.stars.value
}
}
@ -390,6 +397,11 @@ private func sendUploadedMessageContent(
flags |= (1 << 13)
}
if let _ = allowPaidStars {
flags |= 1 << 21
}
let dependencyTag: PendingMessageRequestDependencyTag? = nil//(messageId: messageId)
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):
if bubbleUpEmojiOrStickersets {
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)
case let .forward(sourceInfo):
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) {
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)
} else {
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)
case .messageScreenshot:
let replyTo: Api.InputReplyTo
@ -585,6 +597,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
var replyToStoryId: StoryId?
var scheduleTime: Int32?
var sendAsPeerId: PeerId?
var allowPaidStars: Int64?
var flags: Int32 = 0
flags |= (1 << 7)
@ -609,6 +622,8 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
scheduleTime = attribute.scheduleTime
} else if let attribute = attribute as? SendAsMessageAttribute {
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)
}
if let _ = allowPaidStars {
flags |= 1 << 21
}
let sendMessageRequest: Signal<Api.Updates, NoError>
switch content {
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)
}
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
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)
}
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
return .complete()
}

View File

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

View File

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

View File

@ -2,15 +2,15 @@ import Foundation
import Postbox
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 {
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 {
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, sendPaidMessageStars: sendPaidMessageStars, postpone: postpone, correlationId: correlationId) else {
return false
}
let _ = enqueueMessages(account: account, peerId: peerId, messages: [message]).start()
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
if replyToMessageId == nil, let threadId = threadId {
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 {
attributes.append(NotificationInfoMessageAttribute(flags: .muted))
}
if let sendPaidMessageStars {
attributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: postpone))
}
switch result.message {
case let .auto(caption, entities, replyMarkup):
if let entities = entities {

View File

@ -247,13 +247,14 @@ public extension TelegramEngine {
storyId: StoryId? = nil,
content: EngineOutgoingMessageContent,
silentPosting: Bool = false,
scheduleTime: Int32? = nil
scheduleTime: Int32? = nil,
sendPaidMessageStars: StarsAmount? = nil
) -> Signal<[MessageId?], NoError> {
var message: EnqueueMessage?
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 {
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 {
var attributes: [MessageAttribute] = []
if silentPosting {
@ -262,6 +263,9 @@ public extension TelegramEngine {
if let scheduleTime = scheduleTime {
attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime))
}
if let sendPaidMessageStars {
attributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
}
var text: String = ""
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 {
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)
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, 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? {
return _internal_outgoingMessageWithChatContextResult(to: peerId, threadId: threadId, botId: botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId)
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, sendPaidMessageStars: sendPaidMessageStars, postpone: postpone, correlationId: correlationId)
}
public func setMessageReactions(

View File

@ -1064,7 +1064,8 @@ private final class ProfileGiftsContextImpl {
}
return postbox.transaction { transaction -> ([ProfileGiftsContext.State.StarGift], Int32, String?, Bool?) in
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)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)

View File

@ -99,8 +99,8 @@ 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 {
case let .missingInvitee(flags, userId):
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 }
|> mapToSignal { result -> Signal<Void, AddGroupMemberError> in
@ -186,6 +190,8 @@ func _internal_addChannelMember(account: Account, peerId: PeerId, memberId: Peer
switch result {
case let .invitedUsers(updates, missingInvitees):
if case let .missingInvitee(flags, _) = missingInvitees.first {
let _ = _internal_updateIsPremiumRequiredToContact(account: account, peerIds: [memberPeer.id]).startStandalone()
return .fail(.restricted(TelegramForbiddenInvitePeer(
peer: EnginePeer(memberPeer),
canInviteWithPremium: (flags & (1 << 0)) != 0,
@ -302,7 +308,7 @@ func _internal_addChannelMembers(account: Account, peerId: PeerId, memberIds: [P
account.viewTracker.forceUpdateCachedPeerData(peerId: peerId)
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 {
case let .missingInvitee(flags, userId):
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)
}

View File

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

View File

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

View File

@ -328,23 +328,34 @@ private class ChatMessagePaymentAlertController: AlertController {
self.context = context
self.presentationData = presentationData
self.parentNavigationController = navigationController
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) {
preconditionFailure()
}
override func dismissAnimated() {
super.dismissAnimated()
private func animateOut() {
if let view = self.balance.view {
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)
}
}
override func dismissAnimated() {
super.dismissAnimated()
self.animateOut()
}
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
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)
var maxTitleWidth: CGFloat = 0.0
var maxValueWidth: CGFloat = 0.0
var registrationDateText: String?
let registrationDateTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)?
let registrationDateValueLayoutAndApply: (TextNodeLayout, () -> TextNode)?
@ -272,7 +275,8 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
backgroundSize.height += verticalSpacing
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 {
registrationDateTitleLayoutAndApply = nil
registrationDateValueLayoutAndApply = nil
@ -297,7 +301,8 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
backgroundSize.height += verticalSpacing
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 {
phoneCountryTitleLayoutAndApply = nil
phoneCountryValueLayoutAndApply = nil
@ -322,12 +327,15 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
backgroundSize.height += verticalSpacing
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 {
locationCountryTitleLayoutAndApply = 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()))
backgroundSize.height += verticalSpacing * 2.0 + paragraphSpacing
backgroundSize.height += groupsLayout.size.height

View File

@ -502,9 +502,7 @@ final class GiftOptionsScreenComponent: Component {
return
}
//TODO:unmock
let context = component.context
let alertController = giftTransferAlertController(
context: context,
gift: transferGift,
@ -517,36 +515,53 @@ final class GiftOptionsScreenComponent: Component {
guard let controller, let navigationController = controller.navigationController as? NavigationController else {
return
}
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is ContactSelectionController) && !($0 is GiftOptionsScreen) }
if peer.id.namespace == Namespaces.Peer.CloudChannel {
if let controller = context.sharedContext.makePeerInfoController(
context: context,
updatedPresentationData: nil,
peer: peer._asPeer(),
mode: .gifts,
avatarInitiallyExpanded: false,
fromChat: false,
requestsContext: nil
) {
controllers.append(controller)
}
} else {
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) }
var foundController = false
for controller in controllers.reversed() {
if let chatController = controller as? ChatController, case .peer(id: peer.id) = chatController.chatLocation {
if let controller = controller as? PeerInfoScreen, controller.peerId == component.peerId {
foundController = true
break
}
}
if !foundController {
if let controller = context.sharedContext.makePeerInfoController(
context: context,
updatedPresentationData: nil,
peer: peer._asPeer(),
mode: .gifts,
avatarInitiallyExpanded: false,
fromChat: false,
requestsContext: nil
) {
controllers.append(controller)
}
}
navigationController.setViewControllers(controllers, animated: true)
} else {
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) }
var foundController = false
for controller in controllers.reversed() {
if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation {
chatController.hintPlayNextOutgoingGift()
foundController = true
break
}
}
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()
controllers.append(chatController)
}
navigationController.setViewControllers(controllers, animated: true)
}
if let completion = component.completion {
completion()
}
navigationController.setViewControllers(controllers, animated: true)
}
)
controller.present(alertController, in: .window(.root))

View File

@ -253,7 +253,7 @@ public func presentedLegacyShortcutCamera(context: AccountContext, saveCapturedM
nativeGenerator(_1, _2, _3, nil)
})
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 {
return .single(.done)
}

View File

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

View File

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

View File

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

View File

@ -160,6 +160,7 @@ public final class MessageInputPanelComponent: Component {
public let strings: PresentationStrings
public let style: Style
public let placeholder: Placeholder
public let sendPaidMessageStars: StarsAmount?
public let maxLength: Int?
public let queryTypes: ContextQueryTypes
public let alwaysDarkWhenHasText: Bool
@ -218,6 +219,7 @@ public final class MessageInputPanelComponent: Component {
strings: PresentationStrings,
style: Style,
placeholder: Placeholder,
sendPaidMessageStars: StarsAmount?,
maxLength: Int?,
queryTypes: ContextQueryTypes,
alwaysDarkWhenHasText: Bool,
@ -276,6 +278,7 @@ public final class MessageInputPanelComponent: Component {
self.style = style
self.nextInputMode = nextInputMode
self.placeholder = placeholder
self.sendPaidMessageStars = sendPaidMessageStars
self.maxLength = maxLength
self.queryTypes = queryTypes
self.alwaysDarkWhenHasText = alwaysDarkWhenHasText
@ -346,6 +349,9 @@ public final class MessageInputPanelComponent: Component {
if lhs.placeholder != rhs.placeholder {
return false
}
if lhs.sendPaidMessageStars != rhs.sendPaidMessageStars {
return false
}
if lhs.maxLength != rhs.maxLength {
return false
}
@ -849,43 +855,75 @@ public final class MessageInputPanelComponent: Component {
)
let isEditing = self.textFieldExternalState.isEditing || component.forceIsEditing
var placeholderItems: [AnimatedTextComponent.Item] = []
switch component.placeholder {
case let .plain(string):
placeholderItems.append(AnimatedTextComponent.Item(id: AnyHashable(0 as Int), content: .text(string)))
case let .counter(items):
for item in items {
switch item.content {
case let .text(string):
placeholderItems.append(AnimatedTextComponent.Item(id: AnyHashable(item.id), content: .text(string)))
case let .number(value, minDigits):
placeholderItems.append(AnimatedTextComponent.Item(id: AnyHashable(item.id), content: .number(value, minDigits: minDigits)))
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] = []
switch component.placeholder {
case let .plain(string):
placeholderItems.append(AnimatedTextComponent.Item(id: AnyHashable(0 as Int), content: .text(string)))
case let .counter(items):
for item in items {
switch item.content {
case let .text(string):
placeholderItems.append(AnimatedTextComponent.Item(id: AnyHashable(item.id), content: .text(string)))
case let .number(value, minDigits):
placeholderItems.append(AnimatedTextComponent.Item(id: AnyHashable(item.id), content: .number(value, minDigits: minDigits)))
}
}
}
placeholderSize = self.placeholder.update(
transition: placeholderTransition,
component: AnyComponent(AnimatedTextComponent(
font: Font.regular(17.0),
color: UIColor(rgb: 0xffffff, alpha: 0.3),
items: placeholderItems
)),
environment: {},
containerSize: availableTextFieldSize
)
let _ = self.vibrancyPlaceholder.update(
transition: placeholderTransition,
component: AnyComponent(AnimatedTextComponent(
font: Font.regular(17.0),
color: .black,
items: placeholderItems
)),
environment: {},
containerSize: availableTextFieldSize
)
}
let placeholderTransition: ComponentTransition = (previousPlaceholder != nil && previousPlaceholder != component.placeholder) ? ComponentTransition(animation: .curve(duration: 0.3, curve: .spring)) : .immediate
let placeholderSize = self.placeholder.update(
transition: placeholderTransition,
component: AnyComponent(AnimatedTextComponent(
font: Font.regular(17.0),
color: UIColor(rgb: 0xffffff, alpha: 0.3),
items: placeholderItems
)),
environment: {},
containerSize: availableTextFieldSize
)
let _ = self.vibrancyPlaceholder.update(
transition: placeholderTransition,
component: AnyComponent(AnimatedTextComponent(
font: Font.regular(17.0),
color: .black,
items: placeholderItems
)),
environment: {},
containerSize: availableTextFieldSize
)
if !isEditing && component.setMediaRecordingActive == nil {
insets.right = defaultInsets.left
}

View File

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

View File

@ -17,6 +17,7 @@ import UndoUI
import AnimatedAvatarSetNode
import AvatarNode
import TelegramStringFormatting
import ChatMessagePaymentAlertController
private final class SendInviteLinkScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -26,19 +27,22 @@ private final class SendInviteLinkScreenComponent: Component {
let link: String?
let peers: [TelegramForbiddenInvitePeer]
let peerPresences: [EnginePeer.Id: EnginePeer.Presence]
let sendPaidMessageStars: [EnginePeer.Id: StarsAmount]
init(
context: AccountContext,
peer: EnginePeer,
link: String?,
peers: [TelegramForbiddenInvitePeer],
peerPresences: [EnginePeer.Id: EnginePeer.Presence]
peerPresences: [EnginePeer.Id: EnginePeer.Presence],
sendPaidMessageStars: [EnginePeer.Id: StarsAmount]
) {
self.context = context
self.peer = peer
self.link = link
self.peers = peers
self.peerPresences = peerPresences
self.sendPaidMessageStars = sendPaidMessageStars
}
static func ==(lhs: SendInviteLinkScreenComponent, rhs: SendInviteLinkScreenComponent) -> Bool {
@ -54,6 +58,9 @@ private final class SendInviteLinkScreenComponent: Component {
if lhs.peerPresences != rhs.peerPresences {
return false
}
if lhs.sendPaidMessageStars != rhs.sendPaidMessageStars {
return false
}
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 {
let environment = environment[ViewControllerComponentContainer.Environment.self].value
let themeUpdated = self.environment?.theme !== environment.theme
@ -851,20 +890,37 @@ private final class SendInviteLinkScreenComponent: Component {
} else if let link = component.link {
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()
let text: String
if selectedPeers.count == 1 {
text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string
} else if selectedPeers.count == 2 {
text = environment.strings.Conversation_ShareLinkTooltip_TwoChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), selectedPeers[1].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string
} else {
text = environment.strings.Conversation_ShareLinkTooltip_ManyChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), "\(selectedPeers.count - 1)").string
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
controller.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: false, text: text), elevatedLayout: false, action: { _ in return false }), in: .window(.root))
controller.dismiss()
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
if selectedPeers.count == 1 {
text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string
} else if selectedPeers.count == 2 {
text = environment.strings.Conversation_ShareLinkTooltip_TwoChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), selectedPeers[1].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string
} else {
text = environment.strings.Conversation_ShareLinkTooltip_ManyChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), "\(selectedPeers.count - 1)").string
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
controller.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: false, text: text), elevatedLayout: false, action: { _ in return false }), in: .window(.root))
controller.dismiss()
}
)
} else {
controller.dismiss()
}
@ -1083,16 +1139,21 @@ public class SendInviteLinkScreen: ViewControllerComponentContainer {
self.link = link
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.navigationPresentation = .flatModal
self.blocksBackgroundWhenInOverlay = true
self.presenceDisposable = (context.engine.data.subscribe(EngineDataMap(
peers.map(\.peer.id).map(TelegramEngine.EngineData.Item.Peer.Presence.init(id:))
))
|> deliverOnMainQueue).start(next: { [weak self] presences in
self.presenceDisposable = (context.engine.data.subscribe(
EngineDataMap(
peers.map(\.peer.id).map(TelegramEngine.EngineData.Item.Peer.Presence.init(id:))
),
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 {
return
}
@ -1102,7 +1163,13 @@ public class SendInviteLinkScreen: ViewControllerComponentContainer {
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())
}
let sentItems: ([PeerId], [PeerId: Int64], [PreparedShareItemContent], ShareControllerAccountContext, Bool, String) -> Signal<ShareControllerExternalStatus, NoError> = { peerIds, threadIds, 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: ([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, requireStars: requireStars, items: contents, silently: silently, additionalText: additionalText)
|> `catch` { _ -> Signal<
Float, NoError> in
return .complete()
@ -537,8 +537,20 @@ public class ShareRootControllerImpl {
}
|> 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 {
let rawSignals = TGItemProviderSignals.itemSignals(forInputItems: inputItems)!
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)
|> castError(ShareControllerError.self)
|> 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)
}
case let .done(contents):
return sentItems(peerIds, threadIds, contents, account, silently, additionalText)
return sentItems(peerIds, threadIds, requireStars, contents, account, silently, additionalText)
|> castError(ShareControllerError.self)
}
}

View File

@ -124,9 +124,9 @@ private final class SheetContent: CombinedComponent {
minAmount = StarsAmount(value: 1, 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 {
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))"
} else {
amountLabel = nil

View File

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

View File

@ -219,7 +219,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil,
appliedBoosts: nil
appliedBoosts: nil,
sendPaidMessageStars: cachedUserData.sendPaidMessageStars
)
} else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData {
additionalPeerData = StoryContentContextState.AdditionalPeerData(
@ -230,7 +231,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: cachedChannelData.boostsToUnrestrict,
appliedBoosts: cachedChannelData.appliedBoosts
appliedBoosts: cachedChannelData.appliedBoosts,
sendPaidMessageStars: cachedChannelData.sendPaidMessageStars
)
} else {
additionalPeerData = StoryContentContextState.AdditionalPeerData(
@ -241,7 +243,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil,
appliedBoosts: nil
appliedBoosts: nil,
sendPaidMessageStars: nil
)
}
} else {
@ -253,7 +256,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil,
appliedBoosts: nil
appliedBoosts: nil,
sendPaidMessageStars: nil
)
}
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.Peer.IsPremiumRequiredForMessaging(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
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
}
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
guard let peer else {
@ -1270,7 +1275,8 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: boostsToUnrestrict,
appliedBoosts: appliedBoosts
appliedBoosts: appliedBoosts,
sendPaidMessageStars: sendPaidMessageStars
)
for (storyId, story) in forwardInfoStories {
@ -1436,9 +1442,11 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
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.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
}
}
@ -1544,7 +1552,8 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
TelegramEngine.EngineData.Item.NotificationSettings.Global(),
TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(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) })
self.currentPeerData = currentPeerData
@ -1563,7 +1572,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
self.listState = state
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 additionalPeerData = StoryContentContextState.AdditionalPeerData(
isMuted: isMuted,
@ -1573,7 +1582,8 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: boostsToUnrestrict,
appliedBoosts: appliedBoosts
appliedBoosts: appliedBoosts,
sendPaidMessageStars: sendPaidMessageStars
)
let item = state.items[focusedIndex]
@ -2462,7 +2472,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil,
appliedBoosts: nil
appliedBoosts: nil,
sendPaidMessageStars: cachedUserData.sendPaidMessageStars
)
} else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData {
additionalPeerData = StoryContentContextState.AdditionalPeerData(
@ -2473,7 +2484,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: cachedChannelData.boostsToUnrestrict,
appliedBoosts: cachedChannelData.appliedBoosts
appliedBoosts: cachedChannelData.appliedBoosts,
sendPaidMessageStars: cachedChannelData.sendPaidMessageStars
)
} else {
additionalPeerData = StoryContentContextState.AdditionalPeerData(
@ -2484,7 +2496,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil,
appliedBoosts: nil
appliedBoosts: nil,
sendPaidMessageStars: nil
)
}
}
@ -2497,7 +2510,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext {
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil,
appliedBoosts: nil
appliedBoosts: nil,
sendPaidMessageStars: nil
)
}

View File

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

View File

@ -2823,7 +2823,12 @@ public final class StoryItemSetContainerComponent: Component {
inputPlaceholder = .counter(items)
} else {
inputPlaceholder = .plain(isGroup ? component.strings.Story_InputPlaceholderReplyInGroup : component.strings.Story_InputPlaceholderReplyPrivately)
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 {
inputPlaceholder = .plain(isGroup ? component.strings.Story_InputPlaceholderReplyInGroup : component.strings.Story_InputPlaceholderReplyPrivately)
}
}
let startTime22 = CFAbsoluteTimeGetCurrent()
@ -2867,6 +2872,7 @@ public final class StoryItemSetContainerComponent: Component {
strings: component.strings,
style: .story,
placeholder: inputPlaceholder,
sendPaidMessageStars: component.slice.additionalPeerData.sendPaidMessageStars,
maxLength: 4096,
queryTypes: [.mention, .hashtag, .emoji],
alwaysDarkWhenHasText: component.metrics.widthClass == .regular,
@ -4647,6 +4653,10 @@ public final class StoryItemSetContainerComponent: Component {
case .stars:
break
}
if let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars {
messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
}
let message: EnqueueMessage = .message(
text: text,
@ -4693,7 +4703,6 @@ public final class StoryItemSetContainerComponent: Component {
})
}
}
if self.displayLikeReactions {
if component.slice.item.storyItem.myReaction == updateReaction.reaction {
action()
@ -4704,7 +4713,9 @@ public final class StoryItemSetContainerComponent: Component {
}
} else {
self.sendMessageContext.performWithPossibleStealthModeConfirmation(view: self, action: {
action()
self.sendMessageContext.presentPaidMessageAlertIfNeeded(view: self, completion: {
action()
})
})
}
}

View File

@ -50,6 +50,7 @@ import LocationUI
import ReactionSelectionNode
import StoryQualityUpgradeSheetScreen
import AudioWaveform
import ChatMessagePaymentAlertController
private var ObjCKey_DeinitWatcher: Int?
@ -491,6 +492,30 @@ final class StoryItemSetContainerSendMessage {
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) {
guard let component = view.component, component.stealthModeTimeout != nil else {
action()
@ -512,7 +537,6 @@ final class StoryItemSetContainerSendMessage {
let timestamp = Int32(Date().timeIntervalSince1970)
if noticeCount < 1, let activeUntilTimestamp = config.stealthModeState.actualizedNow().activeUntilTimestamp, activeUntilTimestamp > timestamp {
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) })
@ -575,53 +599,64 @@ final class StoryItemSetContainerSendMessage {
let controller = component.controller() as? StoryContainerScreen
if let recordedAudioPreview = self.recordedAudioPreview, case let .audio(audio) = recordedAudioPreview {
self.recordedAudioPreview = nil
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: [])]
let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: messages).start()
view.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .spring)))
} else if self.hasRecordedVideoPreview, let videoRecorderValue = self.videoRecorderValue {
videoRecorderValue.send()
self.hasRecordedVideoPreview = false
self.videoRecorder.set(.single(nil))
view.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .spring)))
} else {
switch inputPanelView.getSendMessageInput() {
case let .text(text):
if !text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
let entities = generateChatInputTextEntities(text)
let _ = (component.context.engine.messages.enqueueOutgoingMessage(
to: peerId,
replyTo: nil,
storyId: focusedStoryId,
content: .text(text.string, entities),
silentPosting: silentPosting,
scheduleTime: scheduleTime
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
Queue.mainQueue().after(0.3) {
if let self, let view {
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }, isScheduled: scheduleTime != nil)
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 {
self.recordedAudioPreview = nil
let waveformBuffer = audio.waveform.makeBitstream()
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()
view.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .spring)))
} else if self.hasRecordedVideoPreview, let videoRecorderValue = self.videoRecorderValue {
videoRecorderValue.send()
self.hasRecordedVideoPreview = false
self.videoRecorder.set(.single(nil))
view.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .spring)))
} else {
switch inputPanelView.getSendMessageInput() {
case let .text(text):
if !text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
let entities = generateChatInputTextEntities(text)
let _ = (component.context.engine.messages.enqueueOutgoingMessage(
to: peerId,
replyTo: nil,
storyId: focusedStoryId,
content: .text(text.string, entities),
silentPosting: silentPosting,
scheduleTime: scheduleTime,
sendPaidMessageStars: component.slice.additionalPeerData.sendPaidMessageStars
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
Queue.mainQueue().after(0.3) {
if let self, let view {
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }, isScheduled: scheduleTime != nil)
}
}
})
component.storyItemSharedState.replyDrafts.removeValue(forKey: StoryId(peerId: peerId, id: focusedItem.storyItem.id))
inputPanelView.clearSendMessageInput(updateState: true)
self.currentInputMode = .text
if hasFirstResponder(view) {
view.endEditing(true)
} else {
view.state?.updated(transition: .spring(duration: 0.3))
}
})
component.storyItemSharedState.replyDrafts.removeValue(forKey: StoryId(peerId: peerId, id: focusedItem.storyItem.id))
inputPanelView.clearSendMessageInput(updateState: true)
self.currentInputMode = .text
if hasFirstResponder(view) {
view.endEditing(true)
} else {
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))
}
}
}
})
})
}
@ -660,26 +695,32 @@ final class StoryItemSetContainerSendMessage {
})
}
let _ = (component.context.engine.messages.enqueueOutgoingMessage(
to: peerId,
replyTo: nil,
storyId: focusedStoryId,
content: .file(fileReference)
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
Queue.mainQueue().after(0.3) {
if let self, let view {
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
}
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in
guard let self, let view else {
return
}
let _ = (component.context.engine.messages.enqueueOutgoingMessage(
to: peerId,
replyTo: nil,
storyId: focusedStoryId,
content: .file(fileReference),
sendPaidMessageStars: component.slice.additionalPeerData.sendPaidMessageStars
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
Queue.mainQueue().after(0.3) {
if let self, let view {
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
}
}
})
self.currentInputMode = .text
if hasFirstResponder(view) {
view.endEditing(true)
} else {
view.state?.updated(transition: .spring(duration: 0.3))
}
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
})
self.currentInputMode = .text
if hasFirstResponder(view) {
view.endEditing(true)
} else {
view.state?.updated(transition: .spring(duration: 0.3))
}
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
})
}
@ -714,37 +755,48 @@ final class StoryItemSetContainerSendMessage {
})
}
let _ = (component.context.engine.messages.enqueueOutgoingMessage(
to: peerId,
replyTo: nil,
storyId: focusedStoryId,
content: .contextResult(results, result)
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
Queue.mainQueue().after(0.3) {
if let self, let view {
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
}
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in
guard let self, let view else {
return
}
let _ = (component.context.engine.messages.enqueueOutgoingMessage(
to: peerId,
replyTo: nil,
storyId: focusedStoryId,
content: .contextResult(results, result),
sendPaidMessageStars: component.slice.additionalPeerData.sendPaidMessageStars
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
Queue.mainQueue().after(0.3) {
if let self, let view {
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
}
}
})
self.currentInputMode = .text
if hasFirstResponder(view) {
view.endEditing(true)
} else {
view.state?.updated(transition: .spring(duration: 0.3))
}
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
})
self.currentInputMode = .text
if hasFirstResponder(view) {
view.endEditing(true)
} else {
view.state?.updated(transition: .spring(duration: 0.3))
}
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
}
func enqueueGifData(view: StoryItemSetContainerComponent.View, data: Data) {
guard let component = view.component else {
return
}
let peer = component.slice.effectivePeer
let _ = (legacyEnqueueGifMessage(account: component.context.account, data: data) |> deliverOnMainQueue).start(next: { [weak self, weak view] message in
if let self, let view {
self.sendMessages(view: view, peer: peer, messages: [message])
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in
guard let self else {
return
}
let peer = component.slice.effectivePeer
let _ = (legacyEnqueueGifMessage(account: component.context.account, data: data) |> deliverOnMainQueue).start(next: { [weak self, weak view] message in
if let self, let view {
self.sendMessages(view: view, peer: peer, messages: [message])
}
})
})
}
@ -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 message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
self.sendMessages(view: view, peer: peer, messages: [message], silentPosting: false)
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in
guard let self else {
return
}
self.sendMessages(view: view, peer: peer, messages: [message], silentPosting: false)
})
}
})
}
@ -846,7 +903,12 @@ final class StoryItemSetContainerSendMessage {
guard let self, let view else {
return
}
self.sendMessages(view: view, peer: peer, messages: [updatedMessage])
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in
guard let self, let view else {
return
}
self.sendMessages(view: view, peer: peer, messages: [updatedMessage])
})
})
}, displaySlowmodeTooltip: { [weak self] view, rect in
//self?.interfaceInteraction?.displaySlowmodeTooltip(view, rect)
@ -896,9 +958,14 @@ final class StoryItemSetContainerSendMessage {
guard let self, let view else {
return
}
self.sendMessages(view: view, peer: peer, messages: [.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(data.compressedData.count), attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])])
HapticFeedback().tap()
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in
guard let self, let view else {
return
}
self.sendMessages(view: view, peer: peer, messages: [.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(data.compressedData.count), attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])])
HapticFeedback().tap()
})
})
}
})
@ -1593,7 +1660,11 @@ final class StoryItemSetContainerSendMessage {
guard let view, let component = view.component else {
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)])
|> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
if let self, let view {
@ -1636,7 +1707,12 @@ final class StoryItemSetContainerSendMessage {
return
}
let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: location), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
self.sendMessages(view: view, peer: peer, messages: [message])
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in
guard let self else {
return
}
self.sendMessages(view: view, peer: peer, messages: [message])
})
})
completion(controller, controller.mediaPickerContext)
@ -1705,7 +1781,12 @@ final class StoryItemSetContainerSendMessage {
}
}
self.sendMessages(view: view, peer: peer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)
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)
})
} else if let peer = peers.first {
let dataSignal: Signal<(EnginePeer?, DeviceContactExtendedData?), NoError>
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: []))
self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)
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)
})
} 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
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: []))
self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)
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)
})
}
}), completed: nil, cancelled: nil)
component.controller()?.push(contactController)
@ -2187,7 +2278,12 @@ final class StoryItemSetContainerSendMessage {
}
if !messages.isEmpty {
strongSelf.sendMessages(view: view, peer: peer, messages: messages)
strongSelf.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.sendMessages(view: view, peer: peer, messages: messages)
})
}
}
}))
@ -2248,7 +2344,12 @@ final class StoryItemSetContainerSendMessage {
if !inputText.string.isEmpty {
self.clearInputText(view: view)
}
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
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)
})
}
)
}
@ -2268,7 +2369,19 @@ final class StoryItemSetContainerSendMessage {
guard let self, let view, let component = view.component else {
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 {
@ -2416,10 +2529,15 @@ final class StoryItemSetContainerSendMessage {
guard let self, let view else {
return
}
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil, parameters: parameters)
if !inputText.string.isEmpty {
self.clearInputText(view: view)
}
self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in
guard let self, let view else {
return
}
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil, parameters: parameters)
if !inputText.string.isEmpty {
self.clearInputText(view: view)
}
})
}, recognizedQRCode: { _ in
}, presentSchedulePicker: { [weak self, weak view] _, done in
guard let self, let view else {
@ -2545,6 +2663,10 @@ final class StoryItemSetContainerSendMessage {
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
}
}

View File

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

View File

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

View File

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

View File

@ -13,8 +13,9 @@ import ChatInterfaceState
import PremiumUI
import ReactionSelectionNode
import TopMessageReactions
import ChatMessagePaymentAlertController
extension ChatControllerImpl {
extension ChatControllerImpl {
func forwardMessages(messageIds: [MessageId], options: ChatInterfaceForwardOptionsState? = nil, resetCurrent: Bool = false) {
let _ = (self.context.engine.data.get(EngineDataMap(
messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init)
@ -94,190 +95,246 @@ extension ChatControllerImpl {
}
}
controller.multiplePeersSelected = { [weak self, weak controller] peers, peerMap, messageText, mode, forwardOptions, _ in
guard let strongSelf = self, let strongController = controller else {
return
}
strongController.dismiss()
let peerIds = peers.map { $0.id }
var result: [EnqueueMessage] = []
if messageText.string.count > 0 {
let inputText = convertMarkdownToAttributes(messageText)
for text in breakChatInputText(trimChatInputText(inputText)) {
if text.length != 0 {
var attributes: [MessageAttribute] = []
let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text))
if !entities.isEmpty {
attributes.append(TextEntitiesMessageAttribute(entities: entities))
}
result.append(.message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: nil, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
}
}
}
var attributes: [MessageAttribute] = []
attributes.append(ForwardOptionsMessageAttribute(hideNames: forwardOptions?.hideNames == true, hideCaptions: forwardOptions?.hideCaptions == true))
result.append(contentsOf: messages.map { message -> EnqueueMessage in
return .forward(source: message.id, threadId: nil, grouping: .auto, attributes: attributes, correlationId: nil)
})
let commit: ([EnqueueMessage]) -> Void = { result in
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 result = result
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withoutSelectionState() }).updatedSearch(nil) })
var correlationIds: [Int64] = []
for i in 0 ..< result.count {
let correlationId = Int64.random(in: Int64.min ... Int64.max)
correlationIds.append(correlationId)
result[i] = result[i].withUpdatedCorrelationId(correlationId)
var count: Int32 = Int32(messages.count)
if messageText.string.count > 0 {
count += 1
}
let targetPeersShouldDivertSignals: [Signal<(EnginePeer, Bool), NoError>] = peers.map { peer -> Signal<(EnginePeer, Bool), NoError> in
return strongSelf.shouldDivertMessagesToScheduled(targetPeer: peer, messages: result)
|> map { shouldDivert -> (EnginePeer, Bool) in
return (peer, shouldDivert)
var totalAmount: StarsAmount = .zero
for peer in peers {
if let maybeAmount = sendPaidMessageStars[peer.id], let amount = maybeAmount {
totalAmount = totalAmount + amount
}
}
let targetPeersShouldDivert: Signal<[(EnginePeer, Bool)], NoError> = combineLatest(targetPeersShouldDivertSignals)
let _ = (targetPeersShouldDivert
|> deliverOnMainQueue).startStandalone(next: { targetPeersShouldDivert in
guard let strongSelf = self else {
let proceed = { [weak self, weak controller] in
guard let strongSelf = self, let strongController = controller else {
return
}
var displayConvertingTooltip = false
strongController.dismiss()
var displayPeers: [EnginePeer] = []
for (peer, shouldDivert) in targetPeersShouldDivert {
var peerMessages = result
if shouldDivert {
displayConvertingTooltip = true
peerMessages = peerMessages.map { message -> EnqueueMessage in
return message.withUpdatedAttributes { attributes in
var attributes = attributes
attributes.removeAll(where: { $0 is OutgoingScheduleInfoMessageAttribute })
attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(Date().timeIntervalSince1970) + 10 * 24 * 60 * 60))
return attributes
var result: [EnqueueMessage] = []
if messageText.string.count > 0 {
let inputText = convertMarkdownToAttributes(messageText)
for text in breakChatInputText(trimChatInputText(inputText)) {
if text.length != 0 {
var attributes: [MessageAttribute] = []
let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text))
if !entities.isEmpty {
attributes.append(TextEntitiesMessageAttribute(entities: entities))
}
result.append(.message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: nil, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
}
}
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peer.id, messages: peerMessages)
|> deliverOnMainQueue).startStandalone(next: { messageIds in
if let strongSelf = self {
let signals: [Signal<Bool, NoError>] = messageIds.compactMap({ id -> Signal<Bool, NoError>? in
guard let id = id else {
return nil
}
return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id)
|> mapToSignal { status, _ -> Signal<Bool, NoError> in
if status != nil {
return .never()
} else {
return .single(true)
}
}
|> take(1)
})
if strongSelf.shareStatusDisposable == nil {
strongSelf.shareStatusDisposable = MetaDisposable()
}
strongSelf.shareStatusDisposable?.set((combineLatest(signals)
|> deliverOnMainQueue).startStrict())
}
})
if case let .secretChat(secretPeer) = peer {
if let peer = peerMap[secretPeer.regularPeerId] {
displayPeers.append(peer)
}
} else {
displayPeers.append(peer)
}
}
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let text: String
var savedMessages = false
if displayPeers.count == 1, let peerId = displayPeers.first?.id, peerId == strongSelf.context.account.peerId {
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many
savedMessages = true
} else {
if displayPeers.count == 1, let peer = displayPeers.first {
var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string
} else if displayPeers.count == 2, let firstPeer = displayPeers.first, let secondPeer = displayPeers.last {
var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "")
var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).string
} else if let peer = displayPeers.first {
var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(displayPeers.count - 1)").string : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(displayPeers.count - 1)").string
} else {
text = ""
}
}
let reactionItems: Signal<[ReactionItem], NoError>
if savedMessages && messages.count > 0 {
reactionItems = tagMessageReactions(context: strongSelf.context, subPeerId: nil)
} else {
reactionItems = .single([])
}
var attributes: [MessageAttribute] = []
attributes.append(ForwardOptionsMessageAttribute(hideNames: forwardOptions?.hideNames == true, hideCaptions: forwardOptions?.hideCaptions == true))
let _ = (reactionItems
|> deliverOnMainQueue).startStandalone(next: { [weak strongSelf] reactionItems in
guard let strongSelf else {
return
}
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, position: savedMessages && messages.count > 0 ? .top : .bottom, animateInAsReplacement: true, action: { action in
if savedMessages, let self, action == .info {
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let peer else {
return
}
guard let navigationController = self.navigationController as? NavigationController else {
return
}
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true))
})
}
return false
}, additionalView: (savedMessages && messages.count > 0) ? chatShareToSavedMessagesAdditionalView(strongSelf, reactionItems: reactionItems, correlationIds: correlationIds) : nil), in: .current)
result.append(contentsOf: messages.map { message -> EnqueueMessage in
return .forward(source: message.id, threadId: nil, grouping: .auto, attributes: attributes, correlationId: nil)
})
if displayConvertingTooltip {
let commit: ([EnqueueMessage]) -> Void = { result in
guard let strongSelf = self else {
return
}
var result = result
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withoutSelectionState() }).updatedSearch(nil) })
var correlationIds: [Int64] = []
for i in 0 ..< result.count {
let correlationId = Int64.random(in: Int64.min ... Int64.max)
correlationIds.append(correlationId)
result[i] = result[i].withUpdatedCorrelationId(correlationId)
}
let targetPeersShouldDivertSignals: [Signal<(EnginePeer, Bool), NoError>] = peers.map { peer -> Signal<(EnginePeer, Bool), NoError> in
return strongSelf.shouldDivertMessagesToScheduled(targetPeer: peer, messages: result)
|> map { shouldDivert -> (EnginePeer, Bool) in
return (peer, shouldDivert)
}
}
let targetPeersShouldDivert: Signal<[(EnginePeer, Bool)], NoError> = combineLatest(targetPeersShouldDivertSignals)
let _ = (targetPeersShouldDivert
|> deliverOnMainQueue).startStandalone(next: { targetPeersShouldDivert in
guard let strongSelf = self else {
return
}
var displayConvertingTooltip = false
var displayPeers: [EnginePeer] = []
for (peer, shouldDivert) in targetPeersShouldDivert {
var peerMessages = result
if shouldDivert {
displayConvertingTooltip = true
peerMessages = peerMessages.map { message -> EnqueueMessage in
return message.withUpdatedAttributes { attributes in
var attributes = attributes
attributes.removeAll(where: { $0 is OutgoingScheduleInfoMessageAttribute })
attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(Date().timeIntervalSince1970) + 10 * 24 * 60 * 60))
return attributes
}
}
}
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)
|> deliverOnMainQueue).startStandalone(next: { messageIds in
if let strongSelf = self {
let signals: [Signal<Bool, NoError>] = messageIds.compactMap({ id -> Signal<Bool, NoError>? in
guard let id = id else {
return nil
}
return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id)
|> mapToSignal { status, _ -> Signal<Bool, NoError> in
if status != nil {
return .never()
} else {
return .single(true)
}
}
|> take(1)
})
if strongSelf.shareStatusDisposable == nil {
strongSelf.shareStatusDisposable = MetaDisposable()
}
strongSelf.shareStatusDisposable?.set((combineLatest(signals)
|> deliverOnMainQueue).startStrict())
}
})
if case let .secretChat(secretPeer) = peer {
if let peer = peerMap[secretPeer.regularPeerId] {
displayPeers.append(peer)
}
} else {
displayPeers.append(peer)
}
}
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let text: String
var savedMessages = false
if displayPeers.count == 1, let peerId = displayPeers.first?.id, peerId == strongSelf.context.account.peerId {
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many
savedMessages = true
} else {
if displayPeers.count == 1, let peer = displayPeers.first {
var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string
} else if displayPeers.count == 2, let firstPeer = displayPeers.first, let secondPeer = displayPeers.last {
var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "")
var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).string
} else if let peer = displayPeers.first {
var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(displayPeers.count - 1)").string : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(displayPeers.count - 1)").string
} else {
text = ""
}
}
let reactionItems: Signal<[ReactionItem], NoError>
if savedMessages && messages.count > 0 {
reactionItems = tagMessageReactions(context: strongSelf.context, subPeerId: nil)
} else {
reactionItems = .single([])
}
let _ = (reactionItems
|> deliverOnMainQueue).startStandalone(next: { [weak strongSelf] reactionItems in
guard let strongSelf else {
return
}
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, position: savedMessages && messages.count > 0 ? .top : .bottom, animateInAsReplacement: true, action: { action in
if savedMessages, let self, action == .info {
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let peer else {
return
}
guard let navigationController = self.navigationController as? NavigationController else {
return
}
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true))
})
}
return false
}, additionalView: (savedMessages && messages.count > 0) ? chatShareToSavedMessagesAdditionalView(strongSelf, reactionItems: reactionItems, correlationIds: correlationIds) : nil), in: .current)
})
if displayConvertingTooltip {
}
})
}
})
}
switch mode {
case .generic:
commit(result)
case .silent:
let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: true)
commit(transformedMessages)
case .schedule:
strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime in
if let strongSelf = self {
let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleTime)
switch mode {
case .generic:
commit(result)
case .silent:
let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: true)
commit(transformedMessages)
case .schedule:
strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime in
if let strongSelf = self {
let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleTime)
commit(transformedMessages)
}
})
case .whenOnline:
let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleWhenOnlineTimestamp)
commit(transformedMessages)
}
})
case .whenOnline:
let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleWhenOnlineTimestamp)
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
guard let strongSelf = self, let strongController = controller else {

View File

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