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.Enabled" = "Enabled";
"PeerInfo.OptionTopics.Disabled" = "Disabled"; "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.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.SwitchTitle" = "Allow Channel Messages";
"ChannelMessages.PriceSectionTitle" = "PRICE FOR EACH MESSAGE"; "ChannelMessages.PriceSectionTitle" = "PRICE FOR EACH MESSAGE";
@ -14391,10 +14391,21 @@ Sorry for the inconvenience.";
"Chat.InlineTopicMenu.AllTab" = "All"; "Chat.InlineTopicMenu.AllTab" = "All";
"Chat.ChannelMessagesHint" = "Send a message to the channel's admin"; "Chat.ChannelMessagesHint" = "Send a message to the channel's admin";
"Chat.ChannelMessagesHintBadge" = "NEW"; "Chat.ChannelMessagesHintBadge" = "NEW";
"Chat.ContextMenu.AuthorInfo" = "Sent by %@";
"PeerInfo.AllowChannelMessages" = "Allow Channel Messages"; "PeerInfo.AllowChannelMessages" = "Direct Messages";
"PeerInfo.AllowChannelMessages.On" = "On"; "PeerInfo.AllowChannelMessages.Free" = "Free";
"PeerInfo.AllowChannelMessages.Off" = "Off"; "PeerInfo.AllowChannelMessages.Off" = "Off";
"PeerInfo.ChannelMessages" = "Channel Messages"; "PeerInfo.ChannelMessages" = "Direct Messages";
"Chat.EmptyStateMonoforum.Text" = "Send a direct message to the administrator of **%@**."; "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 final class AvatarNode: ASDisplayNode {
public static func avatarBubbleMask(size: CGSize) -> UIImage! { public static func avatarBubbleMask(size: CGSize) -> UIImage! {
return generateImage(size, rotatedContext: { size, context in 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) { 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 path = AvatarNode.avatarBubblePath
let sx = rect.width / 60.0 let sx = rect.width / 60.0
let sy = rect.height / 60.0 let sy = rect.height / 60.274904
var transform = CGAffineTransform( var transform = CGAffineTransform(
a: sx, b: 0.0, a: sx, b: 0.0,
c: 0.0, d: -sy, c: 0.0, d: -sy,
tx: rect.minX, tx: rect.minX,
ty: rect.minY + rect.height ty: rect.minY + rect.height
) )
let transformedPath = path.copy(using: &transform)! let transformedPath = path.copy(using: &transform)!
context.addPath(transformedPath) context.addPath(transformedPath)
}
} }
public static let gradientColors: [[UIColor]] = [ public static let gradientColors: [[UIColor]] = [

View File

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

View File

@ -2980,7 +2980,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
if customMessageListData.commandPrefix != nil { if customMessageListData.commandPrefix != nil {
titleAttributedString = nil titleAttributedString = nil
} else { } 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 let textColor: UIColor
if case let .chatList(index) = item.index, index.messageIndex.id.peerId.namespace == Namespaces.Peer.SecretChat { if case let .chatList(index) = item.index, index.messageIndex.id.peerId.namespace == Namespaces.Peer.SecretChat {
textColor = theme.secretTitleColor textColor = theme.secretTitleColor
@ -2988,6 +2988,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
textColor = theme.titleColor textColor = theme.titleColor
} }
titleAttributedString = NSAttributedString(string: displayTitle, font: titleFont, textColor: textColor) 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 { } else if let threadInfo = threadInfo {

View File

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

View File

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

View File

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

View File

@ -1285,12 +1285,16 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
} }
) )
|> take(1) |> take(1)
|> map { views -> ([EnginePeer.Id: EnginePeer?], [EnginePeer.Id: Int64]) in |> map { views -> ([EnginePeer.Id: EngineRenderedPeer?], [EnginePeer.Id: Int64]) in
var result: [EnginePeer.Id: EnginePeer?] = [:] var result: [EnginePeer.Id: EngineRenderedPeer?] = [:]
var requiresStars: [EnginePeer.Id: Int64] = [:] var requiresStars: [EnginePeer.Id: Int64] = [:]
for peerId in peerIds { for peerId in peerIds {
if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? PeerView, let peer = peerViewMainPeer(view) { 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 peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView {
if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData { if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData {
requiresStars[peerId] = cachedData.sendPaidMessageStars?.value requiresStars[peerId] = cachedData.sendPaidMessageStars?.value
@ -1307,14 +1311,14 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
return return
} }
var mappedPeers: [EnginePeer] = [] var mappedPeers: [EngineRenderedPeer] = []
for peerId in peerIds { for peerId in peerIds {
if let maybePeer = peers[peerId], let peer = maybePeer { if let maybePeer = peers[peerId], let peer = maybePeer {
mappedPeers.append(peer) mappedPeers.append(peer)
} }
} }
if !tryShare(self.inputFieldNode.text, mappedPeers) { if !tryShare(self.inputFieldNode.text, mappedPeers.compactMap(\.peer)) {
return 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) var count: Int32 = Int32(self.messageCount)
if !self.inputFieldNode.text.isEmpty { if !self.inputFieldNode.text.isEmpty {
count += 1 count += 1
} }
var chargingPeers: [EnginePeer] = [] var chargingPeers: [EngineRenderedPeer] = []
var totalAmount: StarsAmount = .zero var totalAmount: StarsAmount = .zero
for peer in peers { for peer in peers {
if let stars = requiresStars[peer.id] { if let stars = requiresStars[peer.peerId] {
chargingPeers.append(peer) chargingPeers.append(peer)
totalAmount = totalAmount + StarsAmount(value: stars, nanos: 0) 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 { public var indexName: PeerIndexNameRepresentation {
var addressNames = self.usernames.map { $0.username } var addressNames = self.usernames.map { $0.username }
if addressNames.isEmpty, let username = self.username, !username.isEmpty { if addressNames.isEmpty, let username = self.username, !username.isEmpty {

View File

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

View File

@ -537,7 +537,7 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod
return 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 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)) index = EngineChatList.Item.Index.chatList(ChatListIndex(pinningIndex: nil, messageIndex: item.data.topMessages[0].index))
messages = item.data.topMessages messages = item.data.topMessages
} else { } 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 = [] messages = []
} }
@ -577,7 +577,7 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod
index: index, index: index,
content: .peer(ChatListItemContent.PeerData( content: .peer(ChatListItemContent.PeerData(
messages: messages, messages: messages,
peer: EngineRenderedPeer(peer: item.data.peer), peer: item.data.peer,
threadInfo: nil, threadInfo: nil,
combinedReadState: nil, combinedReadState: nil,
isRemovedFromTotalUnreadCount: false, isRemovedFromTotalUnreadCount: false,

View File

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

View File

@ -1197,8 +1197,8 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat
} }
if displayPersonalChannel { if displayPersonalChannel {
var personalChannelTitle: String? var personalChannelTitle: String?
if let personalChannel = data.personalChannel { if let personalChannel = data.personalChannel, let peer = personalChannel.peer.chatOrMonoforumMainPeer {
personalChannelTitle = personalChannel.peer.compactDisplayTitle personalChannelTitle = peer.compactDisplayTitle
} }
items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerPersonalChannel, label: .text(personalChannelTitle ?? presentationData.strings.Settings_PersonalChannelEmptyValue), text: presentationData.strings.Settings_PersonalChannelItem, icon: nil, action: { 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 { if let personalChannel = data.personalChannel {
let peerId = personalChannel.peer.id let peerId = personalChannel.peer.peerId
var label: String? var label: String?
if let subscriberCount = personalChannel.subscriberCount { if let subscriberCount = personalChannel.subscriberCount {
label = presentationData.strings.Conversation_StatusSubscribers(Int32(subscriberCount)) label = presentationData.strings.Conversation_StatusSubscribers(Int32(subscriberCount))
@ -2024,6 +2024,7 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt
case peerDataSettings case peerDataSettings
case peerVerifySettings case peerVerifySettings
case peerSettings case peerSettings
case linkedMonoforum
case peerAdditionalSettings case peerAdditionalSettings
case peerActions 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: { items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemDiscussionGroup, label: .text(discussionGroupTitle), text: presentationData.strings.Channel_DiscussionGroup, icon: UIImage(bundleImageName: "Chat/Info/GroupDiscussionIcon"), action: {
interaction.editingOpenDiscussionGroupSetup() 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) { 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 { if let approximateBoostLevel = channel.approximateBoostLevel, approximateBoostLevel < 1 {
boostIcon = generateDisclosureActionBoostLevelBadgeImage(text: presentationData.strings.Channel_Info_BoostLevelPlusBadge("1").string) boostIcon = generateDisclosureActionBoostLevelBadgeImage(text: presentationData.strings.Channel_Info_BoostLevelPlusBadge("1").string)
} else { } 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 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 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) 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) UIGraphicsPushContext(context)
labelText.draw(at: CGPoint(x: 4.0, y: 1.0 + UIScreenPixel)) labelText.draw(at: CGPoint(x: 4.0, y: 1.0 + UIScreenPixel))
UIGraphicsPopContext() 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: { 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() 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 var canEditMembers = false
if channel.hasPermission(.banMembers) && (channel.adminRights != nil || channel.flags.contains(.isCreator)) { if channel.hasPermission(.banMembers) && (channel.adminRights != nil || channel.flags.contains(.isCreator)) {
canEditMembers = true 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 { guard let component = self.component else {
completion() completion()
return return
} }
var totalAmount: StarsAmount = .zero var totalAmount: StarsAmount = .zero
for peer in peers { for peer in peers {
if let amount = requiresStars[peer.id] { if let amount = requiresStars[peer.peerId] {
totalAmount = totalAmount + amount totalAmount = totalAmount + amount
} }
} }
@ -968,7 +968,7 @@ private final class SendInviteLinkScreenComponent: Component {
let selectedPeers = component.peers.filter { self.selectedItems.contains($0.peer.id) } let selectedPeers = component.peers.filter { self.selectedItems.contains($0.peer.id) }
self.presentPaidMessageAlertIfNeeded( self.presentPaidMessageAlertIfNeeded(
peers: selectedPeers.map { $0.peer }, peers: selectedPeers.map { EngineRenderedPeer(peer: $0.peer) },
requiresStars: component.sendPaidMessageStars, requiresStars: component.sendPaidMessageStars,
completion: { [weak self] in completion: { [weak self] in
guard let self, let component = self.component, let controller = self.environment?.controller() else { 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) minAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMinAmount, nanos: 0)
maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxAmount, nanos: 0) maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxAmount, nanos: 0)
case let .paidMessages(_, minAmountValue, _, kind): case let .paidMessages(_, minAmountValue, _, _):
//TODO:localize titleString = environment.strings.Stars_SendMessage_AdjustmentTitle
switch kind { amountTitle = environment.strings.Stars_SendMessage_AdjustmentSectionHeader
case .privacy: amountPlaceholder = environment.strings.Stars_SendMessage_AdjustmentPlaceholder
titleString = "Price per Message"
case .postSuggestion:
titleString = "Price for each Suggestion"
}
amountTitle = "PRICE IN STARS"
amountPlaceholder = "Enter Price"
minAmount = StarsAmount(value: minAmountValue, nanos: 0) minAmount = StarsAmount(value: minAmountValue, nanos: 0)
maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, 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 { if let value = state.amount?.value, value > 0 {
let fullValue: Int64 = Int64(value) * 1_000_000_000 * Int64(fractionAfterCommission) / 100 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)) 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 { } 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( amountFooter = AnyComponent(MultilineTextComponent(
text: .plain(amountInfoString), text: .plain(amountInfoString),
@ -376,8 +370,7 @@ private final class SheetContent: CombinedComponent {
buttonString = environment.strings.Stars_SellGift_Sell buttonString = environment.strings.Stars_SellGift_Sell
} }
} else if case .paidMessages = component.mode { } else if case .paidMessages = component.mode {
//TODO:localize buttonString = environment.strings.Stars_SendMessage_AdjustmentAction
buttonString = "OK"
} else if let amount = state.amount { } else if let amount = state.amount {
buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))" buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))"
} else { } else {
@ -672,6 +665,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } 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 var text = presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount))).string
if case .starGiftResell = self.mode { if case .starGiftResell = self.mode {
//TODO:localize
text = "You cannot sell gift for less than \(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount)))." 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, context: component.context,
presentationData: presentationData, presentationData: presentationData,
updatedPresentationData: nil, updatedPresentationData: nil,
peers: [component.slice.effectivePeer], peers: [EngineRenderedPeer(peer: component.slice.effectivePeer)],
count: 1, count: 1,
amount: sendPaidMessageStars, amount: sendPaidMessageStars,
totalAmount: nil, totalAmount: nil,

View File

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

View File

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

View File

@ -398,6 +398,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
} else { } else {
self.loadingPlaceholderNode = nil self.loadingPlaceholderNode = nil
loadingPlaceholderNode.removeFromSupernode() loadingPlaceholderNode.removeFromSupernode()
self.backgroundNode.updateIsLooping(false)
} }
} }
} else { } else {
@ -1433,7 +1434,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
translationPanelNode.clipsToBounds = true 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 translationPanelHeight = height
if immediatelyLayoutTranslationPanelNodeAndAnimateAppearance { if immediatelyLayoutTranslationPanelNodeAndAnimateAppearance {
translationPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) 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) 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 let previousIsEnabled = self.chatInterfaceState?.translationState?.isEnabled
self.chatInterfaceState = interfaceState self.chatInterfaceState = interfaceState
@ -176,7 +176,7 @@ final class ChatTranslationPanelNode: ASDisplayNode {
self.buttonTextNode.bounds = CGRect(origin: CGPoint(), size: buttonTextFrame.size) 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 return panelHeight
} }

View File

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