Monoforums

This commit is contained in:
Isaac 2025-05-29 02:05:13 +08:00
parent 98dc32fe4c
commit eb3f95ea63
21 changed files with 264 additions and 109 deletions

View File

@ -14377,7 +14377,7 @@ Sorry for the inconvenience.";
"PeerInfo.OptionTopics.Enabled" = "Enabled";
"PeerInfo.OptionTopics.Disabled" = "Disabled";
"ChannelMessages.Title" = "Allow Channel Messages";
"ChannelMessages.Title" = "Direct Messages";
"ChannelMessages.Info" = "Allow users to send messages to your channel, with the option to charge a fee for each message.";
"ChannelMessages.SwitchTitle" = "Allow Channel Messages";
"ChannelMessages.PriceSectionTitle" = "PRICE FOR EACH MESSAGE";
@ -14391,10 +14391,21 @@ Sorry for the inconvenience.";
"Chat.InlineTopicMenu.AllTab" = "All";
"Chat.ChannelMessagesHint" = "Send a message to the channel's admin";
"Chat.ChannelMessagesHintBadge" = "NEW";
"Chat.ContextMenu.AuthorInfo" = "Sent by %@";
"PeerInfo.AllowChannelMessages" = "Allow Channel Messages";
"PeerInfo.AllowChannelMessages.On" = "On";
"PeerInfo.AllowChannelMessages" = "Direct Messages";
"PeerInfo.AllowChannelMessages.Free" = "Free";
"PeerInfo.AllowChannelMessages.Off" = "Off";
"PeerInfo.ChannelMessages" = "Channel Messages";
"PeerInfo.ChannelMessages" = "Direct Messages";
"Chat.EmptyStateMonoforum.Text" = "Send a direct message to the administrator of **%@**.";
"Chat.EmptyStateMonoforumPaid.Text" = "**%1$@** charges **%2$@**\nper message to its admin.";
"Monoforum.NameFormat" = "%@ Messages";
"Stars.SendMessage.AdjustmentTitle" = "Price for each Message";
"Stars.SendMessage.AdjustmentPlaceholder" = "Price for each Message";
"Stars.SendMessage.AdjustmentSectionHeader" = "PRICE IN STARS";
"Stars.SendMessage.AdjustmentSectionFooterValue" = "You will receive **%@ Stars**.";
"Stars.SendMessage.AdjustmentSectionFooterEmpty" = "You will receive **80%**.";
"Stars.SendMessage.AdjustmentAction" = "OK";

View File

@ -272,6 +272,10 @@ public final class AvatarEditOverlayNode: ASDisplayNode {
}
}
private func generateAvatarBubblePath() -> CGPath {
return try! convertSvgPath("M60,30.274903 C60,46.843446 46.568544,60.274904 30,60.274904 C13.431458,60.274904 0,46.843446 0,30.274903 C0,23.634797 2.158635,17.499547 5.810547,12.529785 L6.036133,12.226074 C6.921364,10.896042 7.367402,8.104698 5.548828,5.316895 C3.606939,2.340088 1.186019,0.979668 2.399414,0.470215 C3.148032,0.156204 7.572027,0.000065 10.764648,1.790527 C12.148517,2.56662 13.2296,3.342422 14.09224,4.039734 C14.42622,4.309704 14.892063,4.349773 15.265962,4.138523 C19.618079,1.679604 24.644722,0.274902 30,0.274902 C46.568544,0.274902 60,13.70636 60,30.274903 Z ")
}
public final class AvatarNode: ASDisplayNode {
public static func avatarBubbleMask(size: CGSize) -> UIImage! {
return generateImage(size, rotatedContext: { size, context in
@ -282,19 +286,20 @@ public final class AvatarNode: ASDisplayNode {
})
}
public static let avatarBubblePath: CGPath = generateAvatarBubblePath()
public static func addAvatarBubblePath(context: CGContext, rect: CGRect) {
if let path = try? convertSvgPath("M60,30.274903 C60,46.843446 46.568544,60.274904 30,60.274904 C13.431458,60.274904 0,46.843446 0,30.274903 C0,23.634797 2.158635,17.499547 5.810547,12.529785 L6.036133,12.226074 C6.921364,10.896042 7.367402,8.104698 5.548828,5.316895 C3.606939,2.340088 1.186019,0.979668 2.399414,0.470215 C3.148032,0.156204 7.572027,0.000065 10.764648,1.790527 C12.148517,2.56662 13.2296,3.342422 14.09224,4.039734 C14.42622,4.309704 14.892063,4.349773 15.265962,4.138523 C19.618079,1.679604 24.644722,0.274902 30,0.274902 C46.568544,0.274902 60,13.70636 60,30.274903 Z ") {
let sx = rect.width / 60.0
let sy = rect.height / 60.0
var transform = CGAffineTransform(
a: sx, b: 0.0,
c: 0.0, d: -sy,
tx: rect.minX,
ty: rect.minY + rect.height
)
let transformedPath = path.copy(using: &transform)!
context.addPath(transformedPath)
}
let path = AvatarNode.avatarBubblePath
let sx = rect.width / 60.0
let sy = rect.height / 60.274904
var transform = CGAffineTransform(
a: sx, b: 0.0,
c: 0.0, d: -sy,
tx: rect.minX,
ty: rect.minY + rect.height
)
let transformedPath = path.copy(using: &transform)!
context.addPath(transformedPath)
}
public static let gradientColors: [[UIColor]] = [

View File

@ -5316,20 +5316,25 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
var deleteTitle = strongSelf.presentationData.strings.Common_Delete
if case let .channel(channel) = chatPeer {
if case .broadcast = channel.info {
if channel.isMonoForum {
canClear = false
deleteTitle = strongSelf.presentationData.strings.Channel_LeaveChannel
if channel.flags.contains(.isCreator) {
canRemoveGlobally = true
}
canRemoveGlobally = false
} else {
deleteTitle = strongSelf.presentationData.strings.Group_DeleteGroup
if channel.flags.contains(.isCreator) {
canRemoveGlobally = true
if case .broadcast = channel.info {
canClear = false
deleteTitle = strongSelf.presentationData.strings.Channel_LeaveChannel
if channel.flags.contains(.isCreator) {
canRemoveGlobally = true
}
} else {
deleteTitle = strongSelf.presentationData.strings.Group_DeleteGroup
if channel.flags.contains(.isCreator) {
canRemoveGlobally = true
}
}
if let addressName = channel.addressName, !addressName.isEmpty {
canClear = false
}
}
if let addressName = channel.addressName, !addressName.isEmpty {
canClear = false
}
} else if case let .legacyGroup(group) = chatPeer {
if case .creator = group.role {

View File

@ -2980,7 +2980,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
if customMessageListData.commandPrefix != nil {
titleAttributedString = nil
} else {
if let displayTitle = itemPeer.chatMainPeer?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) {
if let displayTitle = itemPeer.chatOrMonoforumMainPeer?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) {
let textColor: UIColor
if case let .chatList(index) = item.index, index.messageIndex.id.peerId.namespace == Namespaces.Peer.SecretChat {
textColor = theme.secretTitleColor
@ -2988,6 +2988,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
textColor = theme.titleColor
}
titleAttributedString = NSAttributedString(string: displayTitle, font: titleFont, textColor: textColor)
if case let .channel(channel) = itemPeer.peer, channel.flags.contains(.isMonoforum) {
titleBadgeText = item.presentationData.strings.ChatList_MonoforumLabel
}
}
}
} else if let threadInfo = threadInfo {

View File

@ -130,8 +130,8 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
text = PresentationStrings.FormattedString(string: strings.ChatList_DeleteSavedMessagesConfirmation, ranges: [])
} else if case let .legacyGroup(chatPeer) = chatPeer {
text = strings.ChatList_DeleteAndLeaveGroupConfirmation(chatPeer.title)
} else if case let .channel(chatPeer) = chatPeer {
text = strings.ChatList_DeleteAndLeaveGroupConfirmation(chatPeer.title)
} else if case .channel = chatPeer {
text = strings.ChatList_DeleteAndLeaveGroupConfirmation(peer.compactDisplayTitle)
} else if case .secretChat = chatPeer {
text = strings.ChatList_DeleteSecretChatConfirmation(peer.displayTitle(strings: strings, displayOrder: nameOrder))
} else {

View File

@ -298,6 +298,7 @@ public protocol Peer: AnyObject, PostboxCoding {
var id: PeerId { get }
var indexName: PeerIndexNameRepresentation { get }
var associatedPeerId: PeerId? { get }
var additionalAssociatedPeerId: PeerId? { get }
var associatedPeerOverridesIdentity: Bool { get }
var notificationSettingsPeerId: PeerId? { get }
var associatedMediaIds: [MediaId]? { get }
@ -307,6 +308,7 @@ public protocol Peer: AnyObject, PostboxCoding {
}
public extension Peer {
var additionalAssociatedPeerId: PeerId? { return nil }
var associatedPeerOverridesIdentity: Bool { return false }
}

View File

@ -48,15 +48,23 @@ final class MutablePeerView: MutablePostboxView {
var messageIds = Set<MessageId>()
peerIds.insert(peerId)
if let peer = getPeer(peerId), let associatedPeerId = peer.associatedPeerId {
peerIds.insert(associatedPeerId)
if peer.associatedPeerOverridesIdentity {
self.contactPeerId = associatedPeerId
self.peerIsContact = postbox.contactsTable.isContact(peerId: associatedPeerId)
if let peer = getPeer(peerId) {
if let associatedPeerId = peer.associatedPeerId {
peerIds.insert(associatedPeerId)
if peer.associatedPeerOverridesIdentity {
self.contactPeerId = associatedPeerId
self.peerIsContact = postbox.contactsTable.isContact(peerId: associatedPeerId)
} else {
self.contactPeerId = peerId
}
} else {
self.contactPeerId = peerId
}
if let additionalAssociatedPeerId = peer.additionalAssociatedPeerId {
peerIds.insert(additionalAssociatedPeerId)
}
} else {
self.contactPeerId = peerId
}
@ -96,6 +104,11 @@ final class MutablePeerView: MutablePostboxView {
} else {
self.notificationSettings = postbox.peerNotificationSettingsTable.getEffective(peerId)
}
if let peer = self.peers[peerId], let additionalAssociatedPeerId = peer.additionalAssociatedPeerId {
if let peer = getPeer(additionalAssociatedPeerId) {
self.peers[additionalAssociatedPeerId] = peer
}
}
for id in messageIds {
if let message = postbox.getMessage(id) {
self.messages[id] = message
@ -141,8 +154,13 @@ final class MutablePeerView: MutablePostboxView {
var peerIds = Set<PeerId>()
peerIds.insert(self.peerId)
if let peer = getPeer(self.peerId), let associatedPeerId = peer.associatedPeerId {
peerIds.insert(associatedPeerId)
if let peer = getPeer(self.peerId) {
if let associatedPeerId = peer.associatedPeerId {
peerIds.insert(associatedPeerId)
}
if let additionalAssociatedPeerId = peer.additionalAssociatedPeerId {
peerIds.insert(additionalAssociatedPeerId)
}
}
peerIds.formUnion(cachedData.peerIds)
@ -186,8 +204,13 @@ final class MutablePeerView: MutablePostboxView {
} else {
var peerIds = Set<PeerId>()
peerIds.insert(self.peerId)
if let peer = getPeer(self.peerId), let associatedPeerId = peer.associatedPeerId {
peerIds.insert(associatedPeerId)
if let peer = getPeer(self.peerId) {
if let associatedPeerId = peer.associatedPeerId {
peerIds.insert(associatedPeerId)
}
if let additionalAssociatedPeerId = peer.additionalAssociatedPeerId {
peerIds.insert(additionalAssociatedPeerId)
}
}
if let cachedData = self.cachedData {
peerIds.formUnion(cachedData.peerIds)

View File

@ -1285,12 +1285,16 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
}
)
|> take(1)
|> map { views -> ([EnginePeer.Id: EnginePeer?], [EnginePeer.Id: Int64]) in
var result: [EnginePeer.Id: EnginePeer?] = [:]
|> map { views -> ([EnginePeer.Id: EngineRenderedPeer?], [EnginePeer.Id: Int64]) in
var result: [EnginePeer.Id: EngineRenderedPeer?] = [:]
var requiresStars: [EnginePeer.Id: Int64] = [:]
for peerId in peerIds {
if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? PeerView, let peer = peerViewMainPeer(view) {
result[peerId] = EnginePeer(peer)
var peers: [EnginePeer.Id: EnginePeer] = [peer.id: EnginePeer(peer)]
if let channel = peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = view.peers[linkedMonoforumId] {
peers[mainChannel.id] = EnginePeer(mainChannel)
}
result[peerId] = EngineRenderedPeer(peerId: peer.id, peers: peers, associatedMedia: [:])
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
@ -1307,14 +1311,14 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
return
}
var mappedPeers: [EnginePeer] = []
var mappedPeers: [EngineRenderedPeer] = []
for peerId in peerIds {
if let maybePeer = peers[peerId], let peer = maybePeer {
mappedPeers.append(peer)
}
}
if !tryShare(self.inputFieldNode.text, mappedPeers) {
if !tryShare(self.inputFieldNode.text, mappedPeers.compactMap(\.peer)) {
return
}
@ -1328,15 +1332,15 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
}
}
private func presentPaidMessageAlertIfNeeded(peers: [EnginePeer], requiresStars: [EnginePeer.Id: Int64], completion: @escaping () -> Void) {
private func presentPaidMessageAlertIfNeeded(peers: [EngineRenderedPeer], requiresStars: [EnginePeer.Id: Int64], completion: @escaping () -> Void) {
var count: Int32 = Int32(self.messageCount)
if !self.inputFieldNode.text.isEmpty {
count += 1
}
var chargingPeers: [EnginePeer] = []
var chargingPeers: [EngineRenderedPeer] = []
var totalAmount: StarsAmount = .zero
for peer in peers {
if let stars = requiresStars[peer.id] {
if let stars = requiresStars[peer.peerId] {
chargingPeers.append(peer)
totalAmount = totalAmount + StarsAmount(value: stars, nanos: 0)
}

View File

@ -223,6 +223,10 @@ public final class TelegramChannel: Peer, Equatable {
}
}
public var additionalAssociatedPeerId: PeerId? {
self.linkedMonoforumId
}
public var indexName: PeerIndexNameRepresentation {
var addressNames = self.usernames.map { $0.username }
if addressNames.isEmpty, let username = self.username, !username.isEmpty {

View File

@ -422,7 +422,7 @@ public func chatMessagePaymentAlertController(
context: AccountContext?,
presentationData: PresentationData,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
peers: [EnginePeer],
peers: [EngineRenderedPeer],
count: Int32,
amount: StarsAmount,
totalAmount: StarsAmount?,
@ -452,10 +452,10 @@ public func chatMessagePaymentAlertController(
if peers.count == 1, let peer = peers.first {
let amountString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(amount.value))
let totalString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(amount.value * Int64(count)))
if case let .channel(channel) = peer, case .broadcast = channel.info {
text = presentationData.strings.Chat_PaidMessage_Confirm_SingleComment_Text(peer.compactDisplayTitle, amountString, totalString, messagesString).string
if case let .channel(channel) = peer.chatOrMonoforumMainPeer, case .broadcast = channel.info {
text = presentationData.strings.Chat_PaidMessage_Confirm_SingleComment_Text(EnginePeer(channel).compactDisplayTitle, amountString, totalString, messagesString).string
} else {
text = presentationData.strings.Chat_PaidMessage_Confirm_Single_Text(peer.compactDisplayTitle, amountString, totalString, messagesString).string
text = presentationData.strings.Chat_PaidMessage_Confirm_Single_Text(peer.chatOrMonoforumMainPeer?.compactDisplayTitle ?? " ", amountString, totalString, messagesString).string
}
} else {
let amount = totalAmount ?? amount

View File

@ -537,7 +537,7 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod
return
}
StoryContainerScreen.openPeerStories(context: item.context, peerId: item.data.peer.id, parentController: controller, avatarNode: itemNode.avatarNode)
StoryContainerScreen.openPeerStories(context: item.context, peerId: item.data.peer.peerId, parentController: controller, avatarNode: itemNode.avatarNode)
},
openStarsTopup: { _ in
},
@ -565,7 +565,7 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod
index = EngineChatList.Item.Index.chatList(ChatListIndex(pinningIndex: nil, messageIndex: item.data.topMessages[0].index))
messages = item.data.topMessages
} else {
index = EngineChatList.Item.Index.chatList(ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: item.data.peer.id, namespace: Namespaces.Message.Cloud, id: 1), timestamp: 0)))
index = EngineChatList.Item.Index.chatList(ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: item.data.peer.peerId, namespace: Namespaces.Message.Cloud, id: 1), timestamp: 0)))
messages = []
}
@ -577,7 +577,7 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod
index: index,
content: .peer(ChatListItemContent.PeerData(
messages: messages,
peer: EngineRenderedPeer(peer: item.data.peer),
peer: item.data.peer,
threadInfo: nil,
combinedReadState: nil,
isRemovedFromTotalUnreadCount: false,

View File

@ -314,13 +314,13 @@ final class TelegramGlobalSettings {
}
final class PeerInfoPersonalChannelData: Equatable {
let peer: EnginePeer
let peer: EngineRenderedPeer
let subscriberCount: Int?
let topMessages: [EngineMessage]
let storyStats: PeerStoryStats?
let isLoading: Bool
init(peer: EnginePeer, subscriberCount: Int?, topMessages: [EngineMessage], storyStats: PeerStoryStats?, isLoading: Bool) {
init(peer: EngineRenderedPeer, subscriberCount: Int?, topMessages: [EngineMessage], storyStats: PeerStoryStats?, isLoading: Bool) {
self.peer = peer
self.subscriberCount = subscriberCount
self.topMessages = topMessages
@ -363,6 +363,7 @@ final class PeerInfoScreenData {
let availablePanes: [PeerInfoPaneKey]
let groupsInCommon: GroupsInCommonContext?
let linkedDiscussionPeer: Peer?
let linkedMonoforumPeer: Peer?
let members: PeerInfoMembersData?
let storyListContext: StoryListContext?
let storyArchiveListContext: StoryListContext?
@ -413,6 +414,7 @@ final class PeerInfoScreenData {
availablePanes: [PeerInfoPaneKey],
groupsInCommon: GroupsInCommonContext?,
linkedDiscussionPeer: Peer?,
linkedMonoforumPeer: Peer?,
members: PeerInfoMembersData?,
storyListContext: StoryListContext?,
storyArchiveListContext: StoryListContext?,
@ -451,6 +453,7 @@ final class PeerInfoScreenData {
self.availablePanes = availablePanes
self.groupsInCommon = groupsInCommon
self.linkedDiscussionPeer = linkedDiscussionPeer
self.linkedMonoforumPeer = linkedMonoforumPeer
self.members = members
self.storyListContext = storyListContext
self.storyArchiveListContext = storyArchiveListContext
@ -663,10 +666,25 @@ public func keepPeerInfoScreenDataHot(context: AccountContext, peerId: PeerId, c
}
}
private func peerInfoPersonalChannel(context: AccountContext, peerId: EnginePeer.Id, isSettings: Bool) -> Signal<PeerInfoPersonalChannelData?, NoError> {
return context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.PersonalChannel(id: peerId)
private func peerInfoPersonalOrLinkedChannel(context: AccountContext, peerId: EnginePeer.Id, isSettings: Bool) -> Signal<PeerInfoPersonalChannelData?, NoError> {
let personalChannel: Signal<TelegramEngine.EngineData.Item.Peer.PersonalChannel.Result, NoError> = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
)
|> mapToSignal { peer -> Signal<TelegramEngine.EngineData.Item.Peer.PersonalChannel.Result, NoError> in
guard let peer else {
return .single(.known(nil))
}
if case .user = peer {
return context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.PersonalChannel(id: peerId)
)
} else if case let .channel(channel) = peer, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId {
return .single(CachedTelegramPersonalChannel.known(TelegramPersonalChannel(peerId: linkedMonoforumId, subscriberCount: nil, topMessageId: nil)))
}
return .single(.known(nil))
}
return personalChannel
|> distinctUntilChanged
|> mapToSignal { personalChannel -> Signal<PeerInfoPersonalChannelData?, NoError> in
guard case let .known(personalChannelValue) = personalChannel, let personalChannelValue else {
@ -674,15 +692,14 @@ private func peerInfoPersonalChannel(context: AccountContext, peerId: EnginePeer
}
return context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: personalChannelValue.peerId),
TelegramEngine.EngineData.Item.Peer.RenderedPeer(id: personalChannelValue.peerId),
TelegramEngine.EngineData.Item.Peer.ParticipantCount(id: personalChannelValue.peerId)
)
|> mapToSignal { channelPeer, participantCount -> Signal<PeerInfoPersonalChannelData?, NoError> in
guard let channelPeer else {
|> mapToSignal { channelRenderedPeer, participantCount -> Signal<PeerInfoPersonalChannelData?, NoError> in
guard let channelRenderedPeer, let channelPeer = channelRenderedPeer.peer else {
return .single(nil)
}
let polledChannel: Signal<Void, NoError> = Signal<Void, NoError>.single(Void())
|> then(
context.account.viewTracker.polledChannel(peerId: channelPeer.id)
@ -723,7 +740,7 @@ private func peerInfoPersonalChannel(context: AccountContext, peerId: EnginePeer
}
return PeerInfoPersonalChannelData(
peer: channelPeer,
peer: channelRenderedPeer,
subscriberCount: mappedParticipantCount,
topMessages: messages,
storyStats: storyStats,
@ -862,7 +879,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
|> distinctUntilChanged,
hasStories,
bots,
peerInfoPersonalChannel(context: context, peerId: peerId, isSettings: true),
peerInfoPersonalOrLinkedChannel(context: context, peerId: peerId, isSettings: true),
starsState
)
|> map { peerView, accountsAndPeers, accountSessions, privacySettings, sharedPreferences, notifications, stickerPacks, hasPassport, hasWatchApp, accountPreferences, suggestions, limits, hasPassword, isPowerSavingEnabled, hasStories, bots, personalChannel, starsState -> PeerInfoScreenData in
@ -925,6 +942,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
availablePanes: [],
groupsInCommon: nil,
linkedDiscussionPeer: nil,
linkedMonoforumPeer: nil,
members: nil,
storyListContext: hasStories == true ? storyListContext : nil,
storyArchiveListContext: nil,
@ -974,6 +992,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
availablePanes: [],
groupsInCommon: nil,
linkedDiscussionPeer: nil,
linkedMonoforumPeer: nil,
members: nil,
storyListContext: nil,
storyArchiveListContext: nil,
@ -1325,7 +1344,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
hasSavedMessages,
hasSavedMessageTags,
hasBotPreviewItems,
peerInfoPersonalChannel(context: context, peerId: peerId, isSettings: false),
peerInfoPersonalOrLinkedChannel(context: context, peerId: peerId, isSettings: false),
privacySettings,
starsRevenueContextAndState,
revenueContextAndState,
@ -1432,6 +1451,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
availablePanes: availablePanes ?? [],
groupsInCommon: groupsInCommon,
linkedDiscussionPeer: nil,
linkedMonoforumPeer: nil,
members: nil,
storyListContext: storyListContext,
storyArchiveListContext: storyArchiveListContext,
@ -1564,6 +1584,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
let profileGiftsContext = ProfileGiftsContext(account: context.account, peerId: peerId)
let personalChannel = peerInfoPersonalOrLinkedChannel(context: context, peerId: peerId, isSettings: false)
return combineLatest(
context.account.viewTracker.peerView(peerId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, isMyProfile: false, chatLocationContextHolder: chatLocationContextHolder),
@ -1582,9 +1604,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
isPremiumRequiredForStoryPosting,
starsRevenueContextAndState,
revenueContextAndState,
profileGiftsContext.state
profileGiftsContext.state,
personalChannel
)
|> map { peerView, availablePanes, globalNotificationSettings, status, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, accountIsPremium, recommendedChannels, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags, isPremiumRequiredForStoryPosting, starsRevenueContextAndState, revenueContextAndState, profileGiftsState -> PeerInfoScreenData in
|> map { peerView, availablePanes, globalNotificationSettings, status, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, accountIsPremium, recommendedChannels, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags, isPremiumRequiredForStoryPosting, starsRevenueContextAndState, revenueContextAndState, profileGiftsState, personalChannel -> PeerInfoScreenData in
var availablePanes = availablePanes
if let hasStories {
if hasStories {
@ -1621,6 +1644,11 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
discussionPeer = peer
}
var monoforumPeer: Peer?
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId {
monoforumPeer = peerView.peers[linkedMonoforumId]
}
var canManageInvitations = false
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let _ = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) {
canManageInvitations = true
@ -1654,6 +1682,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
availablePanes: availablePanes ?? [],
groupsInCommon: nil,
linkedDiscussionPeer: discussionPeer,
linkedMonoforumPeer: monoforumPeer,
members: nil,
storyListContext: storyListContext,
storyArchiveListContext: nil,
@ -1670,7 +1699,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
hasSavedMessageTags: hasSavedMessageTags,
hasBotPreviewItems: false,
isPremiumRequiredForStoryPosting: isPremiumRequiredForStoryPosting,
personalChannel: nil,
personalChannel: personalChannel,
starsState: nil,
starsRevenueStatsState: starsRevenueContextAndState.1,
starsRevenueStatsContext: starsRevenueContextAndState.0,
@ -1903,6 +1932,11 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] {
discussionPeer = peer
}
var monoforumPeer: Peer?
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId {
monoforumPeer = peerView.peers[linkedMonoforumId]
}
var availablePanes = availablePanes
if let membersData = membersData, case .longList = membersData {
@ -1980,6 +2014,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
availablePanes: availablePanes ?? [],
groupsInCommon: nil,
linkedDiscussionPeer: discussionPeer,
linkedMonoforumPeer: monoforumPeer,
members: membersData,
storyListContext: storyListContext,
storyArchiveListContext: nil,

View File

@ -1197,8 +1197,8 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat
}
if displayPersonalChannel {
var personalChannelTitle: String?
if let personalChannel = data.personalChannel {
personalChannelTitle = personalChannel.peer.compactDisplayTitle
if let personalChannel = data.personalChannel, let peer = personalChannel.peer.chatOrMonoforumMainPeer {
personalChannelTitle = peer.compactDisplayTitle
}
items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerPersonalChannel, label: .text(personalChannelTitle ?? presentationData.strings.Settings_PersonalChannelEmptyValue), text: presentationData.strings.Settings_PersonalChannelItem, icon: nil, action: {
@ -1288,7 +1288,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
}
if let personalChannel = data.personalChannel {
let peerId = personalChannel.peer.id
let peerId = personalChannel.peer.peerId
var label: String?
if let subscriberCount = personalChannel.subscriberCount {
label = presentationData.strings.Conversation_StatusSubscribers(Int32(subscriberCount))
@ -2024,6 +2024,7 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt
case peerDataSettings
case peerVerifySettings
case peerSettings
case linkedMonoforum
case peerAdditionalSettings
case peerActions
}
@ -2218,10 +2219,6 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemDiscussionGroup, label: .text(discussionGroupTitle), text: presentationData.strings.Channel_DiscussionGroup, icon: UIImage(bundleImageName: "Chat/Info/GroupDiscussionIcon"), action: {
interaction.editingOpenDiscussionGroupSetup()
}))
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPostSuggestionsSettings, label: .text(channel.linkedMonoforumId == nil ? presentationData.strings.PeerInfo_AllowChannelMessages_Off : presentationData.strings.PeerInfo_AllowChannelMessages_On), additionalBadgeLabel: presentationData.strings.Settings_New, text: presentationData.strings.PeerInfo_AllowChannelMessages, icon: PresentationResourcesSettings.channelMessages, action: {
interaction.editingOpenPostSuggestionsSetup()
}))
}
if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) {
@ -2266,7 +2263,7 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt
if let approximateBoostLevel = channel.approximateBoostLevel, approximateBoostLevel < 1 {
boostIcon = generateDisclosureActionBoostLevelBadgeImage(text: presentationData.strings.Channel_Info_BoostLevelPlusBadge("1").string)
} else {
let labelText = NSAttributedString(string: presentationData.strings.Settings_New, font: Font.medium(11.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor)
/*let labelText = NSAttributedString(string: presentationData.strings.Settings_New, font: Font.medium(11.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor)
let labelBounds = labelText.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil)
let labelSize = CGSize(width: ceil(labelBounds.width), height: ceil(labelBounds.height))
let badgeSize = CGSize(width: labelSize.width + 8.0, height: labelSize.height + 2.0 + 1.0)
@ -2282,7 +2279,7 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt
UIGraphicsPushContext(context)
labelText.draw(at: CGPoint(x: 4.0, y: 1.0 + UIScreenPixel))
UIGraphicsPopContext()
})
})*/
}
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerColor, label: .image(colorImage, colorImage.size), additionalBadgeIcon: boostIcon, text: presentationData.strings.Channel_Info_AppearanceItem, icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: {
interaction.editingOpenNameColorSetup()
@ -2302,6 +2299,62 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt
}))
}
if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) {
let labelString: NSAttributedString
if channel.linkedMonoforumId != nil {
if let monoforumPeer = data.linkedMonoforumPeer as? TelegramChannel {
if let sendPaidMessageStars = monoforumPeer.sendPaidMessageStars {
let formattedLabel = formatStarsAmountText(sendPaidMessageStars, dateTimeFormat: presentationData.dateTimeFormat)
let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0))
let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
let labelColor = presentationData.theme.list.itemSecondaryTextColor
let attributedString = tonAmountAttributedString(formattedLabel, integralFont: labelFont, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString
attributedString.insert(NSAttributedString(string: "*", font: labelFont, textColor: labelColor), at: 0)
if let range = attributedString.string.range(of: "*") {
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
}
labelString = attributedString
} else {
let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
let labelColor = presentationData.theme.list.itemSecondaryTextColor
labelString = NSAttributedString(string: presentationData.strings.PeerInfo_AllowChannelMessages_Free, font: labelFont, textColor: labelColor)
}
} else {
let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
let labelColor = presentationData.theme.list.itemSecondaryTextColor
labelString = NSAttributedString(string: " ", font: labelFont, textColor: labelColor)
}
} else {
let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
let labelColor = presentationData.theme.list.itemSecondaryTextColor
labelString = NSAttributedString(string: presentationData.strings.PeerInfo_AllowChannelMessages_Off, font: labelFont, textColor: labelColor)
}
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPostSuggestionsSettings, label: .attributedText(labelString), additionalBadgeLabel: presentationData.strings.Settings_New, text: presentationData.strings.PeerInfo_AllowChannelMessages, icon: PresentationResourcesSettings.channelMessages, action: {
interaction.editingOpenPostSuggestionsSetup()
}))
if let personalChannel = data.personalChannel {
let peerId = personalChannel.peer.peerId
items[.linkedMonoforum]?.append(PeerInfoScreenPersonalChannelItem(id: 1, context: context, data: personalChannel, controller: { [weak interaction] in
guard let interaction else {
return nil
}
return interaction.getController()
}, action: { [weak interaction] in
guard let interaction else {
return
}
interaction.openChat(peerId)
}))
}
}
var canEditMembers = false
if channel.hasPermission(.banMembers) && (channel.adminRights != nil || channel.flags.contains(.isCreator)) {
canEditMembers = true

View File

@ -267,14 +267,14 @@ private final class SendInviteLinkScreenComponent: Component {
}
}
private func presentPaidMessageAlertIfNeeded(peers: [EnginePeer], requiresStars: [EnginePeer.Id: StarsAmount], completion: @escaping () -> Void) {
private func presentPaidMessageAlertIfNeeded(peers: [EngineRenderedPeer], 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] {
if let amount = requiresStars[peer.peerId] {
totalAmount = totalAmount + amount
}
}
@ -968,7 +968,7 @@ private final class SendInviteLinkScreenComponent: Component {
let selectedPeers = component.peers.filter { self.selectedItems.contains($0.peer.id) }
self.presentPaidMessageAlertIfNeeded(
peers: selectedPeers.map { $0.peer },
peers: selectedPeers.map { EngineRenderedPeer(peer: $0.peer) },
requiresStars: component.sendPaidMessageStars,
completion: { [weak self] in
guard let self, let component = self.component, let controller = self.environment?.controller() else {

View File

@ -151,16 +151,10 @@ private final class SheetContent: CombinedComponent {
minAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMinAmount, nanos: 0)
maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxAmount, nanos: 0)
case let .paidMessages(_, minAmountValue, _, kind):
//TODO:localize
switch kind {
case .privacy:
titleString = "Price per Message"
case .postSuggestion:
titleString = "Price for each Suggestion"
}
amountTitle = "PRICE IN STARS"
amountPlaceholder = "Enter Price"
case let .paidMessages(_, minAmountValue, _, _):
titleString = environment.strings.Stars_SendMessage_AdjustmentTitle
amountTitle = environment.strings.Stars_SendMessage_AdjustmentSectionHeader
amountPlaceholder = environment.strings.Stars_SendMessage_AdjustmentPlaceholder
minAmount = StarsAmount(value: minAmountValue, nanos: 0)
maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0)
@ -299,9 +293,9 @@ private final class SheetContent: CombinedComponent {
if let value = state.amount?.value, value > 0 {
let fullValue: Int64 = Int64(value) * 1_000_000_000 * Int64(fractionAfterCommission) / 100
let amountValue = StarsAmount(value: fullValue / 1_000_000_000, nanos: Int32(fullValue % 1_000_000_000))
amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("You will receive **\(amountValue) Stars**.", attributes: amountMarkdownAttributes, textAlignment: .natural))
amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SendMessage_AdjustmentSectionFooterValue("\(amountValue)").string, attributes: amountMarkdownAttributes, textAlignment: .natural))
} else {
amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("You will receive **80%**.", attributes: amountMarkdownAttributes, textAlignment: .natural))
amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SendMessage_AdjustmentSectionFooterEmpty, attributes: amountMarkdownAttributes, textAlignment: .natural))
}
amountFooter = AnyComponent(MultilineTextComponent(
text: .plain(amountInfoString),
@ -376,8 +370,7 @@ private final class SheetContent: CombinedComponent {
buttonString = environment.strings.Stars_SellGift_Sell
}
} else if case .paidMessages = component.mode {
//TODO:localize
buttonString = "OK"
buttonString = environment.strings.Stars_SendMessage_AdjustmentAction
} else if let amount = state.amount {
buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))"
} else {
@ -672,6 +665,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
var text = presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount))).string
if case .starGiftResell = self.mode {
//TODO:localize
text = "You cannot sell gift for less than \(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount)))."
}

View File

@ -504,7 +504,7 @@ final class StoryItemSetContainerSendMessage {
context: component.context,
presentationData: presentationData,
updatedPresentationData: nil,
peers: [component.slice.effectivePeer],
peers: [EngineRenderedPeer(peer: component.slice.effectivePeer)],
count: 1,
amount: sendPaidMessageStars,
totalAmount: nil,

View File

@ -20,6 +20,9 @@ extension ChatControllerImpl {
completion(false)
return
}
guard let renderedPeer = self.presentationInterfaceState.renderedPeer.flatMap(EngineRenderedPeer.init) else {
return
}
if let sendPaidMessageStars = self.presentationInterfaceState.sendPaidMessageStars, self.presentationInterfaceState.interfaceState.editMessage == nil {
let totalAmount = sendPaidMessageStars.value * Int64(count)
@ -42,14 +45,16 @@ extension ChatControllerImpl {
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
}
var peer = peer
var renderedPeer = renderedPeer
if let peerDiscussionId = self.presentationInterfaceState.peerDiscussionId, let channel = self.contentData?.state.peerView?.peers[peerDiscussionId] {
peer = EnginePeer(channel)
renderedPeer = EngineRenderedPeer(peer: peer)
}
let controller = chatMessagePaymentAlertController(
context: self.context,
presentationData: presentationData,
updatedPresentationData: nil,
peers: [peer],
peers: [renderedPeer],
count: count,
amount: sendPaidMessageStars,
totalAmount: nil,

View File

@ -100,20 +100,25 @@ extension ChatControllerImpl {
let _ = (context.engine.data.get(
EngineDataMap(
peerIds.map(TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars.init(id:))
),
EngineDataList(
peerIds.map(TelegramEngine.EngineData.Item.Peer.RenderedPeer.init(id:))
)
)
|> deliverOnMainQueue).start(next: { [weak self, weak controller] sendPaidMessageStars in
|> deliverOnMainQueue).start(next: { [weak self, weak controller] sendPaidMessageStars, renderedPeers in
guard let strongSelf = self else {
return
}
let renderedPeers = renderedPeers.compactMap({ $0 })
var count: Int32 = Int32(messages.count)
if messageText.string.count > 0 {
count += 1
}
var totalAmount: StarsAmount = .zero
var chargingPeers: [EnginePeer] = []
for peer in peers {
if let maybeAmount = sendPaidMessageStars[peer.id], let amount = maybeAmount {
var chargingPeers: [EngineRenderedPeer] = []
for peer in renderedPeers {
if let maybeAmount = sendPaidMessageStars[peer.peerId], let amount = maybeAmount {
totalAmount = totalAmount + amount
chargingPeers.append(peer)
}

View File

@ -398,6 +398,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
} else {
self.loadingPlaceholderNode = nil
loadingPlaceholderNode.removeFromSupernode()
self.backgroundNode.updateIsLooping(false)
}
}
} else {
@ -1433,7 +1434,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
translationPanelNode.clipsToBounds = true
}
let height = translationPanelNode.updateLayout(width: layout.size.width, leftInset: leftPanelSize?.width ?? layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState)
let height = translationPanelNode.updateLayout(width: layout.size.width, leftInset: leftPanelSize?.width ?? layout.safeInsets.left, rightInset: layout.safeInsets.right, leftDisplayInset: leftPanelSize?.width ?? 0.0, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState)
translationPanelHeight = height
if immediatelyLayoutTranslationPanelNodeAndAnimateAppearance {
translationPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)

View File

@ -82,7 +82,7 @@ final class ChatTranslationPanelNode: ASDisplayNode {
self.layer.animateBounds(from: self.bounds, to: self.bounds.offsetBy(dx: 0.0, dy: self.bounds.size.height), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
}
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat {
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, leftDisplayInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat {
let previousIsEnabled = self.chatInterfaceState?.translationState?.isEnabled
self.chatInterfaceState = interfaceState
@ -176,7 +176,7 @@ final class ChatTranslationPanelNode: ASDisplayNode {
self.buttonTextNode.bounds = CGRect(origin: CGPoint(), size: buttonTextFrame.size)
}
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: leftDisplayInset, y: 0.0), size: CGSize(width: width - leftDisplayInset, height: UIScreenPixel)))
return panelHeight
}

View File

@ -431,16 +431,20 @@ public final class WebAppMessagePreviewScreen: ViewControllerComponentContainer
let _ = (self.context.engine.data.get(
EngineDataMap(
peers.map { TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars.init(id: $0.id) }
),
EngineDataList(
peers.map { TelegramEngine.EngineData.Item.Peer.RenderedPeer.init(id: $0.id) }
)
)
|> deliverOnMainQueue).start(next: { [weak self] sendPaidMessageStars in
|> deliverOnMainQueue).start(next: { [weak self] sendPaidMessageStars, renderedPeers in
guard let self else {
return
}
let renderedPeers = renderedPeers.compactMap({ $0 })
var totalAmount: StarsAmount = .zero
var chargingPeers: [EnginePeer] = []
for peer in peers {
if let maybeAmount = sendPaidMessageStars[peer.id], let amount = maybeAmount {
var chargingPeers: [EngineRenderedPeer] = []
for peer in renderedPeers {
if let maybeAmount = sendPaidMessageStars[peer.peerId], let amount = maybeAmount {
totalAmount = totalAmount + amount
chargingPeers.append(peer)
}