diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 42bf87629b..877ed5d1f5 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -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) } diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index 2cac48b3b5..b3749f0605 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -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)) } } diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 770abaa3e0..df88ea29d2 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -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)) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift index 390710fb04..4602bd7ccc 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift @@ -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) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift index c6673ec321..4aac0f9313 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift @@ -217,6 +217,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 @@ -270,13 +275,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." @@ -287,20 +292,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 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift index ade800e580..9aa7f29a9b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift @@ -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) -> 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) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index 8fd93f0bcf..8053656588 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index cfd5d507aa..1f786cf66a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -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 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index ad8c362519..8e4d882243 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift index 654807dc07..673a9092d4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift @@ -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, 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 { - let balance: Signal - 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 diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 5f6153be25..23c08659a0 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -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( @@ -289,7 +295,11 @@ private final class SheetContent: CombinedComponent { ) } - if case let .suggestedPost(mode, _, _, _) = component.mode { + var tonBalanceValue: StarsAmount = .zero + if let tonBalance = state.tonBalance { + tonBalanceValue = tonBalance + } + if case let .suggestedPost(mode, _, _, _) = component.mode, (state.currency == .ton || tonBalanceValue > StarsAmount.zero) { //TODO:localize let selectedId: AnyHashable = state.currency == .stars ? AnyHashable(0 as Int) : AnyHashable(1 as Int) let starsTitle: String @@ -432,14 +442,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( @@ -595,7 +614,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 } @@ -729,6 +750,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 +1022,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 +1108,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 @@ -1479,8 +1214,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 @@ -1705,7 +1453,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 { diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 6d9a1c7d34..4db78b786f 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift index 0569f1ebc1..9567217139 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift @@ -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) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index c39b3ac5d2..91f821e5a3 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -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) }