mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-06 17:00:13 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
cbb4a890b2
@ -1038,7 +1038,7 @@ public enum StarsWithdrawalScreenSubject {
|
||||
|
||||
case withdraw(completion: (Int64) -> Void)
|
||||
case enterAmount(current: StarsAmount, minValue: StarsAmount, fractionAfterCommission: Int, kind: PaidMessageKind, completion: (Int64) -> Void)
|
||||
case postSuggestion(channel: EnginePeer, current: CurrencyAmount, timestamp: Int32?, completion: (CurrencyAmount, Int32?) -> Void)
|
||||
case postSuggestion(channel: EnginePeer, isFromAdmin: Bool, current: CurrencyAmount, timestamp: Int32?, completion: (CurrencyAmount, Int32?) -> Void)
|
||||
case postSuggestionModification(current: CurrencyAmount, timestamp: Int32?, completion: (CurrencyAmount, Int32?) -> Void)
|
||||
}
|
||||
|
||||
@ -1506,7 +1506,10 @@ public struct StarsSubscriptionConfiguration {
|
||||
paidMessagesAvailable: false,
|
||||
starGiftResaleMinAmount: 125,
|
||||
starGiftResaleMaxAmount: 3500,
|
||||
starGiftCommissionPermille: 80
|
||||
starGiftCommissionPermille: 80,
|
||||
channelMessageSuggestionCommissionPermille: 850,
|
||||
channelMessageSuggestionMaxStarsAmount: 10000,
|
||||
channelMessageSuggestionMaxTonAmount: 10000000000000
|
||||
)
|
||||
}
|
||||
|
||||
@ -1518,6 +1521,9 @@ public struct StarsSubscriptionConfiguration {
|
||||
public let starGiftResaleMinAmount: Int64
|
||||
public let starGiftResaleMaxAmount: Int64
|
||||
public let starGiftCommissionPermille: Int32
|
||||
public let channelMessageSuggestionCommissionPermille: Int32
|
||||
public let channelMessageSuggestionMaxStarsAmount: Int64
|
||||
public let channelMessageSuggestionMaxTonAmount: Int64
|
||||
|
||||
fileprivate init(
|
||||
maxFee: Int64,
|
||||
@ -1527,7 +1533,10 @@ public struct StarsSubscriptionConfiguration {
|
||||
paidMessagesAvailable: Bool,
|
||||
starGiftResaleMinAmount: Int64,
|
||||
starGiftResaleMaxAmount: Int64,
|
||||
starGiftCommissionPermille: Int32
|
||||
starGiftCommissionPermille: Int32,
|
||||
channelMessageSuggestionCommissionPermille: Int32,
|
||||
channelMessageSuggestionMaxStarsAmount: Int64,
|
||||
channelMessageSuggestionMaxTonAmount: Int64
|
||||
) {
|
||||
self.maxFee = maxFee
|
||||
self.usdWithdrawRate = usdWithdrawRate
|
||||
@ -1537,6 +1546,9 @@ public struct StarsSubscriptionConfiguration {
|
||||
self.starGiftResaleMinAmount = starGiftResaleMinAmount
|
||||
self.starGiftResaleMaxAmount = starGiftResaleMaxAmount
|
||||
self.starGiftCommissionPermille = starGiftCommissionPermille
|
||||
self.channelMessageSuggestionCommissionPermille = channelMessageSuggestionCommissionPermille
|
||||
self.channelMessageSuggestionMaxStarsAmount = channelMessageSuggestionMaxStarsAmount
|
||||
self.channelMessageSuggestionMaxTonAmount = channelMessageSuggestionMaxTonAmount
|
||||
}
|
||||
|
||||
public static func with(appConfiguration: AppConfiguration) -> StarsSubscriptionConfiguration {
|
||||
@ -1550,6 +1562,10 @@ public struct StarsSubscriptionConfiguration {
|
||||
let starGiftResaleMaxAmount = (data["stars_stargift_resale_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.starGiftResaleMaxAmount
|
||||
let starGiftCommissionPermille = (data["stars_stargift_resale_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.starGiftCommissionPermille
|
||||
|
||||
let channelMessageSuggestionCommissionPermille = (data["stars_suggested_post_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionCommissionPermille
|
||||
let channelMessageSuggestionMaxStarsAmount = (data["stars_suggested_post_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionMaxStarsAmount
|
||||
let channelMessageSuggestionMaxTonAmount = (data["ton_suggested_post_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionMaxTonAmount
|
||||
|
||||
return StarsSubscriptionConfiguration(
|
||||
maxFee: maxFee,
|
||||
usdWithdrawRate: usdWithdrawRate,
|
||||
@ -1558,7 +1574,10 @@ public struct StarsSubscriptionConfiguration {
|
||||
paidMessagesAvailable: paidMessagesAvailable,
|
||||
starGiftResaleMinAmount: starGiftResaleMinAmount,
|
||||
starGiftResaleMaxAmount: starGiftResaleMaxAmount,
|
||||
starGiftCommissionPermille: starGiftCommissionPermille
|
||||
starGiftCommissionPermille: starGiftCommissionPermille,
|
||||
channelMessageSuggestionCommissionPermille: channelMessageSuggestionCommissionPermille,
|
||||
channelMessageSuggestionMaxStarsAmount: channelMessageSuggestionMaxStarsAmount,
|
||||
channelMessageSuggestionMaxTonAmount: channelMessageSuggestionMaxTonAmount
|
||||
)
|
||||
} else {
|
||||
return .defaultValue
|
||||
|
||||
@ -3797,6 +3797,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
controller.completion = { [weak controller] title, fileId, iconColor, _ in
|
||||
controller?.isInProgress = true
|
||||
controller?.view.endEditing(true)
|
||||
|
||||
let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: iconColor, iconFileId: fileId)
|
||||
|> deliverOnMainQueue).startStandalone(next: { topicId in
|
||||
@ -7127,7 +7128,7 @@ private final class ChatListLocationContext {
|
||||
strings: presentationData.strings,
|
||||
dateTimeFormat: presentationData.dateTimeFormat,
|
||||
nameDisplayOrder: presentationData.nameDisplayOrder,
|
||||
content: .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: nil, customMessageCount: nil, isEnabled: true),
|
||||
content: .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, customSubtitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: nil, customMessageCount: nil, isEnabled: true),
|
||||
tapped: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
|
||||
@ -3220,7 +3220,14 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
if displayAsMessage {
|
||||
switch item.content {
|
||||
case let .peer(peerData):
|
||||
if let peer = peerData.messages.last?.author {
|
||||
var iconPeer: EnginePeer?
|
||||
if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatOrMonoforumMainPeer {
|
||||
iconPeer = peer
|
||||
} else {
|
||||
iconPeer = peerData.messages.last?.author
|
||||
}
|
||||
|
||||
if let peer = iconPeer {
|
||||
if case let .peer(peerData) = item.content, peerData.customMessageListData != nil {
|
||||
currentCredibilityIconContent = nil
|
||||
} else if case .savedMessagesChats = item.chatListLocation, peer.id == item.context.account.peerId {
|
||||
|
||||
@ -182,6 +182,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
||||
private let chatLocation: ChatLocation?
|
||||
private let bannedSendPhotos: (Int32, Bool)?
|
||||
private let bannedSendVideos: (Int32, Bool)?
|
||||
private let enableMultiselection: Bool
|
||||
private let canBoostToUnrestrict: Bool
|
||||
fileprivate let paidMediaAllowed: Bool
|
||||
fileprivate let subject: Subject
|
||||
@ -1845,6 +1846,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
||||
isScheduledMessages: Bool = false,
|
||||
bannedSendPhotos: (Int32, Bool)? = nil,
|
||||
bannedSendVideos: (Int32, Bool)? = nil,
|
||||
enableMultiselection: Bool = true,
|
||||
canBoostToUnrestrict: Bool = false,
|
||||
paidMediaAllowed: Bool = false,
|
||||
subject: Subject,
|
||||
@ -1868,6 +1870,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
||||
self.isScheduledMessages = isScheduledMessages
|
||||
self.bannedSendPhotos = bannedSendPhotos
|
||||
self.bannedSendVideos = bannedSendVideos
|
||||
self.enableMultiselection = enableMultiselection
|
||||
self.canBoostToUnrestrict = canBoostToUnrestrict
|
||||
self.paidMediaAllowed = paidMediaAllowed
|
||||
self.subject = subject
|
||||
@ -1877,7 +1880,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
||||
self.mainButtonAction = mainButtonAction
|
||||
self.secondaryButtonAction = secondaryButtonAction
|
||||
|
||||
let selectionContext = selectionContext ?? TGMediaSelectionContext()
|
||||
let selectionContext = selectionContext ?? TGMediaSelectionContext(groupingAllowed: false, selectionLimit: enableMultiselection ? 100 : 1)!
|
||||
|
||||
self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0)
|
||||
|
||||
@ -1924,11 +1927,12 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData))
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
|
||||
selectionContext.attemptSelectingItem = { [weak self] item in
|
||||
guard let self else {
|
||||
return false
|
||||
}
|
||||
|
||||
if let _ = item as? TGMediaPickerGalleryPhotoItem {
|
||||
if self.bannedSendPhotos != nil {
|
||||
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Chat_SendNotAllowedPhoto, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
@ -2807,7 +2811,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
||||
return
|
||||
}
|
||||
if let selectionContext = self.interaction?.selectionState, let editingContext = self.interaction?.editingState {
|
||||
selectionContext.selectionLimit = 10
|
||||
selectionContext.selectionLimit = self.enableMultiselection ? 10 : 1
|
||||
for case let item as TGMediaEditableItem in selectionContext.selectedItems() {
|
||||
editingContext.setPrice(NSNumber(value: amount), for: item)
|
||||
}
|
||||
|
||||
@ -264,10 +264,10 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
||||
return TelegramMediaAction(action: .suggestedPostApprovalStatus(status: status))
|
||||
case let .messageActionGiftTon(_, currency, amount, cryptoCurrency, cryptoAmount, transactionId):
|
||||
return TelegramMediaAction(action: .giftTon(currency: currency, amount: amount, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, transactionId: transactionId))
|
||||
case .messageActionSuggestedPostSuccess:
|
||||
return nil
|
||||
case .messageActionSuggestedPostRefund:
|
||||
return nil
|
||||
case let .messageActionSuggestedPostSuccess(price):
|
||||
return TelegramMediaAction(action: .suggestedPostSuccess(amount: CurrencyAmount(apiAmount: price)))
|
||||
case let .messageActionSuggestedPostRefund(flags):
|
||||
return TelegramMediaAction(action: .suggestedPostRefund(TelegramMediaActionType.SuggestedPostRefund(isUserInitiated: (flags & (1 << 0)) != 0)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -384,7 +384,7 @@ private func sendUploadedMessageContent(
|
||||
} else if let attribute = attribute as? PaidStarsMessageAttribute {
|
||||
allowPaidStars = attribute.stars.value
|
||||
} else if let attribute = attribute as? SuggestedPostMessageAttribute {
|
||||
suggestedPost = attribute.apiSuggestedPost()
|
||||
suggestedPost = attribute.apiSuggestedPost(fixMinTime: Int32(Date().timeIntervalSince1970 + 10))
|
||||
}
|
||||
}
|
||||
|
||||
@ -656,7 +656,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
|
||||
} else if let attribute = attribute as? PaidStarsMessageAttribute {
|
||||
allowPaidStars = attribute.stars.value
|
||||
} else if let attribute = attribute as? SuggestedPostMessageAttribute {
|
||||
suggestedPost = attribute.apiSuggestedPost()
|
||||
suggestedPost = attribute.apiSuggestedPost(fixMinTime: Int32(Date().timeIntervalSince1970 + 10))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -240,12 +240,12 @@ public final class ConferenceCallE2EContext {
|
||||
var delayPoll = true
|
||||
if let result {
|
||||
if subChainId == 0 {
|
||||
if self.e2ePoll0Offset != result.nextOffset {
|
||||
if let e2ePoll0Offset = self.e2ePoll0Offset, e2ePoll0Offset < result.nextOffset {
|
||||
self.e2ePoll0Offset = result.nextOffset
|
||||
delayPoll = false
|
||||
}
|
||||
} else if subChainId == 1 {
|
||||
if self.e2ePoll1Offset != result.nextOffset {
|
||||
if let e2ePoll1Offset = self.e2ePoll1Offset, e2ePoll1Offset < result.nextOffset {
|
||||
self.e2ePoll1Offset = result.nextOffset
|
||||
delayPoll = false
|
||||
}
|
||||
|
||||
@ -900,7 +900,7 @@ public final class PendingMessageManager {
|
||||
} else if let attribute = attribute as? PaidStarsMessageAttribute {
|
||||
allowPaidStars = attribute.stars.value * Int64(messages.count)
|
||||
} else if let attribute = attribute as? SuggestedPostMessageAttribute {
|
||||
suggestedPost = attribute.apiSuggestedPost()
|
||||
suggestedPost = attribute.apiSuggestedPost(fixMinTime: Int32(Date().timeIntervalSince1970 + 10))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1412,7 +1412,7 @@ public final class PendingMessageManager {
|
||||
} else if let attribute = attribute as? PaidStarsMessageAttribute {
|
||||
allowPaidStars = attribute.stars.value
|
||||
} else if let attribute = attribute as? SuggestedPostMessageAttribute {
|
||||
suggestedPost = attribute.apiSuggestedPost()
|
||||
suggestedPost = attribute.apiSuggestedPost(fixMinTime: Int32(Date().timeIntervalSince1970 + 10))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -70,7 +70,7 @@ extension SuggestedPostMessageAttribute {
|
||||
}
|
||||
}
|
||||
|
||||
func apiSuggestedPost() -> Api.SuggestedPost {
|
||||
func apiSuggestedPost(fixMinTime: Int32?) -> Api.SuggestedPost {
|
||||
var flags: Int32 = 0
|
||||
if let state = self.state {
|
||||
switch state {
|
||||
@ -80,7 +80,14 @@ extension SuggestedPostMessageAttribute {
|
||||
flags |= 1 << 2
|
||||
}
|
||||
}
|
||||
if self.timestamp != nil {
|
||||
var timestamp = self.timestamp
|
||||
if let timestampValue = timestamp, let fixMinTime {
|
||||
if timestampValue < fixMinTime {
|
||||
timestamp = fixMinTime
|
||||
}
|
||||
}
|
||||
|
||||
if timestamp != nil {
|
||||
flags |= 1 << 0
|
||||
}
|
||||
var price: Api.StarsAmount?
|
||||
@ -88,7 +95,7 @@ extension SuggestedPostMessageAttribute {
|
||||
flags |= 1 << 3
|
||||
price = amount.apiAmount
|
||||
}
|
||||
return .suggestedPost(flags: flags, price: price, scheduleDate: self.timestamp)
|
||||
return .suggestedPost(flags: flags, price: price, scheduleDate: timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -177,6 +177,28 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct SuggestedPostRefund: Codable, Equatable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case isUserInitiated = "iui"
|
||||
}
|
||||
|
||||
public var isUserInitiated: Bool
|
||||
|
||||
public init(isUserInitiated: Bool) {
|
||||
self.isUserInitiated = isUserInitiated
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.isUserInitiated = try container.decode(Bool.self, forKey: .isUserInitiated)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(self.isUserInitiated, forKey: .isUserInitiated)
|
||||
}
|
||||
}
|
||||
|
||||
case unknown
|
||||
case groupCreated(title: String)
|
||||
case addedMembers(peerIds: [PeerId])
|
||||
@ -230,6 +252,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
||||
case todoAppendTasks([TelegramMediaTodo.Item])
|
||||
case suggestedPostApprovalStatus(status: SuggestedPostApprovalStatus)
|
||||
case giftTon(currency: String, amount: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?)
|
||||
case suggestedPostSuccess(amount: CurrencyAmount)
|
||||
case suggestedPostRefund(SuggestedPostRefund)
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
|
||||
@ -379,6 +403,10 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
||||
self = .suggestedPostApprovalStatus(status: status ?? .rejected(reason: .generic, comment: nil))
|
||||
case 52:
|
||||
self = .giftTon(currency: decoder.decodeStringForKey("currency", orElse: ""), amount: decoder.decodeInt64ForKey("amount", orElse: 0), cryptoCurrency: decoder.decodeOptionalStringForKey("cryptoCurrency"), cryptoAmount: decoder.decodeOptionalInt64ForKey("cryptoAmount"), transactionId: decoder.decodeOptionalStringForKey("transactionId"))
|
||||
case 53:
|
||||
self = .suggestedPostSuccess(amount: decoder.decodeCodable(CurrencyAmount.self, forKey: "amt") ?? CurrencyAmount(amount: .zero, currency: .stars))
|
||||
case 54:
|
||||
self = .suggestedPostRefund(decoder.decodeCodable(SuggestedPostRefund.self, forKey: "s") ?? SuggestedPostRefund(isUserInitiated: true))
|
||||
default:
|
||||
self = .unknown
|
||||
}
|
||||
@ -808,6 +836,12 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "transactionId")
|
||||
}
|
||||
case let .suggestedPostSuccess(amount):
|
||||
encoder.encodeInt32(53, forKey: "_rawValue")
|
||||
encoder.encodeCodable(amount, forKey: "amt")
|
||||
case let .suggestedPostRefund(status):
|
||||
encoder.encodeInt32(54, forKey: "_rawValue")
|
||||
encoder.encodeCodable(status, forKey: "s")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -160,6 +160,10 @@ public extension TelegramEngine {
|
||||
public func updateStarGiftResalePrice(reference: StarGiftReference, price: Int64?) -> Signal<Never, UpdateStarGiftPriceError> {
|
||||
return _internal_updateStarGiftResalePrice(account: self.account, reference: reference, price: price)
|
||||
}
|
||||
|
||||
public func getStarsTransaction(reference: StarsTransactionReference) -> Signal<StarsContext.State.Transaction?, NoError> {
|
||||
return _internal_getStarsTransaction(accountPeerId: self.account.peerId, postbox: self.account.postbox, network: self.account.network, transactionReference: reference)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -127,6 +127,7 @@ public enum PresentationResourceKey: Int32 {
|
||||
case chatListLocationIcon
|
||||
|
||||
case chatListGeneralTopicIcon
|
||||
case chatListGeneralTopicTemplateIcon
|
||||
case chatListGeneralTopicSmallIcon
|
||||
|
||||
case searchAdIcon
|
||||
|
||||
@ -445,6 +445,12 @@ public struct PresentationResourcesChatList {
|
||||
})
|
||||
}
|
||||
|
||||
public static func generalTopicTemplateIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatListGeneralTopicTemplateIcon.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
})
|
||||
}
|
||||
|
||||
public static func statusAutoremoveIcon(_ theme: PresentationTheme, isActive: Bool) -> UIImage? {
|
||||
return theme.image(PresentationResourceParameterKey.statusAutoremoveIcon(isActive: isActive), { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: isActive ? "Chat List/StatusIconAutoremoveOn" : "Chat List/StatusIconAutoremoveOff"), color: isActive ? theme.list.itemAccentColor : theme.list.itemSecondaryTextColor)
|
||||
|
||||
@ -1259,8 +1259,24 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||
messagePeer = EnginePeer(messagePeerValue)
|
||||
} else if message.id.peerId.namespace == Namespaces.Peer.CloudChannel, let peer = message.peers[message.id.peerId] as? TelegramChannel, peer.isMonoForum {
|
||||
if let author = message.author, let threadId = message.threadId, let threadPeer = message.peers[PeerId(threadId)], author.id != threadPeer.id {
|
||||
isOutgoing = true
|
||||
messagePeer = EnginePeer(threadPeer)
|
||||
if case .channel = author {
|
||||
var isUser = true
|
||||
if let peer = message.peers[message.id.peerId] as? TelegramChannel {
|
||||
if peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
|
||||
isUser = false
|
||||
}
|
||||
}
|
||||
|
||||
if isUser {
|
||||
messagePeer = author
|
||||
} else {
|
||||
messagePeer = EnginePeer(threadPeer)
|
||||
isOutgoing = true
|
||||
}
|
||||
} else {
|
||||
isOutgoing = true
|
||||
messagePeer = EnginePeer(threadPeer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1450,6 +1466,51 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||
}
|
||||
}
|
||||
attributedString = NSAttributedString(string: string, font: titleFont, textColor: primaryTextColor)
|
||||
case let .suggestedPostSuccess(amount):
|
||||
var isUser = true
|
||||
var channelName: String = ""
|
||||
if let peer = message.peers[message.id.peerId] as? TelegramChannel {
|
||||
channelName = peer.title
|
||||
if peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
|
||||
isUser = false
|
||||
}
|
||||
}
|
||||
let _ = isUser
|
||||
|
||||
//TODO:localize
|
||||
let amountString: String
|
||||
switch amount.currency {
|
||||
case .stars:
|
||||
if amount.amount.value == 1 {
|
||||
amountString = "1 Star"
|
||||
} else {
|
||||
amountString = "\(amount.amount.value) Stars"
|
||||
}
|
||||
case .ton:
|
||||
amountString = "\(formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat)) TON"
|
||||
}
|
||||
attributedString = parseMarkdownIntoAttributedString("**\(channelName)** received **\(amountString)** for publishing this post", attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil }))
|
||||
case let .suggestedPostRefund(info):
|
||||
var isUser = true
|
||||
var channelName: String = ""
|
||||
if let peer = message.peers[message.id.peerId] as? TelegramChannel {
|
||||
channelName = peer.title
|
||||
if peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
|
||||
isUser = false
|
||||
}
|
||||
}
|
||||
let _ = channelName
|
||||
|
||||
//TODO:localize
|
||||
if info.isUserInitiated {
|
||||
if isUser {
|
||||
attributedString = NSAttributedString(string: "Suggested post was refunded because you didn't have enough funds", font: titleFont, textColor: primaryTextColor)
|
||||
} else {
|
||||
attributedString = NSAttributedString(string: "Suggested post was refunded because the user didn't have enough funds", font: titleFont, textColor: primaryTextColor)
|
||||
}
|
||||
} else {
|
||||
attributedString = NSAttributedString(string: "Suggested post was refunded because the message was deleted", font: titleFont, textColor: primaryTextColor)
|
||||
}
|
||||
case let .giftTon(currency, amount, _, _, _):
|
||||
attributedString = nil
|
||||
if !forAdditionalServiceMessage {
|
||||
|
||||
@ -228,6 +228,11 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
var isUser = true
|
||||
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
|
||||
isUser = false
|
||||
}
|
||||
|
||||
let imageSize = CGSize(width: 212.0, height: 212.0)
|
||||
|
||||
var updatedAttributedString = attributedString
|
||||
@ -281,13 +286,13 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
switch amount.currency {
|
||||
case .stars:
|
||||
if !item.message.effectivelyIncoming(item.context.account.peerId) {
|
||||
if !isUser {
|
||||
pricePart = "\n\n💰 The user have been charged \(amountString).\n\n⌛ **\(channelName)** will receive the Stars once the post has been live for 24 hours.\n\n🔄 If your remove the post before it has been live for 24 hours, the user's Stars will be refunded."
|
||||
} else {
|
||||
pricePart = "\n\n💰 You have been charged \(amountString).\n\n⌛ **\(channelName)** will receive your Stars once the post has been live for 24 hours.\n\n🔄 If **\(channelName)** removes the post before it has been live for 24 hours, your Stars will be refunded."
|
||||
}
|
||||
case .ton:
|
||||
if !item.message.effectivelyIncoming(item.context.account.peerId) {
|
||||
if !isUser {
|
||||
pricePart = "\n\n💰 The user have been charged \(amountString).\n\n⌛ **\(channelName)** will receive TON once the post has been live for 24 hours.\n\n🔄 If your remove the post before it has been live for 24 hours, the user's TON will be refunded."
|
||||
} else {
|
||||
pricePart = "\n\n💰 You have been charged \(amountString).\n\n⌛ **\(channelName)** will receive your TON once the post has been live for 24 hours.\n\n🔄 If **\(channelName)** removes the post before it has been live for 24 hours, your TON will be refunded."
|
||||
@ -298,20 +303,20 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
let rawString: String
|
||||
if let timestamp {
|
||||
if Int32(Date().timeIntervalSince1970) >= timestamp {
|
||||
if !item.message.effectivelyIncoming(item.context.account.peerId) {
|
||||
if !isUser {
|
||||
rawString = "📅 The post has been automatically published in **\(channelName)** **\(timeString)**." + pricePart
|
||||
} else {
|
||||
rawString = "📅 Your post has been automatically published in **\(channelName)** **\(timeString)**." + pricePart
|
||||
}
|
||||
} else {
|
||||
if !item.message.effectivelyIncoming(item.context.account.peerId) {
|
||||
if !isUser {
|
||||
rawString = "📅 The post will be automatically published in **\(channelName)** **\(timeString)**." + pricePart
|
||||
} else {
|
||||
rawString = "📅 Your post will be automatically published in **\(channelName)** **\(timeString)**." + pricePart
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !item.message.effectivelyIncoming(item.context.account.peerId) {
|
||||
if !isUser {
|
||||
rawString = "📅 The post has been automatically published in **\(channelName)**." + pricePart
|
||||
} else {
|
||||
rawString = "📅 Your post has been automatically published in **\(channelName)**." + pricePart
|
||||
|
||||
@ -213,10 +213,10 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
class func asyncLayout(_ maybeNode: ChatMessageActionButtonNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ bubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ message: Message, _ button: ReplyMarkupButton, _ customIcon: ChatMessageActionButtonsNode.CustomIcon?, _ constrainedWidth: CGFloat, _ position: MessageBubbleActionButtonPosition) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))) {
|
||||
class func asyncLayout(_ maybeNode: ChatMessageActionButtonNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ bubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ message: Message, _ button: ReplyMarkupButton, _ customInfo: ChatMessageActionButtonsNode.CustomInfo?, _ constrainedWidth: CGFloat, _ position: MessageBubbleActionButtonPosition) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))) {
|
||||
let titleLayout = TextNode.asyncLayout(maybeNode?.titleNode)
|
||||
|
||||
return { context, theme, bubbleCorners, strings, backgroundNode, message, button, customIcon, constrainedWidth, position in
|
||||
return { context, theme, bubbleCorners, strings, backgroundNode, message, button, customInfo, constrainedWidth, position in
|
||||
let incoming = message.effectivelyIncoming(context.account.peerId)
|
||||
let graphics = PresentationResourcesChat.additionalGraphics(theme.theme, wallpaper: theme.wallpaper, bubbleCorners: bubbleCorners)
|
||||
|
||||
@ -227,7 +227,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
||||
var isStarsPayment = false
|
||||
let iconImage: UIImage?
|
||||
var tintColor: UIColor?
|
||||
if let customIcon {
|
||||
if let customIcon = customInfo?.icon {
|
||||
switch customIcon {
|
||||
case .suggestedPostReject:
|
||||
iconImage = PresentationResourcesChat.messageButtonsPostReject(theme.theme)
|
||||
@ -314,7 +314,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
var customIconSpaceWidth: CGFloat = 0.0
|
||||
if let iconImage, customIcon != nil {
|
||||
if let iconImage, customInfo?.icon != nil {
|
||||
customIconSpaceWidth = 3.0 + iconImage.size.width
|
||||
}
|
||||
|
||||
@ -334,14 +334,13 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
node.wallpaperBackgroundNode = backgroundNode
|
||||
|
||||
node.button = button
|
||||
|
||||
switch button.action {
|
||||
case .url:
|
||||
node.longTapRecognizer?.isEnabled = true
|
||||
default:
|
||||
node.longTapRecognizer?.isEnabled = false
|
||||
case .url:
|
||||
node.longTapRecognizer?.isEnabled = true
|
||||
default:
|
||||
node.longTapRecognizer?.isEnabled = false
|
||||
}
|
||||
|
||||
//animation.animator.updateFrame(layer: node.backgroundBlurNode.layer, frame: CGRect(origin: CGPoint(), size: CGSize(width: max(0.0, width), height: 42.0)), completion: nil)
|
||||
@ -453,7 +452,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
var titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.size.width) / 2.0), y: floor((42.0 - titleSize.size.height) / 2.0) + 1.0), size: titleSize.size)
|
||||
if let image = node.iconNode?.image, customIcon != nil {
|
||||
if let image = node.iconNode?.image, customInfo?.icon != nil {
|
||||
titleFrame.origin.x = floorToScreenPixels((width - titleSize.size.width - image.size.width - 3.0) * 0.5) + 3.0 + image.size.width
|
||||
}
|
||||
titleNode.layer.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||
@ -464,7 +463,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
||||
}
|
||||
if let iconNode = node.iconNode {
|
||||
let iconFrame: CGRect
|
||||
if customIcon != nil, let image = iconNode.image {
|
||||
if customInfo?.icon != nil, let image = iconNode.image {
|
||||
iconFrame = CGRect(x: titleFrame.minX - 3.0 - image.size.width, y: titleFrame.minY + floorToScreenPixels((titleFrame.height - image.size.height) * 0.5) - 1.0, width: image.size.width, height: image.size.height)
|
||||
} else {
|
||||
iconFrame = CGRect(x: width - 16.0, y: 4.0, width: 12.0, height: 12.0)
|
||||
@ -479,6 +478,18 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
||||
node.accessibilityArea.accessibilityLabel = title
|
||||
node.accessibilityArea.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: 42.0))
|
||||
|
||||
if let buttonView = node.buttonView {
|
||||
let isEnabled = customInfo?.isEnabled ?? true
|
||||
if buttonView.isEnabled != isEnabled {
|
||||
buttonView.isEnabled = isEnabled
|
||||
|
||||
if let backgroundBlurView = node.backgroundBlurView {
|
||||
backgroundBlurView.view.alpha = isEnabled ? 1.0 : 0.55
|
||||
}
|
||||
node.backgroundContent?.alpha = isEnabled ? 1.0 : 0.55
|
||||
}
|
||||
}
|
||||
|
||||
return node
|
||||
})
|
||||
})
|
||||
@ -493,6 +504,16 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode {
|
||||
case suggestedPostEdit
|
||||
}
|
||||
|
||||
public struct CustomInfo {
|
||||
var isEnabled: Bool
|
||||
var icon: CustomIcon?
|
||||
|
||||
public init(isEnabled: Bool, icon: CustomIcon?) {
|
||||
self.isEnabled = isEnabled
|
||||
self.icon = icon
|
||||
}
|
||||
}
|
||||
|
||||
private var buttonNodes: [ChatMessageActionButtonNode] = []
|
||||
|
||||
private var buttonPressedWrapper: ((ReplyMarkupButton, Promise<Bool>) -> Void)?
|
||||
@ -529,10 +550,10 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ chatBubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ replyMarkup: ReplyMarkupMessageAttribute, _ customIcons: [MemoryBuffer: CustomIcon], _ message: Message, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)) {
|
||||
public class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ chatBubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ replyMarkup: ReplyMarkupMessageAttribute, _ customInfos: [MemoryBuffer: CustomInfo], _ message: Message, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)) {
|
||||
let currentButtonLayouts = maybeNode?.buttonNodes.map { ChatMessageActionButtonNode.asyncLayout($0) } ?? []
|
||||
|
||||
return { context, theme, chatBubbleCorners, strings, backgroundNode, replyMarkup, customIcons, message, constrainedWidth in
|
||||
return { context, theme, chatBubbleCorners, strings, backgroundNode, replyMarkup, customInfos, message, constrainedWidth in
|
||||
let buttonHeight: CGFloat = 42.0
|
||||
let buttonSpacing: CGFloat = 2.0
|
||||
|
||||
@ -548,9 +569,9 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode {
|
||||
var finalizeRowButtonLayouts: [((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))] = []
|
||||
var rowButtonIndex = 0
|
||||
for button in row.buttons {
|
||||
var customIcon: CustomIcon?
|
||||
var customInfo: CustomInfo?
|
||||
if case let .callback(_, data) = button.action {
|
||||
customIcon = customIcons[data]
|
||||
customInfo = customInfos[data]
|
||||
}
|
||||
|
||||
let buttonPosition: MessageBubbleActionButtonPosition
|
||||
@ -570,9 +591,9 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode {
|
||||
|
||||
let prepareButtonLayout: (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode)))
|
||||
if buttonIndex < currentButtonLayouts.count {
|
||||
prepareButtonLayout = currentButtonLayouts[buttonIndex](context, theme, chatBubbleCorners, strings, backgroundNode, message, button, customIcon, maximumButtonWidth, buttonPosition)
|
||||
prepareButtonLayout = currentButtonLayouts[buttonIndex](context, theme, chatBubbleCorners, strings, backgroundNode, message, button, customInfo, maximumButtonWidth, buttonPosition)
|
||||
} else {
|
||||
prepareButtonLayout = ChatMessageActionButtonNode.asyncLayout(nil)(context, theme, chatBubbleCorners, strings, backgroundNode, message, button, customIcon, maximumButtonWidth, buttonPosition)
|
||||
prepareButtonLayout = ChatMessageActionButtonNode.asyncLayout(nil)(context, theme, chatBubbleCorners, strings, backgroundNode, message, button, customInfo, maximumButtonWidth, buttonPosition)
|
||||
}
|
||||
|
||||
maximumRowButtonWidth = max(maximumRowButtonWidth, prepareButtonLayout.minimumWidth)
|
||||
|
||||
@ -1295,6 +1295,11 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
maxContentWidth = max(maxContentWidth, minWidth)
|
||||
actionButtonsFinalize = buttonsLayout
|
||||
} else if incoming, let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil {
|
||||
var canApprove = true
|
||||
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect), !mainChannel.hasPermission(.sendSomething) {
|
||||
canApprove = false
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
var buttonDeclineValue: UInt8 = 0
|
||||
let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1))
|
||||
@ -1303,10 +1308,19 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
var buttonSuggestChangesValue: UInt8 = 2
|
||||
let buttonSuggestChanges = MemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1))
|
||||
|
||||
let customIcons: [MemoryBuffer: ChatMessageActionButtonsNode.CustomIcon] = [
|
||||
buttonDecline: .suggestedPostReject,
|
||||
buttonApprove: .suggestedPostApprove,
|
||||
buttonSuggestChanges: .suggestedPostEdit
|
||||
let customInfos: [MemoryBuffer: ChatMessageActionButtonsNode.CustomInfo] = [
|
||||
buttonDecline: ChatMessageActionButtonsNode.CustomInfo(
|
||||
isEnabled: true,
|
||||
icon: .suggestedPostReject
|
||||
),
|
||||
buttonApprove: ChatMessageActionButtonsNode.CustomInfo(
|
||||
isEnabled: canApprove,
|
||||
icon: .suggestedPostApprove
|
||||
),
|
||||
buttonSuggestChanges: ChatMessageActionButtonsNode.CustomInfo(
|
||||
isEnabled: canApprove,
|
||||
icon: .suggestedPostEdit
|
||||
)
|
||||
]
|
||||
|
||||
let (minWidth, buttonsLayout) = actionButtonsLayout(
|
||||
@ -1327,7 +1341,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
],
|
||||
flags: [],
|
||||
placeholder: nil
|
||||
), customIcons, item.message, baseWidth)
|
||||
), customInfos, item.message, baseWidth)
|
||||
maxContentWidth = max(maxContentWidth, minWidth)
|
||||
actionButtonsFinalize = buttonsLayout
|
||||
}
|
||||
@ -1394,7 +1408,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
layoutSize.height += additionalTopHeight
|
||||
imageFrame.origin.y += additionalTopHeight
|
||||
|
||||
var headersOffset: CGFloat = 0.0
|
||||
var headersOffset: CGFloat = additionalTopHeight
|
||||
if let (threadInfoSize, _) = threadInfoApply {
|
||||
headersOffset += threadInfoSize.height + 10.0
|
||||
}
|
||||
@ -1611,7 +1625,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
var headersOffset: CGFloat = 0.0
|
||||
var headersOffset: CGFloat = additionalTopHeight
|
||||
if let (threadInfoSize, threadInfoApply) = threadInfoApply {
|
||||
let threadInfoNode = threadInfoApply(synchronousLoads)
|
||||
if strongSelf.threadInfoNode == nil {
|
||||
|
||||
@ -1491,7 +1491,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode),
|
||||
forwardInfoLayout: (AccountContext, ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, ChatMessageForwardInfoNode.StoryData?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
|
||||
replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode),
|
||||
actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, [MemoryBuffer: ChatMessageActionButtonsNode.CustomIcon], Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)),
|
||||
actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, [MemoryBuffer: ChatMessageActionButtonsNode.CustomInfo], Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)),
|
||||
reactionButtonsLayout: (ChatMessageReactionButtonsNode.Arguments) -> (minWidth: CGFloat, layout: (CGFloat) -> (size: CGSize, apply: (ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode)),
|
||||
unlockButtonLayout: (ChatMessageUnlockMediaNode.Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode),
|
||||
mediaInfoLayout: (ChatMessageStarsMediaInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageStarsMediaInfoNode),
|
||||
@ -2806,6 +2806,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
|
||||
lastNodeTopPosition = .None(.Both)
|
||||
} else if incoming, let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil {
|
||||
var canApprove = true
|
||||
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect), !mainChannel.hasPermission(.sendSomething) {
|
||||
canApprove = false
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
var buttonDeclineValue: UInt8 = 0
|
||||
let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1))
|
||||
@ -2814,10 +2819,19 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
var buttonSuggestChangesValue: UInt8 = 2
|
||||
let buttonSuggestChanges = MemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1))
|
||||
|
||||
let customIcons: [MemoryBuffer: ChatMessageActionButtonsNode.CustomIcon] = [
|
||||
buttonDecline: .suggestedPostReject,
|
||||
buttonApprove: .suggestedPostApprove,
|
||||
buttonSuggestChanges: .suggestedPostEdit
|
||||
let customInfos: [MemoryBuffer: ChatMessageActionButtonsNode.CustomInfo] = [
|
||||
buttonDecline: ChatMessageActionButtonsNode.CustomInfo(
|
||||
isEnabled: true,
|
||||
icon: .suggestedPostReject
|
||||
),
|
||||
buttonApprove: ChatMessageActionButtonsNode.CustomInfo(
|
||||
isEnabled: canApprove,
|
||||
icon: .suggestedPostApprove
|
||||
),
|
||||
buttonSuggestChanges: ChatMessageActionButtonsNode.CustomInfo(
|
||||
isEnabled: canApprove,
|
||||
icon: .suggestedPostEdit
|
||||
)
|
||||
]
|
||||
|
||||
let (minWidth, buttonsLayout) = actionButtonsLayout(
|
||||
@ -2838,7 +2852,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
],
|
||||
flags: [],
|
||||
placeholder: nil
|
||||
), customIcons, item.message, baseWidth)
|
||||
), customInfos, item.message, baseWidth)
|
||||
maxContentWidth = max(maxContentWidth, minWidth)
|
||||
actionButtonsFinalize = buttonsLayout
|
||||
|
||||
|
||||
@ -291,7 +291,7 @@ public func canAddMessageReactions(message: Message) -> Bool {
|
||||
return true
|
||||
} else {
|
||||
switch action.action {
|
||||
case .unknown, .groupCreated, .channelMigratedFromGroup, .groupMigratedToChannel, .historyCleared, .customText, .botDomainAccessGranted, .botAppAccessGranted, .botSentSecureValues, .phoneNumberRequest, .webViewData, .topicCreated, .attachMenuBotAllowed, .requestedPeer, .giveawayLaunched, .suggestedPostApprovalStatus:
|
||||
case .unknown, .groupCreated, .channelMigratedFromGroup, .groupMigratedToChannel, .historyCleared, .customText, .botDomainAccessGranted, .botAppAccessGranted, .botSentSecureValues, .phoneNumberRequest, .webViewData, .topicCreated, .attachMenuBotAllowed, .requestedPeer, .giveawayLaunched, .suggestedPostApprovalStatus, .suggestedPostSuccess, .suggestedPostRefund:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
|
||||
@ -856,6 +856,11 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
maxContentWidth = max(maxContentWidth, minWidth)
|
||||
actionButtonsFinalize = buttonsLayout
|
||||
} else if incoming, let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil {
|
||||
var canApprove = true
|
||||
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect), !mainChannel.hasPermission(.sendSomething) {
|
||||
canApprove = false
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
var buttonDeclineValue: UInt8 = 0
|
||||
let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1))
|
||||
@ -864,10 +869,19 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
var buttonSuggestChangesValue: UInt8 = 2
|
||||
let buttonSuggestChanges = MemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1))
|
||||
|
||||
let customIcons: [MemoryBuffer: ChatMessageActionButtonsNode.CustomIcon] = [
|
||||
buttonDecline: .suggestedPostReject,
|
||||
buttonApprove: .suggestedPostApprove,
|
||||
buttonSuggestChanges: .suggestedPostEdit
|
||||
let customInfos: [MemoryBuffer: ChatMessageActionButtonsNode.CustomInfo] = [
|
||||
buttonDecline: ChatMessageActionButtonsNode.CustomInfo(
|
||||
isEnabled: true,
|
||||
icon: .suggestedPostReject
|
||||
),
|
||||
buttonApprove: ChatMessageActionButtonsNode.CustomInfo(
|
||||
isEnabled: canApprove,
|
||||
icon: .suggestedPostApprove
|
||||
),
|
||||
buttonSuggestChanges: ChatMessageActionButtonsNode.CustomInfo(
|
||||
isEnabled: canApprove,
|
||||
icon: .suggestedPostEdit
|
||||
)
|
||||
]
|
||||
|
||||
let (minWidth, buttonsLayout) = actionButtonsLayout(
|
||||
@ -888,7 +902,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
],
|
||||
flags: [],
|
||||
placeholder: nil
|
||||
), customIcons, item.message, baseWidth)
|
||||
), customInfos, item.message, baseWidth)
|
||||
maxContentWidth = max(maxContentWidth, minWidth)
|
||||
actionButtonsFinalize = buttonsLayout
|
||||
}
|
||||
@ -977,7 +991,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
baseShareButtonFrame.origin.x = dateAndStatusFrame.maxX + 8.0
|
||||
}
|
||||
|
||||
var headersOffset: CGFloat = 0.0
|
||||
var headersOffset: CGFloat = additionalTopHeight
|
||||
if let (threadInfoSize, _) = threadInfoApply {
|
||||
headersOffset += threadInfoSize.height + 10.0
|
||||
}
|
||||
@ -1135,7 +1149,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
var headersOffset: CGFloat = 0.0
|
||||
var headersOffset: CGFloat = additionalTopHeight
|
||||
if let (threadInfoSize, threadInfoApply) = threadInfoApply {
|
||||
let threadInfoNode = threadInfoApply(synchronousLoads)
|
||||
if strongSelf.threadInfoNode == nil {
|
||||
|
||||
@ -199,10 +199,14 @@ public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode {
|
||||
contentHeight += titleLayout.0.size.height
|
||||
contentHeight += titleSpacing
|
||||
|
||||
maxContentWidth = max(maxContentWidth, priceLabelLayout.0.size.width + labelSpacing + priceValueLayout.0.size.width)
|
||||
contentHeight += priceLabelLayout.0.size.height + valuesVerticalSpacing
|
||||
var tableContentWidth: CGFloat = 0.0
|
||||
tableContentWidth = max(tableContentWidth, priceLabelLayout.0.size.width + labelSpacing + priceValueLayout.0.size.width)
|
||||
tableContentWidth = max(tableContentWidth, timeLabelLayout.0.size.width + labelSpacing + timeValueLayout.0.size.width)
|
||||
|
||||
maxContentWidth = max(maxContentWidth, timeLabelLayout.0.size.width + labelSpacing + timeValueLayout.0.size.width)
|
||||
let labelValueOffset = labelSpacing + max(priceLabelLayout.0.size.width, timeLabelLayout.0.size.width)
|
||||
|
||||
maxContentWidth = max(maxContentWidth, tableContentWidth)
|
||||
contentHeight += priceLabelLayout.0.size.height + valuesVerticalSpacing
|
||||
contentHeight += timeLabelLayout.0.size.height
|
||||
|
||||
let size = CGSize(width: insets.left + insets.right + maxContentWidth, height: insets.top + insets.bottom + contentHeight)
|
||||
@ -252,13 +256,15 @@ public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode {
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.0.size.width) * 0.5), y: insets.top), size: titleLayout.0.size)
|
||||
titleNode.frame = titleFrame
|
||||
|
||||
let priceLabelFrame = CGRect(origin: CGPoint(x: insets.left, y: titleFrame.maxY + titleSpacing), size: priceLabelLayout.0.size)
|
||||
let tableX: CGFloat = floor((size.width - tableContentWidth) * 0.5)
|
||||
|
||||
let priceLabelFrame = CGRect(origin: CGPoint(x: tableX, y: titleFrame.maxY + titleSpacing), size: priceLabelLayout.0.size)
|
||||
priceLabelNode.frame = priceLabelFrame
|
||||
priceValueNode.frame = CGRect(origin: CGPoint(x: priceLabelFrame.maxX + labelSpacing, y: priceLabelFrame.minY), size: priceValueLayout.0.size)
|
||||
priceValueNode.frame = CGRect(origin: CGPoint(x: tableX + labelValueOffset, y: priceLabelFrame.minY), size: priceValueLayout.0.size)
|
||||
|
||||
let timeLabelFrame = CGRect(origin: CGPoint(x: insets.left, y: priceLabelFrame.maxY + valuesVerticalSpacing), size: timeLabelLayout.0.size)
|
||||
let timeLabelFrame = CGRect(origin: CGPoint(x: tableX, y: priceLabelFrame.maxY + valuesVerticalSpacing), size: timeLabelLayout.0.size)
|
||||
timeLabelNode.frame = timeLabelFrame
|
||||
timeValueNode.frame = CGRect(origin: CGPoint(x: timeLabelFrame.maxX + labelSpacing, y: timeLabelFrame.minY), size: timeValueLayout.0.size)
|
||||
timeValueNode.frame = CGRect(origin: CGPoint(x: tableX + labelValueOffset, y: timeLabelFrame.minY), size: timeValueLayout.0.size)
|
||||
|
||||
return node
|
||||
})
|
||||
|
||||
@ -1015,8 +1015,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
private var channelsForPublicReaction: [EnginePeer] = []
|
||||
private var channelsForPublicReactionDisposable: Disposable?
|
||||
|
||||
private var currentSuggestPostTimestamp: Int32?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.bottomOverscrollLimit = 200.0
|
||||
|
||||
@ -1393,28 +1391,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
private func displaySuggestTimeSelectionMenu(sourceView: UIView) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
guard let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
|
||||
let mode: ChatScheduleTimeControllerMode = .suggestPost(needsTime: false)
|
||||
let theme = environment.theme
|
||||
let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: mode, style: .default, currentTime: self.currentSuggestPostTimestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self] time in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.currentSuggestPostTimestamp = time == 0 ? nil : time
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
})
|
||||
environment.controller()?.present(controller, in: .window(.root))
|
||||
}
|
||||
|
||||
func update(component: ChatSendStarsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
@ -1518,9 +1494,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
})
|
||||
case let .suggestPost(suggestPostData):
|
||||
self.currentSuggestPostTimestamp = suggestPostData.initialTimestamp
|
||||
self.amount = Amount(realValue: 50, maxRealValue: 10000, maxSliderValue: 999, isLogarithmic: true)
|
||||
}
|
||||
|
||||
if let starsContext = component.context.starsContext {
|
||||
@ -1578,8 +1551,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
switch component.initialData.subjectInitialData {
|
||||
case let .react(reactData):
|
||||
maxAmount = reactData.maxAmount
|
||||
case let .suggestPost(suggestPostData):
|
||||
maxAmount = suggestPostData.maxAmount
|
||||
}
|
||||
|
||||
self.amount = self.amount.withSliderValue(value)
|
||||
@ -1659,8 +1630,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
} else {
|
||||
self.isPastTopCutoff = nil
|
||||
}
|
||||
case .suggestPost:
|
||||
break
|
||||
}
|
||||
|
||||
let _ = self.sliderBackground.update(
|
||||
@ -1784,8 +1753,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
transition.setFrame(view: peerSelectorButtonView, frame: peerSelectorButtonFrame)
|
||||
peerSelectorButtonView.isHidden = sendAsPeers.count <= 1
|
||||
}
|
||||
case .suggestPost:
|
||||
break
|
||||
}
|
||||
|
||||
if themeUpdated {
|
||||
@ -1837,8 +1804,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
case let .react(reactData):
|
||||
let currentMyPeer = self.currentMyPeer ?? reactData.myPeer
|
||||
subtitleText = environment.strings.SendStarReactions_SubtitleFrom(currentMyPeer.compactDisplayTitle).string
|
||||
case .suggestPost:
|
||||
subtitleText = nil
|
||||
}
|
||||
|
||||
var subtitleSize: CGSize?
|
||||
@ -1857,9 +1822,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
switch component.initialData.subjectInitialData {
|
||||
case .react:
|
||||
titleText = environment.strings.SendStarReactions_Title
|
||||
case .suggestPost:
|
||||
//TODO:localize
|
||||
titleText = "Suggest a Message"
|
||||
}
|
||||
|
||||
let titleSize = title.update(
|
||||
@ -1907,9 +1869,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
} else {
|
||||
text = environment.strings.SendStarReactions_TextGeneric(reactData.peer.debugDisplayTitle).string
|
||||
}
|
||||
case let .suggestPost(suggestPostData):
|
||||
//TODO:localize
|
||||
text = "Choose how many stars you want to offer **\(suggestPostData.peer.compactDisplayTitle)** to publish this message."
|
||||
}
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)
|
||||
@ -1943,38 +1902,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
contentHeight += 22.0
|
||||
contentHeight += 2.0
|
||||
|
||||
if case .suggestPost = component.initialData.subjectInitialData {
|
||||
contentHeight += 3.0
|
||||
|
||||
let timeSelectorButtonSize = self.timeSelectorButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(TimeSelectorBadgeComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
timestamp: self.currentSuggestPostTimestamp,
|
||||
action: { [weak self] sourceView in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.displaySuggestTimeSelectionMenu(sourceView: sourceView)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
)
|
||||
let timeSelectorButtonFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - timeSelectorButtonSize.width) * 0.5), y: contentHeight), size: timeSelectorButtonSize)
|
||||
if let timeSelectorButtonView = self.timeSelectorButton.view {
|
||||
if timeSelectorButtonView.superview == nil {
|
||||
self.navigationBarContainer.addSubview(timeSelectorButtonView)
|
||||
}
|
||||
transition.setFrame(view: timeSelectorButtonView, frame: timeSelectorButtonFrame)
|
||||
}
|
||||
contentHeight += timeSelectorButtonSize.height
|
||||
|
||||
contentHeight += 32.0
|
||||
}
|
||||
|
||||
switch component.initialData.subjectInitialData {
|
||||
case let .react(reactData):
|
||||
if !reactData.topPeers.isEmpty {
|
||||
@ -2307,8 +2234,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
}
|
||||
|
||||
contentHeight += anonymousContentsSize.height + 27.0
|
||||
case .suggestPost:
|
||||
break
|
||||
}
|
||||
|
||||
initialContentHeight = contentHeight
|
||||
@ -2321,8 +2246,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
switch component.initialData.subjectInitialData {
|
||||
case .react:
|
||||
buttonString = environment.strings.SendStarReactions_SendButtonTitle("\(self.amount.realValue)").string
|
||||
case .suggestPost:
|
||||
buttonString = "Offer # \(self.amount.realValue)"
|
||||
}
|
||||
let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: .white, paragraphAlignment: .center)
|
||||
if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.cachedStarImage?.0 {
|
||||
@ -2369,9 +2292,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
switch component.initialData.subjectInitialData {
|
||||
case let .react(reactData):
|
||||
purchasePurpose = .reactions(peerId: reactData.peer.id, requiredStars: Int64(self.amount.realValue))
|
||||
case let .suggestPost(suggestPost):
|
||||
//TODO:release
|
||||
purchasePurpose = .reactions(peerId: suggestPost.peer.id, requiredStars: Int64(self.amount.realValue))
|
||||
}
|
||||
|
||||
let purchaseScreen = component.context.sharedContext.makeStarsPurchaseScreen(context: component.context, starsContext: starsContext, options: options, purpose: purchasePurpose, completion: { result in
|
||||
@ -2415,8 +2335,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
sourceView: badgeView.badgeIcon
|
||||
)
|
||||
)
|
||||
case let .suggestPost(suggestPostData):
|
||||
suggestPostData.completion(Int64(self.amount.realValue), self.currentSuggestPostTimestamp)
|
||||
}
|
||||
self.environment?.controller()?.dismiss()
|
||||
}
|
||||
@ -2560,22 +2478,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
}
|
||||
|
||||
class SuggestPost {
|
||||
let peer: EnginePeer
|
||||
let initialTimestamp: Int32?
|
||||
let maxAmount: Int
|
||||
let completion: (Int64, Int32?) -> Void
|
||||
|
||||
init(peer: EnginePeer, initialTimestamp: Int32?, maxAmount: Int, completion: @escaping (Int64, Int32?) -> Void) {
|
||||
self.peer = peer
|
||||
self.initialTimestamp = initialTimestamp
|
||||
self.maxAmount = maxAmount
|
||||
self.completion = completion
|
||||
}
|
||||
}
|
||||
|
||||
case react(React)
|
||||
case suggestPost(SuggestPost)
|
||||
}
|
||||
|
||||
public final class InitialData {
|
||||
@ -2828,46 +2731,6 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
}
|
||||
|
||||
public static func initialData(context: AccountContext, peerId: EnginePeer.Id, suggestMessageAmount: StarsAmount, completion: @escaping (Int64, Int32?) -> Void) -> Signal<InitialData?, NoError> {
|
||||
let balance: Signal<StarsAmount?, NoError>
|
||||
if let starsContext = context.starsContext {
|
||||
balance = starsContext.state
|
||||
|> map { state in
|
||||
return state?.balance
|
||||
}
|
||||
|> take(1)
|
||||
} else {
|
||||
balance = .single(nil)
|
||||
}
|
||||
|
||||
var maxAmount = 2500
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["stars_suggest_post_amount_max"] as? Double {
|
||||
maxAmount = Int(value)
|
||||
}
|
||||
|
||||
return combineLatest(
|
||||
context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
||||
),
|
||||
balance
|
||||
)
|
||||
|> map { peer, balance -> InitialData? in
|
||||
guard let peer else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return InitialData(
|
||||
subjectInitialData: .suggestPost(SubjectInitialData.SuggestPost(
|
||||
peer: peer,
|
||||
initialTimestamp: nil,
|
||||
maxAmount: maxAmount,
|
||||
completion: completion
|
||||
)),
|
||||
balance: balance
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
if !self.isDismissed {
|
||||
self.isDismissed = true
|
||||
|
||||
@ -360,6 +360,7 @@ public final class ChatSideTopicsPanel: Component {
|
||||
}
|
||||
|
||||
func update(component: VerticalItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let previousComponent = self.component
|
||||
self.component = component
|
||||
|
||||
self.tapRecognizer?.isEnabled = component.action != nil
|
||||
@ -381,12 +382,12 @@ public final class ChatSideTopicsPanel: Component {
|
||||
if case let .forum(topicId) = component.item.item.id {
|
||||
if topicId != 1, let threadData = component.item.item.threadData {
|
||||
if let fileId = threadData.info.icon, fileId != 0 {
|
||||
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: iconSize, placeholderColor: component.theme.list.mediaPlaceholderColor, themeColor: component.theme.list.itemAccentColor, loopMode: .count(0))
|
||||
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: iconSize, placeholderColor: component.theme.list.mediaPlaceholderColor, themeColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.controlColor, loopMode: .count(0))
|
||||
} else {
|
||||
avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: iconSize)
|
||||
}
|
||||
} else {
|
||||
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(component.theme), tintColor: component.theme.rootController.navigationBar.secondaryTextColor)
|
||||
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicTemplateIcon(component.theme), tintColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.controlColor)
|
||||
}
|
||||
}
|
||||
|
||||
@ -406,8 +407,14 @@ public final class ChatSideTopicsPanel: Component {
|
||||
icon = ComponentView()
|
||||
self.icon = icon
|
||||
}
|
||||
|
||||
var iconTransition = transition
|
||||
if iconTransition.animation.isImmediate, let previousComponent, previousComponent.isSelected != component.isSelected {
|
||||
iconTransition = .easeInOut(duration: 0.2)
|
||||
}
|
||||
|
||||
let _ = icon.update(
|
||||
transition: .immediate,
|
||||
transition: iconTransition,
|
||||
component: AnyComponent(avatarIconComponent),
|
||||
environment: {},
|
||||
containerSize: iconSize
|
||||
@ -813,12 +820,12 @@ public final class ChatSideTopicsPanel: Component {
|
||||
if case let .forum(topicId) = component.item.item.id {
|
||||
if topicId != 1, let threadData = component.item.item.threadData {
|
||||
if let fileId = threadData.info.icon, fileId != 0 {
|
||||
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: iconSize, placeholderColor: component.theme.list.mediaPlaceholderColor, themeColor: component.theme.list.itemAccentColor, loopMode: .count(0))
|
||||
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: iconSize, placeholderColor: component.theme.list.mediaPlaceholderColor, themeColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.controlColor, loopMode: .count(0))
|
||||
} else {
|
||||
avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: iconSize)
|
||||
}
|
||||
} else {
|
||||
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(component.theme), tintColor: component.theme.rootController.navigationBar.secondaryTextColor)
|
||||
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicTemplateIcon(component.theme), tintColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.controlColor)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1845,7 +1852,7 @@ public final class ChatSideTopicsPanel: Component {
|
||||
case .side:
|
||||
scrollSize = CGSize(width: availableSize.width, height: availableSize.height - directionContainerInset)
|
||||
scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: directionContainerInset), size: scrollSize)
|
||||
listContentInsets = UIEdgeInsets(top: 8.0, left: 0.0, bottom: 8.0, right: 0.0)
|
||||
listContentInsets = UIEdgeInsets(top: 8.0 + environment.insets.top, left: 0.0, bottom: 8.0 + environment.insets.bottom, right: 0.0)
|
||||
case .top:
|
||||
scrollSize = CGSize(width: availableSize.width - directionContainerInset, height: availableSize.height)
|
||||
scrollFrame = CGRect(origin: CGPoint(x: directionContainerInset, y: 0.0), size: scrollSize)
|
||||
|
||||
@ -92,20 +92,23 @@ public enum ChatTitleContent: Equatable {
|
||||
case replies
|
||||
}
|
||||
|
||||
case peer(peerView: PeerData, customTitle: String?, onlineMemberCount: (total: Int32?, recent: Int32?), isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?, isEnabled: Bool)
|
||||
case peer(peerView: PeerData, customTitle: String?, customSubtitle: String?, onlineMemberCount: (total: Int32?, recent: Int32?), isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?, isEnabled: Bool)
|
||||
case replyThread(type: ReplyThreadType, count: Int)
|
||||
case custom(String, String?, Bool)
|
||||
|
||||
public static func ==(lhs: ChatTitleContent, rhs: ChatTitleContent) -> Bool {
|
||||
switch lhs {
|
||||
case let .peer(peerView, customTitle, onlineMemberCount, isScheduledMessages, isMuted, customMessageCount, isEnabled):
|
||||
if case let .peer(rhsPeerView, rhsCustomTitle, rhsOnlineMemberCount, rhsIsScheduledMessages, rhsIsMuted, rhsCustomMessageCount, rhsIsEnabled) = rhs {
|
||||
case let .peer(peerView, customTitle, customSubtitle, onlineMemberCount, isScheduledMessages, isMuted, customMessageCount, isEnabled):
|
||||
if case let .peer(rhsPeerView, rhsCustomTitle, rhsCustomSubtitle, rhsOnlineMemberCount, rhsIsScheduledMessages, rhsIsMuted, rhsCustomMessageCount, rhsIsEnabled) = rhs {
|
||||
if peerView != rhsPeerView {
|
||||
return false
|
||||
}
|
||||
if customTitle != rhsCustomTitle {
|
||||
return false
|
||||
}
|
||||
if customSubtitle != rhsCustomSubtitle {
|
||||
return false
|
||||
}
|
||||
if onlineMemberCount.0 != rhsOnlineMemberCount.0 || onlineMemberCount.1 != rhsOnlineMemberCount.1 {
|
||||
return false
|
||||
}
|
||||
@ -246,7 +249,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
var titleStatusIcon: ChatTitleCredibilityIcon = .none
|
||||
var isEnabled = true
|
||||
switch titleContent {
|
||||
case let .peer(peerView, customTitle, _, isScheduledMessages, isMuted, _, isEnabledValue):
|
||||
case let .peer(peerView, customTitle, _, _, isScheduledMessages, isMuted, _, isEnabledValue):
|
||||
if peerView.peerId.isReplies {
|
||||
let typeText: String = self.strings.DialogList_Replies
|
||||
segments = [.text(0, NSAttributedString(string: typeText, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
|
||||
@ -260,7 +263,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
isEnabled = false
|
||||
} else {
|
||||
if let peer = peerView.peer {
|
||||
if let customTitle = customTitle {
|
||||
if let customTitle {
|
||||
segments = [.text(0, NSAttributedString(string: customTitle, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
|
||||
} else if peerView.peerId == self.context.account.peerId {
|
||||
if peerView.isSavedMessages {
|
||||
@ -444,8 +447,8 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
|
||||
var enableAnimation = false
|
||||
switch titleContent {
|
||||
case let .peer(_, customTitle, _, _, _, _, _):
|
||||
if case let .peer(_, previousCustomTitle, _, _, _, _, _) = oldValue {
|
||||
case let .peer(_, customTitle, _, _, _, _, _, _):
|
||||
if case let .peer(_, previousCustomTitle, _, _, _, _, _, _) = oldValue {
|
||||
if customTitle != previousCustomTitle {
|
||||
enableAnimation = false
|
||||
}
|
||||
@ -471,7 +474,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
var inputActivitiesAllowed = true
|
||||
if let titleContent = self.titleContent {
|
||||
switch titleContent {
|
||||
case let .peer(peerView, _, _, isScheduledMessages, _, _, _):
|
||||
case let .peer(peerView, _, _, _, isScheduledMessages, _, _, _):
|
||||
if let peer = peerView.peer {
|
||||
if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isRepliesOrVerificationCodes {
|
||||
inputActivitiesAllowed = false
|
||||
@ -572,8 +575,11 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
} else {
|
||||
if let titleContent = self.titleContent {
|
||||
switch titleContent {
|
||||
case let .peer(peerView, customTitle, onlineMemberCount, isScheduledMessages, _, customMessageCount, _):
|
||||
if let customMessageCount = customMessageCount, customMessageCount != 0 {
|
||||
case let .peer(peerView, customTitle, customSubtitle, onlineMemberCount, isScheduledMessages, _, customMessageCount, _):
|
||||
if let customSubtitle {
|
||||
let string = NSAttributedString(string: customSubtitle, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
} else if let customMessageCount = customMessageCount, customMessageCount != 0 {
|
||||
let string = NSAttributedString(string: self.strings.Conversation_Messages(Int32(customMessageCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
} else if let peer = peerView.peer {
|
||||
|
||||
@ -233,7 +233,10 @@ public final class EmojiStatusComponent: Component {
|
||||
private weak var state: EmptyComponentState?
|
||||
private var component: EmojiStatusComponent?
|
||||
private var starsLayer: StarsEffectLayer?
|
||||
private var iconView: UIImageView?
|
||||
|
||||
private var iconLayer: SimpleLayer?
|
||||
private var iconLayerImage: UIImage?
|
||||
|
||||
private var animationLayer: InlineStickerItemLayer?
|
||||
private var lottieAnimationView: AnimationView?
|
||||
private let hierarchyTrackingLayer: HierarchyTrackingLayer
|
||||
@ -323,7 +326,7 @@ public final class EmojiStatusComponent: Component {
|
||||
case let .premium(color):
|
||||
iconTintColor = color
|
||||
|
||||
if case .premium = self.component?.content, let image = self.iconView?.image {
|
||||
if case .premium = self.component?.content, let image = self.iconLayerImage {
|
||||
iconImage = image
|
||||
} else {
|
||||
if let sourceImage = UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon") {
|
||||
@ -454,7 +457,7 @@ public final class EmojiStatusComponent: Component {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
iconImage = self.iconView?.image
|
||||
iconImage = self.iconLayerImage
|
||||
if case let .animation(animationContent, size, placeholderColor, themeColor, loopMode) = component.content {
|
||||
emojiFileId = animationContent.fileId.id
|
||||
emojiPlaceholderColor = placeholderColor
|
||||
@ -471,31 +474,28 @@ public final class EmojiStatusComponent: Component {
|
||||
var size = CGSize()
|
||||
|
||||
if let iconImage = iconImage {
|
||||
let iconView: UIImageView
|
||||
if let current = self.iconView {
|
||||
iconView = current
|
||||
let iconLayer: SimpleLayer
|
||||
if let current = self.iconLayer {
|
||||
iconLayer = current
|
||||
} else {
|
||||
iconView = UIImageView()
|
||||
self.iconView = iconView
|
||||
self.addSubview(iconView)
|
||||
iconLayer = SimpleLayer()
|
||||
self.iconLayer = iconLayer
|
||||
self.layer.addSublayer(iconLayer)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
iconView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
iconView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
|
||||
iconLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
iconLayer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
|
||||
}
|
||||
}
|
||||
if iconView.image !== iconImage {
|
||||
iconView.image = iconImage
|
||||
if self.iconLayerImage !== iconImage {
|
||||
self.iconLayerImage = iconImage
|
||||
iconLayer.contents = iconImage.cgImage
|
||||
}
|
||||
|
||||
if let iconTintColor {
|
||||
if transition.animation.isImmediate {
|
||||
iconView.tintColor = iconTintColor
|
||||
} else {
|
||||
transition.setTintColor(view: iconView, color: iconTintColor)
|
||||
}
|
||||
transition.setTintColor(layer: iconLayer, color: iconTintColor)
|
||||
} else {
|
||||
iconView.tintColor = nil
|
||||
iconLayer.layerTintColor = nil
|
||||
}
|
||||
|
||||
var useFit = false
|
||||
@ -509,24 +509,25 @@ public final class EmojiStatusComponent: Component {
|
||||
}
|
||||
if useFit {
|
||||
size = CGSize(width: iconImage.size.width, height: availableSize.height)
|
||||
iconView.frame = CGRect(origin: CGPoint(x: floor((size.width - iconImage.size.width) / 2.0), y: floor((size.height - iconImage.size.height) / 2.0)), size: iconImage.size)
|
||||
iconLayer.frame = CGRect(origin: CGPoint(x: floor((size.width - iconImage.size.width) / 2.0), y: floor((size.height - iconImage.size.height) / 2.0)), size: iconImage.size)
|
||||
} else {
|
||||
size = iconImage.size.aspectFilled(availableSize)
|
||||
iconView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
iconLayer.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
} else {
|
||||
if let iconView = self.iconView {
|
||||
self.iconView = nil
|
||||
if let iconLayer = self.iconLayer {
|
||||
self.iconLayer = nil
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
iconView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak iconView] _ in
|
||||
iconView?.removeFromSuperview()
|
||||
iconLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak iconLayer] _ in
|
||||
iconLayer?.removeFromSuperlayer()
|
||||
})
|
||||
iconView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||
iconLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||
} else {
|
||||
iconView.removeFromSuperview()
|
||||
iconLayer.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
self.iconLayerImage = nil
|
||||
}
|
||||
|
||||
let emojiFileUpdated = component.emojiFileUpdated
|
||||
@ -607,44 +608,6 @@ public final class EmojiStatusComponent: Component {
|
||||
|
||||
animationLayer.frame = CGRect(origin: CGPoint(), size: size)
|
||||
animationLayer.isVisibleForAnimations = component.isVisibleForAnimations
|
||||
/*} else {
|
||||
if self.emojiFileDataPathDisposable == nil {
|
||||
let account = component.context.account
|
||||
self.emojiFileDataPathDisposable = (Signal<AnimationFileProperties?, NoError> { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
let _ = (account.postbox.mediaBox.resourceData(emojiFile.resource)
|
||||
|> take(1)).start(next: { firstAttemptData in
|
||||
if firstAttemptData.complete {
|
||||
subscriber.putNext(AnimationFileProperties.load(from: firstAttemptData.path))
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
let fetchDisposable = freeMediaFileInteractiveFetched(account: account, fileReference: .standalone(media: emojiFile)).start()
|
||||
let dataDisposable = account.postbox.mediaBox.resourceData(emojiFile.resource).start(next: { data in
|
||||
if data.complete {
|
||||
subscriber.putNext(AnimationFileProperties.load(from: data.path))
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
})
|
||||
|
||||
disposable.set(ActionDisposable {
|
||||
fetchDisposable.dispose()
|
||||
dataDisposable.dispose()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return disposable
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] properties in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.emojiFileDataProperties = properties
|
||||
strongSelf.state?.updated(transition: transition)
|
||||
})
|
||||
}
|
||||
}*/
|
||||
} else {
|
||||
if self.emojiFileDisposable == nil {
|
||||
self.emojiFileDisposable = (component.resolveInlineStickers([emojiFileId])
|
||||
|
||||
@ -172,24 +172,27 @@ final class ForumSettingsScreenComponent: Component {
|
||||
if let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController {
|
||||
var viewControllers = navigationController.viewControllers
|
||||
|
||||
if self.isOn && self.mode == .list {
|
||||
for i in 0 ..< viewControllers.count {
|
||||
if let chatController = viewControllers[i] as? ChatController, chatController.chatLocation.peerId == component.peerId {
|
||||
let chatListController = component.context.sharedContext.makeChatListController(context: component.context, location: .forum(peerId: component.peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false)
|
||||
viewControllers[i] = chatListController
|
||||
}
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
if case .legacyGroup = peer {
|
||||
} else {
|
||||
for i in (0 ..< viewControllers.count).reversed() {
|
||||
if let chatListController = viewControllers[i] as? ChatListController, chatListController.location == .forum(peerId: component.peerId) {
|
||||
viewControllers.remove(at: i)
|
||||
if self.isOn && self.mode == .list {
|
||||
for i in 0 ..< viewControllers.count {
|
||||
if let chatController = viewControllers[i] as? ChatController, chatController.chatLocation.peerId == component.peerId {
|
||||
let chatListController = component.context.sharedContext.makeChatListController(context: component.context, location: .forum(peerId: component.peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false)
|
||||
viewControllers[i] = chatListController
|
||||
}
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
} else {
|
||||
for i in (0 ..< viewControllers.count).reversed() {
|
||||
if let chatListController = viewControllers[i] as? ChatListController, chatListController.location == .forum(peerId: component.peerId) {
|
||||
viewControllers.remove(at: i)
|
||||
}
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
|
||||
if let baseController = navigationController as? TelegramRootControllerInterface, let chatListController = baseController.getChatsController() as? ChatListController {
|
||||
chatListController.resetForumStackIfOpen()
|
||||
}
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
|
||||
if let baseController = navigationController as? TelegramRootControllerInterface, let chatListController = baseController.getChatsController() as? ChatListController {
|
||||
chatListController.resetForumStackIfOpen()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -232,6 +235,34 @@ final class ForumSettingsScreenComponent: Component {
|
||||
}
|
||||
if let resultPeerId {
|
||||
self.peerIdPromise.set(resultPeerId)
|
||||
|
||||
let _ = component.context.engine.peers.setChannelForumMode(id: resultPeerId, isForum: true, displayForumAsTabs: self.mode == .tabs).startStandalone()
|
||||
|
||||
if let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController {
|
||||
var viewControllers = navigationController.viewControllers
|
||||
if self.mode == .list {
|
||||
for i in 0 ..< viewControllers.count {
|
||||
if let chatController = viewControllers[i] as? ChatController, chatController.chatLocation.peerId == component.peerId {
|
||||
let chatListController = component.context.sharedContext.makeChatListController(context: component.context, location: .forum(peerId: resultPeerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false)
|
||||
viewControllers[i] = chatListController
|
||||
}
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
} else {
|
||||
for i in (0 ..< viewControllers.count).reversed() {
|
||||
if let chatListController = viewControllers[i] as? ChatListController, chatListController.location == .forum(peerId: component.peerId) {
|
||||
viewControllers.remove(at: i)
|
||||
} else if let peerInfoScreen = viewControllers[i] as? PeerInfoScreen, peerInfoScreen.peerId == component.peerId {
|
||||
viewControllers.remove(at: i)
|
||||
}
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
|
||||
if let baseController = navigationController as? TelegramRootControllerInterface, let chatListController = baseController.getChatsController() as? ChatListController {
|
||||
chatListController.resetForumStackIfOpen()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.isOn = false
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.2))
|
||||
|
||||
@ -2290,8 +2290,12 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
|
||||
if hasVoiceChat || canStartVoiceChat {
|
||||
result.append(.voiceChat)
|
||||
}
|
||||
if case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), !channel.hasPermission(.manageDirect) {
|
||||
result.append(.message)
|
||||
}
|
||||
result.append(.mute)
|
||||
if hasDiscussion {
|
||||
if case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), !channel.hasPermission(.manageDirect) {
|
||||
} else if hasDiscussion {
|
||||
result.append(.discussion)
|
||||
}
|
||||
result.append(.search)
|
||||
|
||||
@ -1959,7 +1959,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
}))
|
||||
}
|
||||
|
||||
if let personalChannel = data.personalChannel {
|
||||
if channel.hasPermission(.manageDirect), let personalChannel = data.personalChannel {
|
||||
let peerId = personalChannel.peer.peerId
|
||||
items[.channelMonoforum]?.append(PeerInfoScreenPersonalChannelItem(id: ItemPeerPersonalChannel, context: context, data: personalChannel, controller: { [weak interaction] in
|
||||
guard let interaction else {
|
||||
@ -5998,18 +5998,32 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
switch key {
|
||||
case .message:
|
||||
if let navigationController = controller.navigationController as? NavigationController, let peer = self.data?.peer {
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in
|
||||
if let strongSelf = self, strongSelf.nearbyPeerDistance != nil {
|
||||
var viewControllers = navigationController.viewControllers
|
||||
viewControllers = viewControllers.filter { controller in
|
||||
if controller is PeerInfoScreen {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
if let channel = peer as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId {
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
|
||||
guard let peer = await self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: linkedMonoforumId)).get() else {
|
||||
return
|
||||
}
|
||||
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default))
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in
|
||||
if let strongSelf = self, strongSelf.nearbyPeerDistance != nil {
|
||||
var viewControllers = navigationController.viewControllers
|
||||
viewControllers = viewControllers.filter { controller in
|
||||
if controller is PeerInfoScreen {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
case .discussion:
|
||||
if let cachedData = self.data?.cachedData as? CachedChannelData, case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId {
|
||||
|
||||
@ -29,7 +29,7 @@ final class PostSuggestionsSettingsScreenComponent: Component {
|
||||
|
||||
let context: AccountContext
|
||||
let usdWithdrawRate: Int64
|
||||
let paidMessageCommissionPermille: Int
|
||||
let channelMessageSuggestionCommissionPermille: Int
|
||||
let peer: EnginePeer?
|
||||
let initialPrice: StarsAmount?
|
||||
let completion: () -> Void
|
||||
@ -37,14 +37,14 @@ final class PostSuggestionsSettingsScreenComponent: Component {
|
||||
init(
|
||||
context: AccountContext,
|
||||
usdWithdrawRate: Int64,
|
||||
paidMessageCommissionPermille: Int,
|
||||
channelMessageSuggestionCommissionPermille: Int,
|
||||
peer: EnginePeer?,
|
||||
initialPrice: StarsAmount?,
|
||||
completion: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.usdWithdrawRate = usdWithdrawRate
|
||||
self.paidMessageCommissionPermille = paidMessageCommissionPermille
|
||||
self.channelMessageSuggestionCommissionPermille = channelMessageSuggestionCommissionPermille
|
||||
self.peer = peer
|
||||
self.initialPrice = initialPrice
|
||||
self.completion = completion
|
||||
@ -373,7 +373,7 @@ final class PostSuggestionsSettingsScreenComponent: Component {
|
||||
}
|
||||
|
||||
let currentAmount: StarsAmount = StarsAmount(value: Int64(self.starCount), nanos: 0)
|
||||
let starsScreen = component.context.sharedContext.makeStarsWithdrawalScreen(context: component.context, subject: .enterAmount(current: currentAmount, minValue: StarsAmount(value: 0, nanos: 0), fractionAfterCommission: component.paidMessageCommissionPermille / 10, kind: .postSuggestion, completion: { [weak self] amount in
|
||||
let starsScreen = component.context.sharedContext.makeStarsWithdrawalScreen(context: component.context, subject: .enterAmount(current: currentAmount, minValue: StarsAmount(value: 0, nanos: 0), fractionAfterCommission: component.channelMessageSuggestionCommissionPermille / 10, kind: .postSuggestion, completion: { [weak self] amount in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
@ -404,7 +404,7 @@ final class PostSuggestionsSettingsScreenComponent: Component {
|
||||
)),
|
||||
footer: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: environment.strings.ChannelMessages_PriceSectionFooterValue("\(component.paidMessageCommissionPermille / 10)").string,
|
||||
string: environment.strings.ChannelMessages_PriceSectionFooterValue("\(component.channelMessageSuggestionCommissionPermille / 10)").string,
|
||||
font: Font.regular(13.0),
|
||||
textColor: self.starCount == 0 ? .clear : environment.theme.list.freeTextColor
|
||||
)),
|
||||
@ -503,7 +503,7 @@ public final class PostSuggestionsSettingsScreen: ViewControllerComponentContain
|
||||
super.init(context: context, component: PostSuggestionsSettingsScreenComponent(
|
||||
context: context,
|
||||
usdWithdrawRate: configuration.usdWithdrawRate,
|
||||
paidMessageCommissionPermille: Int(configuration.paidMessageCommissionPermille),
|
||||
channelMessageSuggestionCommissionPermille: Int(configuration.channelMessageSuggestionCommissionPermille),
|
||||
peer: peer,
|
||||
initialPrice: initialPrice,
|
||||
completion: completion
|
||||
|
||||
@ -24,6 +24,7 @@ import UndoUI
|
||||
import ListActionItemComponent
|
||||
import ChatScheduleTimeController
|
||||
import TabSelectorComponent
|
||||
import PresentationDataUtils
|
||||
|
||||
private let amountTag = GenericComponentViewTag()
|
||||
|
||||
@ -83,8 +84,15 @@ private final class SheetContent: CombinedComponent {
|
||||
let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0
|
||||
|
||||
if case let .suggestedPost(mode, _, _, _) = component.mode {
|
||||
var displayBalance = false
|
||||
switch mode {
|
||||
case .sender:
|
||||
case let .sender(_, isFromAdmin):
|
||||
displayBalance = !isFromAdmin
|
||||
case .admin:
|
||||
break
|
||||
}
|
||||
|
||||
if displayBalance {
|
||||
let balance = balance.update(
|
||||
component: BalanceComponent(
|
||||
context: component.context,
|
||||
@ -102,8 +110,6 @@ private final class SheetContent: CombinedComponent {
|
||||
.anchorPoint(CGPoint(x: 1.0, y: 0.0))
|
||||
.position(CGPoint(x: balanceFrame.maxX, y: balanceFrame.minY))
|
||||
)
|
||||
case .admin:
|
||||
break
|
||||
}
|
||||
|
||||
let closeButton = closeButton.update(
|
||||
@ -214,14 +220,14 @@ private final class SheetContent: CombinedComponent {
|
||||
switch state.currency {
|
||||
case .stars:
|
||||
amountTitle = "ENTER A PRICE IN STARS"
|
||||
maxAmount = StarsAmount(value: resaleConfiguration.channelMessageSuggestionMaxStarsAmount, nanos: 0)
|
||||
case .ton:
|
||||
amountTitle = "ENTER A PRICE IN TON"
|
||||
maxAmount = StarsAmount(value: resaleConfiguration.channelMessageSuggestionMaxTonAmount, nanos: 0)
|
||||
}
|
||||
amountPlaceholder = "Price"
|
||||
|
||||
minAmount = StarsAmount(value: 0, nanos: 0)
|
||||
//TODO:release
|
||||
maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0)
|
||||
}
|
||||
|
||||
let title = title.update(
|
||||
@ -289,69 +295,90 @@ private final class SheetContent: CombinedComponent {
|
||||
)
|
||||
}
|
||||
|
||||
var tonBalanceValue: StarsAmount = .zero
|
||||
if let tonBalance = state.tonBalance {
|
||||
tonBalanceValue = tonBalance
|
||||
}
|
||||
|
||||
if case let .suggestedPost(mode, _, _, _) = component.mode {
|
||||
//TODO:localize
|
||||
let selectedId: AnyHashable = state.currency == .stars ? AnyHashable(0 as Int) : AnyHashable(1 as Int)
|
||||
let starsTitle: String
|
||||
let tonTitle: String
|
||||
var displayCurrencySelector = false
|
||||
switch mode {
|
||||
case .sender:
|
||||
starsTitle = "Offer Stars"
|
||||
tonTitle = "Offer TON"
|
||||
case let .sender(_, isFromAdmin):
|
||||
if isFromAdmin {
|
||||
displayCurrencySelector = true
|
||||
} else {
|
||||
if state.currency == .ton || tonBalanceValue > StarsAmount.zero {
|
||||
displayCurrencySelector = true
|
||||
}
|
||||
}
|
||||
case .admin:
|
||||
starsTitle = "Request Stars"
|
||||
tonTitle = "Request TON"
|
||||
displayCurrencySelector = true
|
||||
}
|
||||
|
||||
let currencyToggle = currencyToggle.update(
|
||||
component: TabSelectorComponent(
|
||||
colors: TabSelectorComponent.Colors(
|
||||
foreground: theme.list.itemSecondaryTextColor,
|
||||
selection: theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15),
|
||||
simple: true
|
||||
),
|
||||
customLayout: TabSelectorComponent.CustomLayout(
|
||||
font: Font.medium(14.0),
|
||||
spacing: 10.0
|
||||
),
|
||||
items: [
|
||||
TabSelectorComponent.Item(
|
||||
id: AnyHashable(0),
|
||||
content: .component(AnyComponent(CurrencyTabItemComponent(icon: .stars, title: starsTitle, theme: theme)))
|
||||
if displayCurrencySelector {
|
||||
//TODO:localize
|
||||
let selectedId: AnyHashable = state.currency == .stars ? AnyHashable(0 as Int) : AnyHashable(1 as Int)
|
||||
let starsTitle: String
|
||||
let tonTitle: String
|
||||
switch mode {
|
||||
case .sender:
|
||||
starsTitle = "Offer Stars"
|
||||
tonTitle = "Offer TON"
|
||||
case .admin:
|
||||
starsTitle = "Request Stars"
|
||||
tonTitle = "Request TON"
|
||||
}
|
||||
|
||||
let currencyToggle = currencyToggle.update(
|
||||
component: TabSelectorComponent(
|
||||
colors: TabSelectorComponent.Colors(
|
||||
foreground: theme.list.itemSecondaryTextColor,
|
||||
selection: theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15),
|
||||
simple: true
|
||||
),
|
||||
TabSelectorComponent.Item(
|
||||
id: AnyHashable(1),
|
||||
content: .component(AnyComponent(CurrencyTabItemComponent(icon: .ton, title: tonTitle, theme: theme)))
|
||||
)
|
||||
],
|
||||
selectedId: selectedId,
|
||||
setSelectedId: { [weak state] id in
|
||||
guard let state else {
|
||||
return
|
||||
customLayout: TabSelectorComponent.CustomLayout(
|
||||
font: Font.medium(14.0),
|
||||
spacing: 10.0
|
||||
),
|
||||
items: [
|
||||
TabSelectorComponent.Item(
|
||||
id: AnyHashable(0),
|
||||
content: .component(AnyComponent(CurrencyTabItemComponent(icon: .stars, title: starsTitle, theme: theme)))
|
||||
),
|
||||
TabSelectorComponent.Item(
|
||||
id: AnyHashable(1),
|
||||
content: .component(AnyComponent(CurrencyTabItemComponent(icon: .ton, title: tonTitle, theme: theme)))
|
||||
)
|
||||
],
|
||||
selectedId: selectedId,
|
||||
setSelectedId: { [weak state] id in
|
||||
guard let state else {
|
||||
return
|
||||
}
|
||||
|
||||
let currency: CurrencyAmount.Currency
|
||||
if id == AnyHashable(0) {
|
||||
currency = .stars
|
||||
} else {
|
||||
currency = .ton
|
||||
}
|
||||
if state.currency != currency {
|
||||
state.currency = currency
|
||||
state.amount = nil
|
||||
}
|
||||
state.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
|
||||
let currency: CurrencyAmount.Currency
|
||||
if id == AnyHashable(0) {
|
||||
currency = .stars
|
||||
} else {
|
||||
currency = .ton
|
||||
}
|
||||
if state.currency != currency {
|
||||
state.currency = currency
|
||||
state.amount = nil
|
||||
}
|
||||
state.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 100.0),
|
||||
transition: context.transition
|
||||
)
|
||||
contentSize.height -= 17.0
|
||||
let currencyToggleFrame = CGRect(origin: CGPoint(x: floor((context.availableSize.width - currencyToggle.size.width) * 0.5), y: contentSize.height), size: currencyToggle.size)
|
||||
context.add(currencyToggle
|
||||
.position(currencyToggle.size.centered(in: currencyToggleFrame).center))
|
||||
|
||||
contentSize.height += currencyToggle.size.height + 29.0
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 100.0),
|
||||
transition: context.transition
|
||||
)
|
||||
contentSize.height -= 17.0
|
||||
let currencyToggleFrame = CGRect(origin: CGPoint(x: floor((context.availableSize.width - currencyToggle.size.width) * 0.5), y: contentSize.height), size: currencyToggle.size)
|
||||
context.add(currencyToggle
|
||||
.position(currencyToggle.size.centered(in: currencyToggleFrame).center))
|
||||
|
||||
contentSize.height += currencyToggle.size.height + 29.0
|
||||
}
|
||||
}
|
||||
|
||||
let amountFont = Font.regular(13.0)
|
||||
@ -432,14 +459,23 @@ private final class SheetContent: CombinedComponent {
|
||||
))
|
||||
case let .suggestedPost(mode, _, _, _):
|
||||
switch mode {
|
||||
case let .sender(channel):
|
||||
case let .sender(channel, isFromAdmin):
|
||||
//TODO:localize
|
||||
let string: String
|
||||
switch state.currency {
|
||||
case .stars:
|
||||
string = "Choose how many Stars you want to offer \(channel.compactDisplayTitle) to publish this message."
|
||||
case .ton:
|
||||
string = "Choose how many TON you want to offer \(channel.compactDisplayTitle) to publish this message."
|
||||
if isFromAdmin {
|
||||
switch state.currency {
|
||||
case .stars:
|
||||
string = "Choose how many Stars you charge for the message."
|
||||
case .ton:
|
||||
string = "Choose how many TON you charge for the message."
|
||||
}
|
||||
} else {
|
||||
switch state.currency {
|
||||
case .stars:
|
||||
string = "Choose how many Stars you want to offer \(channel.compactDisplayTitle) to publish this message."
|
||||
case .ton:
|
||||
string = "Choose how many TON you want to offer \(channel.compactDisplayTitle) to publish this message."
|
||||
}
|
||||
}
|
||||
let amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(string, attributes: amountMarkdownAttributes, textAlignment: .natural))
|
||||
amountFooter = AnyComponent(MultilineTextComponent(
|
||||
@ -487,7 +523,7 @@ private final class SheetContent: CombinedComponent {
|
||||
accentColor: theme.list.itemAccentColor,
|
||||
value: state.amount?.value,
|
||||
minValue: minAmount?.value,
|
||||
maxValue: state.currency == .ton ? nil : maxAmount?.value,
|
||||
maxValue: maxAmount?.value,
|
||||
placeholderText: amountPlaceholder,
|
||||
labelText: amountLabel,
|
||||
currency: state.currency,
|
||||
@ -595,7 +631,9 @@ private final class SheetContent: CombinedComponent {
|
||||
let component = state.component
|
||||
|
||||
let theme = environment.theme
|
||||
let controller = ChatScheduleTimeController(context: state.context, updatedPresentationData: (state.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), state.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: .suggestPost(needsTime: false), style: .default, currentTime: state.timestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak state] time in
|
||||
|
||||
let minimalTime: Int32 = Int32(Date().timeIntervalSince1970) + 5 * 60 + 10
|
||||
let controller = ChatScheduleTimeController(context: state.context, updatedPresentationData: (state.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), state.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: .suggestPost(needsTime: false), style: .default, currentTime: state.timestamp, minimalTime: minimalTime, dismissByTapOutside: true, completion: { [weak state] time in
|
||||
guard let state else {
|
||||
return
|
||||
}
|
||||
@ -637,7 +675,7 @@ private final class SheetContent: CombinedComponent {
|
||||
//TODO:localize
|
||||
switch mode {
|
||||
case .sender:
|
||||
if let amount = state.amount {
|
||||
if let amount = state.amount, amount != .zero {
|
||||
let currencySymbol: String
|
||||
let currencyAmount: String
|
||||
switch state.currency {
|
||||
@ -729,6 +767,36 @@ private final class SheetContent: CombinedComponent {
|
||||
case let .paidMessages(_, _, _, _, completion):
|
||||
completion(amount.value)
|
||||
case let .suggestedPost(_, _, _, completion):
|
||||
switch state.currency {
|
||||
case .stars:
|
||||
if let balance = state.starsBalance, amount > balance {
|
||||
guard let starsContext = state.context.starsContext else {
|
||||
return
|
||||
}
|
||||
let _ = (state.context.engine.payments.starsTopUpOptions()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak controller, weak state] options in
|
||||
guard let controller, let state else {
|
||||
return
|
||||
}
|
||||
let purchaseController = state.context.sharedContext.makeStarsPurchaseScreen(context: state.context, starsContext: starsContext, options: options, purpose: .generic, completion: { _ in
|
||||
})
|
||||
controller.push(purchaseController)
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
case .ton:
|
||||
if let balance = state.tonBalance, amount > balance {
|
||||
//TODO:localize
|
||||
let presentationData = state.context.sharedContext.currentPresentationData.with { $0 }
|
||||
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: "Not enough TON", actions: [
|
||||
TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})
|
||||
]), in: .window(.root))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
completion(CurrencyAmount(amount: amount, currency: state.currency), state.timestamp)
|
||||
}
|
||||
|
||||
@ -971,7 +1039,7 @@ private final class StarsWithdrawSheetComponent: CombinedComponent {
|
||||
public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
||||
public enum Mode {
|
||||
public enum SuggestedPostMode {
|
||||
case sender(channel: EnginePeer)
|
||||
case sender(channel: EnginePeer, isFromAdmin: Bool)
|
||||
case admin
|
||||
}
|
||||
|
||||
@ -1057,322 +1125,6 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
}
|
||||
|
||||
private let invalidAmountCharacters = CharacterSet.decimalDigits.inverted
|
||||
|
||||
private final class AmountFieldTonFormatter: NSObject, UITextFieldDelegate {
|
||||
private struct Representation {
|
||||
private let format: CurrencyFormat
|
||||
private var caretIndex: Int = 0
|
||||
private var wholePart: [Int] = []
|
||||
private var decimalPart: [Int] = []
|
||||
|
||||
init(string: String, format: CurrencyFormat) {
|
||||
self.format = format
|
||||
|
||||
var isDecimalPart = false
|
||||
for c in string {
|
||||
if c.isNumber {
|
||||
if let value = Int(String(c)) {
|
||||
if isDecimalPart {
|
||||
self.decimalPart.append(value)
|
||||
} else {
|
||||
self.wholePart.append(value)
|
||||
}
|
||||
}
|
||||
} else if String(c) == format.decimalSeparator {
|
||||
isDecimalPart = true
|
||||
}
|
||||
}
|
||||
|
||||
while self.wholePart.count > 1 {
|
||||
if self.wholePart[0] != 0 {
|
||||
break
|
||||
} else {
|
||||
self.wholePart.removeFirst()
|
||||
}
|
||||
}
|
||||
if self.wholePart.isEmpty {
|
||||
self.wholePart = [0]
|
||||
}
|
||||
|
||||
while self.decimalPart.count > 1 {
|
||||
if self.decimalPart[self.decimalPart.count - 1] != 0 {
|
||||
break
|
||||
} else {
|
||||
self.decimalPart.removeLast()
|
||||
}
|
||||
}
|
||||
while self.decimalPart.count < format.decimalDigits {
|
||||
self.decimalPart.append(0)
|
||||
}
|
||||
|
||||
self.caretIndex = self.wholePart.count
|
||||
}
|
||||
|
||||
var minCaretIndex: Int {
|
||||
for i in 0 ..< self.wholePart.count {
|
||||
if self.wholePart[i] != 0 {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return self.wholePart.count
|
||||
}
|
||||
|
||||
mutating func moveCaret(offset: Int) {
|
||||
self.caretIndex = max(self.minCaretIndex, min(self.caretIndex + offset, self.wholePart.count + self.decimalPart.count))
|
||||
}
|
||||
|
||||
mutating func normalize() {
|
||||
while self.wholePart.count > 1 {
|
||||
if self.wholePart[0] != 0 {
|
||||
break
|
||||
} else {
|
||||
self.wholePart.removeFirst()
|
||||
self.moveCaret(offset: -1)
|
||||
}
|
||||
}
|
||||
if self.wholePart.isEmpty {
|
||||
self.wholePart = [0]
|
||||
}
|
||||
|
||||
while self.decimalPart.count < format.decimalDigits {
|
||||
self.decimalPart.append(0)
|
||||
}
|
||||
while self.decimalPart.count > format.decimalDigits {
|
||||
self.decimalPart.removeLast()
|
||||
}
|
||||
|
||||
self.caretIndex = max(self.minCaretIndex, min(self.caretIndex, self.wholePart.count + self.decimalPart.count))
|
||||
}
|
||||
|
||||
mutating func backspace() {
|
||||
if self.caretIndex > self.wholePart.count {
|
||||
let decimalIndex = self.caretIndex - self.wholePart.count
|
||||
if decimalIndex > 0 {
|
||||
self.decimalPart.remove(at: decimalIndex - 1)
|
||||
|
||||
self.moveCaret(offset: -1)
|
||||
self.normalize()
|
||||
}
|
||||
} else {
|
||||
if self.caretIndex > 0 {
|
||||
self.wholePart.remove(at: self.caretIndex - 1)
|
||||
|
||||
self.moveCaret(offset: -1)
|
||||
self.normalize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutating func insert(letter: String) {
|
||||
if letter == "." || letter == "," {
|
||||
if self.caretIndex == self.wholePart.count {
|
||||
return
|
||||
} else if self.caretIndex < self.wholePart.count {
|
||||
for i in (self.caretIndex ..< self.wholePart.count).reversed() {
|
||||
self.decimalPart.insert(self.wholePart[i], at: 0)
|
||||
self.wholePart.remove(at: i)
|
||||
}
|
||||
}
|
||||
|
||||
self.normalize()
|
||||
} else if letter.count == 1 && letter[letter.startIndex].isNumber {
|
||||
if let value = Int(letter) {
|
||||
if self.caretIndex <= self.wholePart.count {
|
||||
self.wholePart.insert(value, at: self.caretIndex)
|
||||
} else {
|
||||
let decimalIndex = self.caretIndex - self.wholePart.count
|
||||
self.decimalPart.insert(value, at: decimalIndex)
|
||||
}
|
||||
self.moveCaret(offset: 1)
|
||||
self.normalize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var string: String {
|
||||
var result = ""
|
||||
|
||||
for digit in self.wholePart {
|
||||
result.append("\(digit)")
|
||||
}
|
||||
result.append(self.format.decimalSeparator)
|
||||
for digit in self.decimalPart {
|
||||
result.append("\(digit)")
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
var stringCaretIndex: Int {
|
||||
var logicalIndex = 0
|
||||
var resolvedIndex = 0
|
||||
|
||||
if logicalIndex == self.caretIndex {
|
||||
return resolvedIndex
|
||||
}
|
||||
|
||||
for _ in self.wholePart {
|
||||
logicalIndex += 1
|
||||
resolvedIndex += 1
|
||||
|
||||
if logicalIndex == self.caretIndex {
|
||||
return resolvedIndex
|
||||
}
|
||||
}
|
||||
|
||||
resolvedIndex += 1
|
||||
|
||||
for _ in self.decimalPart {
|
||||
logicalIndex += 1
|
||||
resolvedIndex += 1
|
||||
|
||||
if logicalIndex == self.caretIndex {
|
||||
return resolvedIndex
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedIndex
|
||||
}
|
||||
|
||||
var numericalValue: Int64 {
|
||||
var result: Int64 = 0
|
||||
|
||||
for digit in self.wholePart {
|
||||
result *= 10
|
||||
result += Int64(digit)
|
||||
}
|
||||
for digit in self.decimalPart {
|
||||
result *= 10
|
||||
result += Int64(digit)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private let format: CurrencyFormat
|
||||
private let currency: String
|
||||
private let maxNumericalValue: Int64
|
||||
private let updated: (Int64) -> Void
|
||||
private let isEmptyUpdated: (Bool) -> Void
|
||||
private let focusUpdated: (Bool) -> Void
|
||||
|
||||
private var representation: Representation
|
||||
|
||||
private var previousResolvedCaretIndex: Int = 0
|
||||
private var ignoreTextSelection: Bool = false
|
||||
private var enableTextSelectionProcessing: Bool = false
|
||||
|
||||
init?(textField: UITextField, currency: String, maxNumericalValue: Int64, initialValue: String, updated: @escaping (Int64) -> Void, isEmptyUpdated: @escaping (Bool) -> Void, focusUpdated: @escaping (Bool) -> Void) {
|
||||
guard let format = CurrencyFormat(currency: currency) else {
|
||||
return nil
|
||||
}
|
||||
self.format = format
|
||||
self.currency = currency
|
||||
self.maxNumericalValue = maxNumericalValue
|
||||
self.updated = updated
|
||||
self.isEmptyUpdated = isEmptyUpdated
|
||||
self.focusUpdated = focusUpdated
|
||||
|
||||
self.representation = Representation(string: initialValue, format: format)
|
||||
|
||||
super.init()
|
||||
|
||||
textField.text = self.representation.string
|
||||
self.previousResolvedCaretIndex = self.representation.stringCaretIndex
|
||||
|
||||
self.isEmptyUpdated(false)
|
||||
}
|
||||
|
||||
func reset(textField: UITextField, initialValue: String) {
|
||||
self.representation = Representation(string: initialValue, format: self.format)
|
||||
self.resetFromRepresentation(textField: textField, notifyUpdated: false)
|
||||
}
|
||||
|
||||
private func resetFromRepresentation(textField: UITextField, notifyUpdated: Bool) {
|
||||
self.ignoreTextSelection = true
|
||||
|
||||
if self.representation.numericalValue > self.maxNumericalValue {
|
||||
self.representation = Representation(string: formatCurrencyAmountCustom(self.maxNumericalValue, currency: self.currency).0, format: self.format)
|
||||
}
|
||||
|
||||
textField.text = self.representation.string
|
||||
self.previousResolvedCaretIndex = self.representation.stringCaretIndex
|
||||
|
||||
if self.enableTextSelectionProcessing {
|
||||
let stringCaretIndex = self.representation.stringCaretIndex
|
||||
if let caretPosition = textField.position(from: textField.beginningOfDocument, offset: stringCaretIndex) {
|
||||
textField.selectedTextRange = textField.textRange(from: caretPosition, to: caretPosition)
|
||||
}
|
||||
}
|
||||
self.ignoreTextSelection = false
|
||||
|
||||
if notifyUpdated {
|
||||
self.updated(self.representation.numericalValue)
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
if string.count == 1 {
|
||||
self.representation.insert(letter: string)
|
||||
self.resetFromRepresentation(textField: textField, notifyUpdated: true)
|
||||
} else if string.count == 0 {
|
||||
self.representation.backspace()
|
||||
self.resetFromRepresentation(textField: textField, notifyUpdated: true)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@objc public func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
self.enableTextSelectionProcessing = true
|
||||
self.focusUpdated(true)
|
||||
|
||||
let stringCaretIndex = self.representation.stringCaretIndex
|
||||
self.previousResolvedCaretIndex = stringCaretIndex
|
||||
if let caretPosition = textField.position(from: textField.beginningOfDocument, offset: stringCaretIndex) {
|
||||
self.ignoreTextSelection = true
|
||||
textField.selectedTextRange = textField.textRange(from: caretPosition, to: caretPosition)
|
||||
DispatchQueue.main.async {
|
||||
textField.selectedTextRange = textField.textRange(from: caretPosition, to: caretPosition)
|
||||
self.ignoreTextSelection = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func textFieldDidChangeSelection(_ textField: UITextField) {
|
||||
if self.ignoreTextSelection {
|
||||
return
|
||||
}
|
||||
if !self.enableTextSelectionProcessing {
|
||||
return
|
||||
}
|
||||
|
||||
if let selectedTextRange = textField.selectedTextRange {
|
||||
let index = textField.offset(from: textField.beginningOfDocument, to: selectedTextRange.end)
|
||||
if self.previousResolvedCaretIndex != index {
|
||||
self.representation.moveCaret(offset: self.previousResolvedCaretIndex < index ? 1 : -1)
|
||||
|
||||
let stringCaretIndex = self.representation.stringCaretIndex
|
||||
self.previousResolvedCaretIndex = stringCaretIndex
|
||||
if let caretPosition = textField.position(from: textField.beginningOfDocument, offset: stringCaretIndex) {
|
||||
textField.selectedTextRange = textField.textRange(from: caretPosition, to: caretPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
self.enableTextSelectionProcessing = false
|
||||
self.focusUpdated(false)
|
||||
}
|
||||
}
|
||||
|
||||
private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate {
|
||||
private let currency: CurrencyAmount.Currency
|
||||
private let dateTimeFormat: PresentationDateTimeFormat
|
||||
@ -1429,9 +1181,14 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate {
|
||||
// Convert and combine
|
||||
if let whole = Int64(wholeSlice),
|
||||
let frac = Int64(fractionStr) {
|
||||
|
||||
let whole = min(whole, Int64.max / scale)
|
||||
|
||||
amount = whole * scale + frac
|
||||
}
|
||||
} else if let whole = Int64(text) { // string had no dot at all
|
||||
let whole = min(whole, Int64.max / scale)
|
||||
|
||||
amount = whole * scale
|
||||
}
|
||||
}
|
||||
@ -1479,8 +1236,21 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate {
|
||||
return false
|
||||
}
|
||||
case .ton:
|
||||
var fixedText = false
|
||||
if let index = newText.firstIndex(of: ".") {
|
||||
let fractionalString = newText[newText.index(after: index)...]
|
||||
if fractionalString.count > 2 {
|
||||
newText = String(newText[newText.startIndex ..< newText.index(index, offsetBy: 3)])
|
||||
fixedText = true
|
||||
}
|
||||
}
|
||||
|
||||
if (newText == "0" && !acceptZero) || (newText.count > 1 && newText.hasPrefix("0") && !newText.hasPrefix("0.")) {
|
||||
newText.removeFirst()
|
||||
fixedText = true
|
||||
}
|
||||
|
||||
if fixedText {
|
||||
textField.text = newText
|
||||
self.onTextChanged(text: newText)
|
||||
return false
|
||||
@ -1493,7 +1263,7 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate {
|
||||
case .stars:
|
||||
textField.text = "\(self.maxValue)"
|
||||
case .ton:
|
||||
textField.text = "\(formatTonAmountText(self.maxValue, dateTimeFormat: self.dateTimeFormat))"
|
||||
textField.text = "\(formatTonAmountText(self.maxValue, dateTimeFormat: PresentationDateTimeFormat(timeFormat: self.dateTimeFormat.timeFormat, dateFormat: self.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: ".", groupingSeparator: "")))"
|
||||
}
|
||||
self.onTextChanged(text: self.textField.text ?? "")
|
||||
self.animateError()
|
||||
@ -1648,13 +1418,13 @@ private final class AmountFieldComponent: Component {
|
||||
|
||||
self.textField.textColor = component.textColor
|
||||
if self.component?.currency != component.currency {
|
||||
if let value = component.value {
|
||||
if let value = component.value, value != .zero {
|
||||
var text = ""
|
||||
switch component.currency {
|
||||
case .stars:
|
||||
text = "\(value)"
|
||||
case .ton:
|
||||
text = "\(formatTonAmountText(value, dateTimeFormat: component.dateTimeFormat))"
|
||||
text = "\(formatTonAmountText(value, dateTimeFormat: PresentationDateTimeFormat(timeFormat: component.dateTimeFormat.timeFormat, dateFormat: component.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: ".", groupingSeparator: "")))"
|
||||
}
|
||||
self.textField.text = text
|
||||
} else {
|
||||
@ -1705,7 +1475,6 @@ private final class AmountFieldComponent: Component {
|
||||
}
|
||||
self.tonFormatter = nil
|
||||
self.textField.delegate = self.starsFormatter
|
||||
self.textField.text = ""
|
||||
case .ton:
|
||||
self.textField.keyboardType = .numbersAndPunctuation
|
||||
if self.tonFormatter == nil {
|
||||
@ -1714,7 +1483,7 @@ private final class AmountFieldComponent: Component {
|
||||
currency: component.currency,
|
||||
dateTimeFormat: component.dateTimeFormat,
|
||||
minValue: component.minValue ?? 0,
|
||||
maxValue: component.maxValue ?? Int64.max,
|
||||
maxValue: component.maxValue ?? 10000000,
|
||||
updated: { [weak self] value in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
|
||||
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DeletePaid.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DeletePaid.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "trash_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DeletePaid.imageset/trash_24.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DeletePaid.imageset/trash_24.pdf
vendored
Normal file
Binary file not shown.
@ -20,6 +20,11 @@ extension ChatControllerImpl {
|
||||
}
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] settings in
|
||||
if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
|
||||
var enableMultiselection = true
|
||||
if strongSelf.presentationInterfaceState.interfaceState.postSuggestionState != nil {
|
||||
enableMultiselection = false
|
||||
}
|
||||
|
||||
strongSelf.chatDisplayNode.dismissInput()
|
||||
let controller = mediaPasteboardScreen(
|
||||
context: strongSelf.context,
|
||||
@ -28,7 +33,7 @@ extension ChatControllerImpl {
|
||||
subjects: subjects,
|
||||
presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendPhotos, bannedSendVideos, present in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] fromGallery, signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in
|
||||
strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, enableMultiselection: enableMultiselection, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] fromGallery, signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in
|
||||
self?.enqueueMediaMessages(fromGallery: fromGallery, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1011,8 +1011,15 @@ extension ChatControllerImpl {
|
||||
})
|
||||
})
|
||||
} else {
|
||||
var isFromAdmin = false
|
||||
if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum {
|
||||
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
|
||||
isFromAdmin = true
|
||||
}
|
||||
}
|
||||
subject = .postSuggestion(
|
||||
channel: .channel(channel),
|
||||
isFromAdmin: isFromAdmin,
|
||||
current: postSuggestionState.price ?? CurrencyAmount(amount: .zero, currency: .stars),
|
||||
timestamp: postSuggestionState.timestamp,
|
||||
completion: { [weak self] price, timestamp in
|
||||
|
||||
@ -967,7 +967,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return false
|
||||
}
|
||||
switch action.action {
|
||||
case .pinnedMessageUpdated, .gameScore, .setSameChatWallpaper, .giveawayResults, .customText, .todoCompletions, .todoAppendTasks:
|
||||
case .pinnedMessageUpdated, .gameScore, .setSameChatWallpaper, .giveawayResults, .customText, .todoCompletions, .todoAppendTasks, .suggestedPostRefund, .suggestedPostSuccess, .suggestedPostApprovalStatus:
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
var todoTaskId: Int32?
|
||||
@ -1173,6 +1173,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let controller = self.context.sharedContext.makeStarsGiftScreen(context: self.context, message: EngineMessage(message))
|
||||
self.push(controller)
|
||||
return true
|
||||
case let .giftTon(_, _, _, _, transactionId):
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self, let transactionId, let peerId = self.chatLocation.peerId else {
|
||||
return
|
||||
}
|
||||
let transactionData = await self.context.engine.payments.getStarsTransaction(reference: StarsTransactionReference(peerId: self.context.account.peerId, ton: true, id: transactionId, isRefund: false)).get()
|
||||
let peer = await self.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
||||
).get()
|
||||
if let transactionData, let peer {
|
||||
self.push(self.context.sharedContext.makeStarsTransactionScreen(context: self.context, transaction: transactionData, peer: peer))
|
||||
}
|
||||
}
|
||||
case let .giftCode(slug, _, _, _, _, _, _, _, _, _, _):
|
||||
self.openResolved(result: .premiumGiftCode(slug: slug), sourceMessageId: message.id, progress: params.progress)
|
||||
return true
|
||||
@ -1244,6 +1257,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
case .boostsApplied:
|
||||
self.controllerInteraction?.openGroupBoostInfo(nil, 0)
|
||||
return true
|
||||
case .paidMessagesPriceEdited:
|
||||
self.interfaceInteraction?.openMonoforum()
|
||||
return true
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
@ -389,6 +389,47 @@ extension ChatControllerImpl {
|
||||
return
|
||||
}
|
||||
|
||||
if let message = messages.values.compactMap({ $0 }).first(where: { message in message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) }), let attribute = message.attributes.first(where: { $0 is PublishedSuggestedPostMessageAttribute }) as? PublishedSuggestedPostMessageAttribute {
|
||||
let commit = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
//TODO:localize
|
||||
let titleString: String
|
||||
let textString: String
|
||||
switch attribute.currency {
|
||||
case .stars:
|
||||
titleString = "Stars Will Be Lost"
|
||||
textString = "You won't receive **Stars** for this post if you delete it now. The post must remain visible for at least **24 hours** after publication."
|
||||
case .ton:
|
||||
titleString = "TON Will Be Lost"
|
||||
textString = "You won't receive **TON** for this post if you delete it now. The post must remain visible for at least **24 hours** after publication."
|
||||
}
|
||||
self.present(standardTextAlertController(
|
||||
theme: AlertControllerTheme(presentationData: self.presentationData),
|
||||
title: titleString,
|
||||
text: textString,
|
||||
actions: [
|
||||
TextAlertAction(type: .destructiveAction, title: "Delete Anyway", action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.beginDeleteMessagesWithUndo(messageIds: messageIds, type: .forEveryone)
|
||||
}),
|
||||
TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {})
|
||||
],
|
||||
actionLayout: .vertical,
|
||||
parseMarkdown: true
|
||||
), in: .window(.root))
|
||||
}
|
||||
if let contextController {
|
||||
contextController.dismiss(completion: commit)
|
||||
} else {
|
||||
commit()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||
var items: [ActionSheetItem] = []
|
||||
var personalPeerName: String?
|
||||
|
||||
@ -548,12 +548,20 @@ extension ChatControllerImpl {
|
||||
strongSelf.state.chatTitleContent = .custom(strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false)
|
||||
} else if let channel = peer as? TelegramChannel, channel.isMonoForum {
|
||||
if let linkedMonoforumId = channel.linkedMonoforumId, let mainPeer = peerView.peers[linkedMonoforumId] {
|
||||
strongSelf.state.chatTitleContent = .custom(mainPeer.debugDisplayTitle, strings.Chat_Monoforum_Subtitle, true)
|
||||
strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(
|
||||
peerId: mainPeer.id,
|
||||
peer: mainPeer,
|
||||
isContact: false,
|
||||
isSavedMessages: false,
|
||||
notificationSettings: nil,
|
||||
peerPresences: [:],
|
||||
cachedData: nil
|
||||
), customTitle: nil, customSubtitle: strings.Chat_Monoforum_Subtitle, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: nil, customMessageCount: nil, isEnabled: true)
|
||||
} else {
|
||||
strongSelf.state.chatTitleContent = .custom(channel.debugDisplayTitle, nil, true)
|
||||
}
|
||||
} else {
|
||||
strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo)
|
||||
strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, customSubtitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo)
|
||||
|
||||
let imageOverride: AvatarNodeImageOverride?
|
||||
if context.account.peerId == peer.id {
|
||||
@ -1447,7 +1455,7 @@ extension ChatControllerImpl {
|
||||
customMessageCount = savedMessagesPeer?.messageCount ?? 0
|
||||
}
|
||||
|
||||
strongSelf.state.chatTitleContent = .peer(peerView: mappedPeerData, customTitle: nil, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: false, customMessageCount: customMessageCount, isEnabled: true)
|
||||
strongSelf.state.chatTitleContent = .peer(peerView: mappedPeerData, customTitle: nil, customSubtitle: nil, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: false, customMessageCount: customMessageCount, isEnabled: true)
|
||||
|
||||
strongSelf.state.peerView = peerView
|
||||
|
||||
@ -1534,7 +1542,7 @@ extension ChatControllerImpl {
|
||||
}
|
||||
|
||||
if let threadInfo = messageAndTopic.threadData?.info {
|
||||
strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount, isEnabled: true)
|
||||
strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: threadInfo.title, customSubtitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount, isEnabled: true)
|
||||
|
||||
let avatarContent: EmojiStatusComponent.Content
|
||||
if chatLocation.threadId == 1 {
|
||||
|
||||
@ -2477,11 +2477,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
ChatSidePanelEnvironment(insets: UIEdgeInsets(
|
||||
top: 0.0,
|
||||
left: leftPanelLeftInset,
|
||||
bottom: 0.0,
|
||||
bottom: containerInsets.bottom + inputPanelsHeight,
|
||||
right: 0.0
|
||||
))
|
||||
},
|
||||
containerSize: CGSize(width: leftPanelSize.width, height: leftPanelSize.height - sidePanelTopInset - (containerInsets.bottom + inputPanelsHeight))
|
||||
containerSize: CGSize(width: leftPanelSize.width, height: leftPanelSize.height - sidePanelTopInset)
|
||||
)
|
||||
|
||||
let leftPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: sidePanelTopInset), size: leftPanelSize)
|
||||
@ -4472,6 +4472,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
}
|
||||
|
||||
var effectivePresentationInterfaceState = self.chatPresentationInterfaceState
|
||||
|
||||
if let textInputPanelNode = self.textInputPanelNode {
|
||||
effectivePresentationInterfaceState = effectivePresentationInterfaceState.updatedInterfaceState { $0.withUpdatedEffectiveInputState(textInputPanelNode.inputTextState) }
|
||||
}
|
||||
|
||||
@ -61,6 +61,11 @@ extension ChatControllerImpl {
|
||||
var bannedSendVideos: (Int32, Bool)?
|
||||
var bannedSendFiles: (Int32, Bool)?
|
||||
|
||||
var enableMultiselection = true
|
||||
if self.presentationInterfaceState.interfaceState.postSuggestionState != nil {
|
||||
enableMultiselection = false
|
||||
}
|
||||
|
||||
var canSendPolls = true
|
||||
var canSendTodos = true
|
||||
if let peer = self.presentationInterfaceState.renderedPeer?.peer {
|
||||
@ -336,7 +341,7 @@ extension ChatControllerImpl {
|
||||
controller.prepareForReuse()
|
||||
return
|
||||
}
|
||||
strongSelf.presentMediaPicker(saveEditedPhotos: dataSettings.storeEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: { controller, mediaPickerContext in
|
||||
strongSelf.presentMediaPicker(saveEditedPhotos: dataSettings.storeEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, enableMultiselection: enableMultiselection, present: { controller, mediaPickerContext in
|
||||
let _ = currentMediaController.swap(controller)
|
||||
if !inputText.string.isEmpty {
|
||||
mediaPickerContext?.setCaption(inputText)
|
||||
@ -1230,7 +1235,7 @@ extension ChatControllerImpl {
|
||||
self.present(actionSheet, in: .window(.root))
|
||||
}
|
||||
|
||||
func presentMediaPicker(subject: MediaPickerScreenImpl.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreenImpl, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping (Bool, [Any], Bool, Int32?, ChatSendMessageActionSheetController.SendParameters?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) {
|
||||
func presentMediaPicker(subject: MediaPickerScreenImpl.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, enableMultiselection: Bool, present: @escaping (MediaPickerScreenImpl, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping (Bool, [Any], Bool, Int32?, ChatSendMessageActionSheetController.SendParameters?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) {
|
||||
var isScheduledMessages = false
|
||||
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
@ -1248,6 +1253,7 @@ extension ChatControllerImpl {
|
||||
isScheduledMessages: isScheduledMessages,
|
||||
bannedSendPhotos: bannedSendPhotos,
|
||||
bannedSendVideos: bannedSendVideos,
|
||||
enableMultiselection: enableMultiselection,
|
||||
canBoostToUnrestrict: (self.presentationInterfaceState.boostsToUnrestrict ?? 0) > 0 && bannedSendPhotos?.1 != true && bannedSendVideos?.1 != true,
|
||||
paidMediaAllowed: paidMediaAllowed,
|
||||
subject: subject,
|
||||
|
||||
@ -233,7 +233,7 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
||||
}
|
||||
}
|
||||
|
||||
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let mainChannel = chatPresentationInterfaceState.renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel, !mainChannel.hasPermission(.manageDirect) {
|
||||
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let mainChannel = chatPresentationInterfaceState.renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel, (!mainChannel.hasPermission(.manageDirect) || chatPresentationInterfaceState.chatLocation.threadId != nil) {
|
||||
if chatPresentationInterfaceState.interfaceState.postSuggestionState == nil {
|
||||
accessoryItems.append(.suggestPost)
|
||||
}
|
||||
|
||||
@ -1924,8 +1924,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
}
|
||||
}), false))
|
||||
} else if !isUnremovableAction {
|
||||
var iconName: String = isSending ? "Chat/Context Menu/Clear" : "Chat/Context Menu/Delete"
|
||||
if message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) {
|
||||
iconName = "Chat/Context Menu/DeletePaid"
|
||||
}
|
||||
|
||||
actions.append(.action(ContextMenuActionItem(text: title, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: isSending ? "Chat/Context Menu/Clear" : "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
|
||||
return generateTintedImage(image: UIImage(bundleImageName: iconName), color: theme.actionSheet.destructiveActionTextColor)
|
||||
}, action: { controller, f in
|
||||
if isEditing {
|
||||
context.account.pendingUpdateMessageManager.cancel(messageId: message.id)
|
||||
|
||||
@ -1523,6 +1523,13 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
||||
if case let .media(value) = editMessageState.content {
|
||||
isEditingMedia = !value.isEmpty
|
||||
isMediaEnabled = !value.isEmpty
|
||||
|
||||
if interfaceState.interfaceState.postSuggestionState != nil {
|
||||
if value.contains(.file) {
|
||||
isEditingMedia = false
|
||||
isMediaEnabled = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isMediaEnabled = true
|
||||
}
|
||||
|
||||
@ -149,6 +149,13 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
||||
canMatchThread = true
|
||||
switchToThread = true
|
||||
}
|
||||
if case .replyThread = params.chatLocation {
|
||||
if case let .replyThread(replyThread) = params.chatLocation, (replyThread.isForumPost || replyThread.isMonoforumPost) {
|
||||
} else {
|
||||
canMatchThread = false
|
||||
switchToThread = false
|
||||
}
|
||||
}
|
||||
|
||||
if controller.chatLocation.peerId == params.chatLocation.asChatLocation.peerId && canMatchThread && (controller.subject != .scheduledMessages || controller.subject == params.subject) {
|
||||
if let updateTextInputState = params.updateTextInputState {
|
||||
@ -191,7 +198,10 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
||||
|
||||
controller.purposefulAction = params.purposefulAction
|
||||
if let activateInput = params.activateInput {
|
||||
controller.activateInput(type: activateInput)
|
||||
if case let .replyThread(replyThread) = params.chatLocation, (replyThread.isForumPost || replyThread.isMonoforumPost) {
|
||||
} else {
|
||||
controller.activateInput(type: activateInput)
|
||||
}
|
||||
}
|
||||
if params.changeColors {
|
||||
controller.presentThemeSelection()
|
||||
|
||||
@ -3734,8 +3734,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
mode = .accountWithdraw(completion: completion)
|
||||
case let .enterAmount(current, minValue, fractionAfterCommission, kind, completion):
|
||||
mode = .paidMessages(current: current.value, minValue: minValue.value, fractionAfterCommission: fractionAfterCommission, kind: kind, completion: completion)
|
||||
case let .postSuggestion(channel, current, timestamp, completion):
|
||||
mode = .suggestedPost(mode: .sender(channel: channel), price: current, timestamp: timestamp, completion: completion)
|
||||
case let .postSuggestion(channel, isFromAdmin, current, timestamp, completion):
|
||||
mode = .suggestedPost(mode: .sender(channel: channel, isFromAdmin: isFromAdmin), price: current, timestamp: timestamp, completion: completion)
|
||||
case let .postSuggestionModification(current, timestamp, completion):
|
||||
mode = .suggestedPost(mode: .admin, price: current, timestamp: timestamp, completion: completion)
|
||||
}
|
||||
|
||||
20
third-party/td/TdBinding/Sources/TdBinding.mm
vendored
20
third-party/td/TdBinding/Sources/TdBinding.mm
vendored
@ -392,6 +392,26 @@ NSData * _Nullable tdGenerateSelfAddBlock(TdKeyPair *keyPair, int64_t userId, NS
|
||||
std::string mappedPublicKey((uint8_t *)keyPair.publicKey.bytes, ((uint8_t *)keyPair.publicKey.bytes) + keyPair.publicKey.length);
|
||||
std::string mappedPreviousBlock((uint8_t *)previousBlock.bytes, ((uint8_t *)previousBlock.bytes) + previousBlock.length);
|
||||
|
||||
|
||||
#if DEBUG
|
||||
auto describeResult = tde2e_api::call_describe_block(mappedPreviousBlock);
|
||||
if (describeResult.is_ok()) {
|
||||
NSString *utf8String = [[NSString alloc] initWithBytes:describeResult.value().data() length:describeResult.value().size() encoding:NSUTF8StringEncoding];
|
||||
if (utf8String) {
|
||||
NSLog(@"TdCall.selfAddBlock block: %@", utf8String);
|
||||
} else {
|
||||
NSString *lossyString = [[NSString alloc] initWithData:[NSData dataWithBytes:describeResult.value().data() length:describeResult.value().size()] encoding:NSASCIIStringEncoding];
|
||||
if (lossyString) {
|
||||
NSLog(@"TdCall.selfAddBlock block (lossy conversion): %@", lossyString);
|
||||
} else {
|
||||
NSLog(@"TdCall.selfAddBlock block: [binary data, length: %lu]", (unsigned long)describeResult.value().size());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NSLog(@"TdCall.selfAddBlock describe block failed");
|
||||
}
|
||||
#endif
|
||||
|
||||
auto publicKeyId = tde2e_api::key_from_public_key(mappedPublicKey);
|
||||
if (!publicKeyId.is_ok()) {
|
||||
return nil;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user