mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-07 16:11:13 +00:00
Various improvements
This commit is contained in:
parent
8818ef04ad
commit
3e40713aad
@ -1038,7 +1038,7 @@ public enum StarsWithdrawalScreenSubject {
|
|||||||
|
|
||||||
case withdraw(completion: (Int64) -> Void)
|
case withdraw(completion: (Int64) -> Void)
|
||||||
case enterAmount(current: StarsAmount, minValue: StarsAmount, fractionAfterCommission: Int, kind: PaidMessageKind, 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)
|
case postSuggestionModification(current: CurrencyAmount, timestamp: Int32?, completion: (CurrencyAmount, Int32?) -> Void)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,7 +384,7 @@ private func sendUploadedMessageContent(
|
|||||||
} else if let attribute = attribute as? PaidStarsMessageAttribute {
|
} else if let attribute = attribute as? PaidStarsMessageAttribute {
|
||||||
allowPaidStars = attribute.stars.value
|
allowPaidStars = attribute.stars.value
|
||||||
} else if let attribute = attribute as? SuggestedPostMessageAttribute {
|
} 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 {
|
} else if let attribute = attribute as? PaidStarsMessageAttribute {
|
||||||
allowPaidStars = attribute.stars.value
|
allowPaidStars = attribute.stars.value
|
||||||
} else if let attribute = attribute as? SuggestedPostMessageAttribute {
|
} else if let attribute = attribute as? SuggestedPostMessageAttribute {
|
||||||
suggestedPost = attribute.apiSuggestedPost()
|
suggestedPost = attribute.apiSuggestedPost(fixMinTime: Int32(Date().timeIntervalSince1970 + 10))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -900,7 +900,7 @@ public final class PendingMessageManager {
|
|||||||
} else if let attribute = attribute as? PaidStarsMessageAttribute {
|
} else if let attribute = attribute as? PaidStarsMessageAttribute {
|
||||||
allowPaidStars = attribute.stars.value * Int64(messages.count)
|
allowPaidStars = attribute.stars.value * Int64(messages.count)
|
||||||
} else if let attribute = attribute as? SuggestedPostMessageAttribute {
|
} 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 {
|
} else if let attribute = attribute as? PaidStarsMessageAttribute {
|
||||||
allowPaidStars = attribute.stars.value
|
allowPaidStars = attribute.stars.value
|
||||||
} else if let attribute = attribute as? SuggestedPostMessageAttribute {
|
} 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
|
var flags: Int32 = 0
|
||||||
if let state = self.state {
|
if let state = self.state {
|
||||||
switch state {
|
switch state {
|
||||||
@ -80,7 +80,14 @@ extension SuggestedPostMessageAttribute {
|
|||||||
flags |= 1 << 2
|
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
|
flags |= 1 << 0
|
||||||
}
|
}
|
||||||
var price: Api.StarsAmount?
|
var price: Api.StarsAmount?
|
||||||
@ -88,7 +95,7 @@ extension SuggestedPostMessageAttribute {
|
|||||||
flags |= 1 << 3
|
flags |= 1 << 3
|
||||||
price = amount.apiAmount
|
price = amount.apiAmount
|
||||||
}
|
}
|
||||||
return .suggestedPost(flags: flags, price: price, scheduleDate: self.timestamp)
|
return .suggestedPost(flags: flags, price: price, scheduleDate: timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
let imageSize = CGSize(width: 212.0, height: 212.0)
|
||||||
|
|
||||||
var updatedAttributedString = attributedString
|
var updatedAttributedString = attributedString
|
||||||
@ -270,13 +275,13 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
switch amount.currency {
|
switch amount.currency {
|
||||||
case .stars:
|
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."
|
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 {
|
} 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."
|
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:
|
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."
|
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 {
|
} 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."
|
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
|
let rawString: String
|
||||||
if let timestamp {
|
if let timestamp {
|
||||||
if Int32(Date().timeIntervalSince1970) >= 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
|
rawString = "📅 The post has been automatically published in **\(channelName)** **\(timeString)**." + pricePart
|
||||||
} else {
|
} else {
|
||||||
rawString = "📅 Your post has been automatically published in **\(channelName)** **\(timeString)**." + pricePart
|
rawString = "📅 Your post has been automatically published in **\(channelName)** **\(timeString)**." + pricePart
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !item.message.effectivelyIncoming(item.context.account.peerId) {
|
if !isUser {
|
||||||
rawString = "📅 The post will be automatically published in **\(channelName)** **\(timeString)**." + pricePart
|
rawString = "📅 The post will be automatically published in **\(channelName)** **\(timeString)**." + pricePart
|
||||||
} else {
|
} else {
|
||||||
rawString = "📅 Your post will be automatically published in **\(channelName)** **\(timeString)**." + pricePart
|
rawString = "📅 Your post will be automatically published in **\(channelName)** **\(timeString)**." + pricePart
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !item.message.effectivelyIncoming(item.context.account.peerId) {
|
if !isUser {
|
||||||
rawString = "📅 The post has been automatically published in **\(channelName)**." + pricePart
|
rawString = "📅 The post has been automatically published in **\(channelName)**." + pricePart
|
||||||
} else {
|
} else {
|
||||||
rawString = "📅 Your post has been automatically published in **\(channelName)**." + pricePart
|
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)
|
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 incoming = message.effectivelyIncoming(context.account.peerId)
|
||||||
let graphics = PresentationResourcesChat.additionalGraphics(theme.theme, wallpaper: theme.wallpaper, bubbleCorners: bubbleCorners)
|
let graphics = PresentationResourcesChat.additionalGraphics(theme.theme, wallpaper: theme.wallpaper, bubbleCorners: bubbleCorners)
|
||||||
|
|
||||||
@ -227,7 +227,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
|||||||
var isStarsPayment = false
|
var isStarsPayment = false
|
||||||
let iconImage: UIImage?
|
let iconImage: UIImage?
|
||||||
var tintColor: UIColor?
|
var tintColor: UIColor?
|
||||||
if let customIcon {
|
if let customIcon = customInfo?.icon {
|
||||||
switch customIcon {
|
switch customIcon {
|
||||||
case .suggestedPostReject:
|
case .suggestedPostReject:
|
||||||
iconImage = PresentationResourcesChat.messageButtonsPostReject(theme.theme)
|
iconImage = PresentationResourcesChat.messageButtonsPostReject(theme.theme)
|
||||||
@ -314,7 +314,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var customIconSpaceWidth: CGFloat = 0.0
|
var customIconSpaceWidth: CGFloat = 0.0
|
||||||
if let iconImage, customIcon != nil {
|
if let iconImage, customInfo?.icon != nil {
|
||||||
customIconSpaceWidth = 3.0 + iconImage.size.width
|
customIconSpaceWidth = 3.0 + iconImage.size.width
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,14 +334,13 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node.wallpaperBackgroundNode = backgroundNode
|
node.wallpaperBackgroundNode = backgroundNode
|
||||||
|
|
||||||
node.button = button
|
node.button = button
|
||||||
|
|
||||||
switch button.action {
|
switch button.action {
|
||||||
case .url:
|
case .url:
|
||||||
node.longTapRecognizer?.isEnabled = true
|
node.longTapRecognizer?.isEnabled = true
|
||||||
default:
|
default:
|
||||||
node.longTapRecognizer?.isEnabled = false
|
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)
|
//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)
|
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
|
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)
|
titleNode.layer.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||||
@ -464,7 +463,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
if let iconNode = node.iconNode {
|
if let iconNode = node.iconNode {
|
||||||
let iconFrame: CGRect
|
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)
|
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 {
|
} else {
|
||||||
iconFrame = CGRect(x: width - 16.0, y: 4.0, width: 12.0, height: 12.0)
|
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.accessibilityLabel = title
|
||||||
node.accessibilityArea.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: 42.0))
|
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
|
return node
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -493,6 +504,16 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode {
|
|||||||
case suggestedPostEdit
|
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 buttonNodes: [ChatMessageActionButtonNode] = []
|
||||||
|
|
||||||
private var buttonPressedWrapper: ((ReplyMarkupButton, Promise<Bool>) -> Void)?
|
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) } ?? []
|
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 buttonHeight: CGFloat = 42.0
|
||||||
let buttonSpacing: CGFloat = 2.0
|
let buttonSpacing: CGFloat = 2.0
|
||||||
|
|
||||||
@ -548,9 +569,9 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode {
|
|||||||
var finalizeRowButtonLayouts: [((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))] = []
|
var finalizeRowButtonLayouts: [((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))] = []
|
||||||
var rowButtonIndex = 0
|
var rowButtonIndex = 0
|
||||||
for button in row.buttons {
|
for button in row.buttons {
|
||||||
var customIcon: CustomIcon?
|
var customInfo: CustomInfo?
|
||||||
if case let .callback(_, data) = button.action {
|
if case let .callback(_, data) = button.action {
|
||||||
customIcon = customIcons[data]
|
customInfo = customInfos[data]
|
||||||
}
|
}
|
||||||
|
|
||||||
let buttonPosition: MessageBubbleActionButtonPosition
|
let buttonPosition: MessageBubbleActionButtonPosition
|
||||||
@ -570,9 +591,9 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode {
|
|||||||
|
|
||||||
let prepareButtonLayout: (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode)))
|
let prepareButtonLayout: (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode)))
|
||||||
if buttonIndex < currentButtonLayouts.count {
|
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 {
|
} 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)
|
maximumRowButtonWidth = max(maximumRowButtonWidth, prepareButtonLayout.minimumWidth)
|
||||||
|
@ -1295,6 +1295,11 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
maxContentWidth = max(maxContentWidth, minWidth)
|
maxContentWidth = max(maxContentWidth, minWidth)
|
||||||
actionButtonsFinalize = buttonsLayout
|
actionButtonsFinalize = buttonsLayout
|
||||||
} else if incoming, let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil {
|
} 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
|
//TODO:localize
|
||||||
var buttonDeclineValue: UInt8 = 0
|
var buttonDeclineValue: UInt8 = 0
|
||||||
let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1))
|
let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1))
|
||||||
@ -1303,10 +1308,19 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
var buttonSuggestChangesValue: UInt8 = 2
|
var buttonSuggestChangesValue: UInt8 = 2
|
||||||
let buttonSuggestChanges = MemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1))
|
let buttonSuggestChanges = MemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1))
|
||||||
|
|
||||||
let customIcons: [MemoryBuffer: ChatMessageActionButtonsNode.CustomIcon] = [
|
let customInfos: [MemoryBuffer: ChatMessageActionButtonsNode.CustomInfo] = [
|
||||||
buttonDecline: .suggestedPostReject,
|
buttonDecline: ChatMessageActionButtonsNode.CustomInfo(
|
||||||
buttonApprove: .suggestedPostApprove,
|
isEnabled: true,
|
||||||
buttonSuggestChanges: .suggestedPostEdit
|
icon: .suggestedPostReject
|
||||||
|
),
|
||||||
|
buttonApprove: ChatMessageActionButtonsNode.CustomInfo(
|
||||||
|
isEnabled: canApprove,
|
||||||
|
icon: .suggestedPostApprove
|
||||||
|
),
|
||||||
|
buttonSuggestChanges: ChatMessageActionButtonsNode.CustomInfo(
|
||||||
|
isEnabled: canApprove,
|
||||||
|
icon: .suggestedPostEdit
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
let (minWidth, buttonsLayout) = actionButtonsLayout(
|
let (minWidth, buttonsLayout) = actionButtonsLayout(
|
||||||
@ -1327,7 +1341,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
],
|
],
|
||||||
flags: [],
|
flags: [],
|
||||||
placeholder: nil
|
placeholder: nil
|
||||||
), customIcons, item.message, baseWidth)
|
), customInfos, item.message, baseWidth)
|
||||||
maxContentWidth = max(maxContentWidth, minWidth)
|
maxContentWidth = max(maxContentWidth, minWidth)
|
||||||
actionButtonsFinalize = buttonsLayout
|
actionButtonsFinalize = buttonsLayout
|
||||||
}
|
}
|
||||||
|
@ -1491,7 +1491,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode),
|
threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode),
|
||||||
forwardInfoLayout: (AccountContext, ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, ChatMessageForwardInfoNode.StoryData?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
|
forwardInfoLayout: (AccountContext, ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, ChatMessageForwardInfoNode.StoryData?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
|
||||||
replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode),
|
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)),
|
reactionButtonsLayout: (ChatMessageReactionButtonsNode.Arguments) -> (minWidth: CGFloat, layout: (CGFloat) -> (size: CGSize, apply: (ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode)),
|
||||||
unlockButtonLayout: (ChatMessageUnlockMediaNode.Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode),
|
unlockButtonLayout: (ChatMessageUnlockMediaNode.Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode),
|
||||||
mediaInfoLayout: (ChatMessageStarsMediaInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageStarsMediaInfoNode),
|
mediaInfoLayout: (ChatMessageStarsMediaInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageStarsMediaInfoNode),
|
||||||
@ -2806,6 +2806,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
|
|
||||||
lastNodeTopPosition = .None(.Both)
|
lastNodeTopPosition = .None(.Both)
|
||||||
} else if incoming, let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil {
|
} 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
|
//TODO:localize
|
||||||
var buttonDeclineValue: UInt8 = 0
|
var buttonDeclineValue: UInt8 = 0
|
||||||
let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1))
|
let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1))
|
||||||
@ -2814,10 +2819,19 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
var buttonSuggestChangesValue: UInt8 = 2
|
var buttonSuggestChangesValue: UInt8 = 2
|
||||||
let buttonSuggestChanges = MemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1))
|
let buttonSuggestChanges = MemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1))
|
||||||
|
|
||||||
let customIcons: [MemoryBuffer: ChatMessageActionButtonsNode.CustomIcon] = [
|
let customInfos: [MemoryBuffer: ChatMessageActionButtonsNode.CustomInfo] = [
|
||||||
buttonDecline: .suggestedPostReject,
|
buttonDecline: ChatMessageActionButtonsNode.CustomInfo(
|
||||||
buttonApprove: .suggestedPostApprove,
|
isEnabled: true,
|
||||||
buttonSuggestChanges: .suggestedPostEdit
|
icon: .suggestedPostReject
|
||||||
|
),
|
||||||
|
buttonApprove: ChatMessageActionButtonsNode.CustomInfo(
|
||||||
|
isEnabled: canApprove,
|
||||||
|
icon: .suggestedPostApprove
|
||||||
|
),
|
||||||
|
buttonSuggestChanges: ChatMessageActionButtonsNode.CustomInfo(
|
||||||
|
isEnabled: canApprove,
|
||||||
|
icon: .suggestedPostEdit
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
let (minWidth, buttonsLayout) = actionButtonsLayout(
|
let (minWidth, buttonsLayout) = actionButtonsLayout(
|
||||||
@ -2838,7 +2852,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
],
|
],
|
||||||
flags: [],
|
flags: [],
|
||||||
placeholder: nil
|
placeholder: nil
|
||||||
), customIcons, item.message, baseWidth)
|
), customInfos, item.message, baseWidth)
|
||||||
maxContentWidth = max(maxContentWidth, minWidth)
|
maxContentWidth = max(maxContentWidth, minWidth)
|
||||||
actionButtonsFinalize = buttonsLayout
|
actionButtonsFinalize = buttonsLayout
|
||||||
|
|
||||||
|
@ -856,6 +856,11 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
maxContentWidth = max(maxContentWidth, minWidth)
|
maxContentWidth = max(maxContentWidth, minWidth)
|
||||||
actionButtonsFinalize = buttonsLayout
|
actionButtonsFinalize = buttonsLayout
|
||||||
} else if incoming, let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil {
|
} 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
|
//TODO:localize
|
||||||
var buttonDeclineValue: UInt8 = 0
|
var buttonDeclineValue: UInt8 = 0
|
||||||
let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1))
|
let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1))
|
||||||
@ -864,10 +869,19 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
var buttonSuggestChangesValue: UInt8 = 2
|
var buttonSuggestChangesValue: UInt8 = 2
|
||||||
let buttonSuggestChanges = MemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1))
|
let buttonSuggestChanges = MemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1))
|
||||||
|
|
||||||
let customIcons: [MemoryBuffer: ChatMessageActionButtonsNode.CustomIcon] = [
|
let customInfos: [MemoryBuffer: ChatMessageActionButtonsNode.CustomInfo] = [
|
||||||
buttonDecline: .suggestedPostReject,
|
buttonDecline: ChatMessageActionButtonsNode.CustomInfo(
|
||||||
buttonApprove: .suggestedPostApprove,
|
isEnabled: true,
|
||||||
buttonSuggestChanges: .suggestedPostEdit
|
icon: .suggestedPostReject
|
||||||
|
),
|
||||||
|
buttonApprove: ChatMessageActionButtonsNode.CustomInfo(
|
||||||
|
isEnabled: canApprove,
|
||||||
|
icon: .suggestedPostApprove
|
||||||
|
),
|
||||||
|
buttonSuggestChanges: ChatMessageActionButtonsNode.CustomInfo(
|
||||||
|
isEnabled: canApprove,
|
||||||
|
icon: .suggestedPostEdit
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
let (minWidth, buttonsLayout) = actionButtonsLayout(
|
let (minWidth, buttonsLayout) = actionButtonsLayout(
|
||||||
@ -888,7 +902,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
],
|
],
|
||||||
flags: [],
|
flags: [],
|
||||||
placeholder: nil
|
placeholder: nil
|
||||||
), customIcons, item.message, baseWidth)
|
), customInfos, item.message, baseWidth)
|
||||||
maxContentWidth = max(maxContentWidth, minWidth)
|
maxContentWidth = max(maxContentWidth, minWidth)
|
||||||
actionButtonsFinalize = buttonsLayout
|
actionButtonsFinalize = buttonsLayout
|
||||||
}
|
}
|
||||||
|
@ -1015,8 +1015,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
private var channelsForPublicReaction: [EnginePeer] = []
|
private var channelsForPublicReaction: [EnginePeer] = []
|
||||||
private var channelsForPublicReactionDisposable: Disposable?
|
private var channelsForPublicReactionDisposable: Disposable?
|
||||||
|
|
||||||
private var currentSuggestPostTimestamp: Int32?
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.bottomOverscrollLimit = 200.0
|
self.bottomOverscrollLimit = 200.0
|
||||||
|
|
||||||
@ -1393,28 +1391,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
controller.presentInGlobalOverlay(contextController)
|
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 {
|
func update(component: ChatSendStarsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||||
self.isUpdating = true
|
self.isUpdating = true
|
||||||
defer {
|
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 {
|
if let starsContext = component.context.starsContext {
|
||||||
@ -1578,8 +1551,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
switch component.initialData.subjectInitialData {
|
switch component.initialData.subjectInitialData {
|
||||||
case let .react(reactData):
|
case let .react(reactData):
|
||||||
maxAmount = reactData.maxAmount
|
maxAmount = reactData.maxAmount
|
||||||
case let .suggestPost(suggestPostData):
|
|
||||||
maxAmount = suggestPostData.maxAmount
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.amount = self.amount.withSliderValue(value)
|
self.amount = self.amount.withSliderValue(value)
|
||||||
@ -1659,8 +1630,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
self.isPastTopCutoff = nil
|
self.isPastTopCutoff = nil
|
||||||
}
|
}
|
||||||
case .suggestPost:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.sliderBackground.update(
|
let _ = self.sliderBackground.update(
|
||||||
@ -1784,8 +1753,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
transition.setFrame(view: peerSelectorButtonView, frame: peerSelectorButtonFrame)
|
transition.setFrame(view: peerSelectorButtonView, frame: peerSelectorButtonFrame)
|
||||||
peerSelectorButtonView.isHidden = sendAsPeers.count <= 1
|
peerSelectorButtonView.isHidden = sendAsPeers.count <= 1
|
||||||
}
|
}
|
||||||
case .suggestPost:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if themeUpdated {
|
if themeUpdated {
|
||||||
@ -1837,8 +1804,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
case let .react(reactData):
|
case let .react(reactData):
|
||||||
let currentMyPeer = self.currentMyPeer ?? reactData.myPeer
|
let currentMyPeer = self.currentMyPeer ?? reactData.myPeer
|
||||||
subtitleText = environment.strings.SendStarReactions_SubtitleFrom(currentMyPeer.compactDisplayTitle).string
|
subtitleText = environment.strings.SendStarReactions_SubtitleFrom(currentMyPeer.compactDisplayTitle).string
|
||||||
case .suggestPost:
|
|
||||||
subtitleText = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var subtitleSize: CGSize?
|
var subtitleSize: CGSize?
|
||||||
@ -1857,9 +1822,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
switch component.initialData.subjectInitialData {
|
switch component.initialData.subjectInitialData {
|
||||||
case .react:
|
case .react:
|
||||||
titleText = environment.strings.SendStarReactions_Title
|
titleText = environment.strings.SendStarReactions_Title
|
||||||
case .suggestPost:
|
|
||||||
//TODO:localize
|
|
||||||
titleText = "Suggest a Message"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleSize = title.update(
|
let titleSize = title.update(
|
||||||
@ -1907,9 +1869,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
text = environment.strings.SendStarReactions_TextGeneric(reactData.peer.debugDisplayTitle).string
|
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)
|
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 += 22.0
|
||||||
contentHeight += 2.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 {
|
switch component.initialData.subjectInitialData {
|
||||||
case let .react(reactData):
|
case let .react(reactData):
|
||||||
if !reactData.topPeers.isEmpty {
|
if !reactData.topPeers.isEmpty {
|
||||||
@ -2307,8 +2234,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
contentHeight += anonymousContentsSize.height + 27.0
|
contentHeight += anonymousContentsSize.height + 27.0
|
||||||
case .suggestPost:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initialContentHeight = contentHeight
|
initialContentHeight = contentHeight
|
||||||
@ -2321,8 +2246,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
switch component.initialData.subjectInitialData {
|
switch component.initialData.subjectInitialData {
|
||||||
case .react:
|
case .react:
|
||||||
buttonString = environment.strings.SendStarReactions_SendButtonTitle("\(self.amount.realValue)").string
|
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)
|
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 {
|
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 {
|
switch component.initialData.subjectInitialData {
|
||||||
case let .react(reactData):
|
case let .react(reactData):
|
||||||
purchasePurpose = .reactions(peerId: reactData.peer.id, requiredStars: Int64(self.amount.realValue))
|
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
|
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
|
sourceView: badgeView.badgeIcon
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case let .suggestPost(suggestPostData):
|
|
||||||
suggestPostData.completion(Int64(self.amount.realValue), self.currentSuggestPostTimestamp)
|
|
||||||
}
|
}
|
||||||
self.environment?.controller()?.dismiss()
|
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 react(React)
|
||||||
case suggestPost(SuggestPost)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class InitialData {
|
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) {
|
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||||
if !self.isDismissed {
|
if !self.isDismissed {
|
||||||
self.isDismissed = true
|
self.isDismissed = true
|
||||||
|
@ -24,6 +24,7 @@ import UndoUI
|
|||||||
import ListActionItemComponent
|
import ListActionItemComponent
|
||||||
import ChatScheduleTimeController
|
import ChatScheduleTimeController
|
||||||
import TabSelectorComponent
|
import TabSelectorComponent
|
||||||
|
import PresentationDataUtils
|
||||||
|
|
||||||
private let amountTag = GenericComponentViewTag()
|
private let amountTag = GenericComponentViewTag()
|
||||||
|
|
||||||
@ -83,8 +84,15 @@ private final class SheetContent: CombinedComponent {
|
|||||||
let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0
|
let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0
|
||||||
|
|
||||||
if case let .suggestedPost(mode, _, _, _) = component.mode {
|
if case let .suggestedPost(mode, _, _, _) = component.mode {
|
||||||
|
var displayBalance = false
|
||||||
switch mode {
|
switch mode {
|
||||||
case .sender:
|
case let .sender(_, isFromAdmin):
|
||||||
|
displayBalance = !isFromAdmin
|
||||||
|
case .admin:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if displayBalance {
|
||||||
let balance = balance.update(
|
let balance = balance.update(
|
||||||
component: BalanceComponent(
|
component: BalanceComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
@ -102,8 +110,6 @@ private final class SheetContent: CombinedComponent {
|
|||||||
.anchorPoint(CGPoint(x: 1.0, y: 0.0))
|
.anchorPoint(CGPoint(x: 1.0, y: 0.0))
|
||||||
.position(CGPoint(x: balanceFrame.maxX, y: balanceFrame.minY))
|
.position(CGPoint(x: balanceFrame.maxX, y: balanceFrame.minY))
|
||||||
)
|
)
|
||||||
case .admin:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let closeButton = closeButton.update(
|
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
|
//TODO:localize
|
||||||
let selectedId: AnyHashable = state.currency == .stars ? AnyHashable(0 as Int) : AnyHashable(1 as Int)
|
let selectedId: AnyHashable = state.currency == .stars ? AnyHashable(0 as Int) : AnyHashable(1 as Int)
|
||||||
let starsTitle: String
|
let starsTitle: String
|
||||||
@ -432,14 +442,23 @@ private final class SheetContent: CombinedComponent {
|
|||||||
))
|
))
|
||||||
case let .suggestedPost(mode, _, _, _):
|
case let .suggestedPost(mode, _, _, _):
|
||||||
switch mode {
|
switch mode {
|
||||||
case let .sender(channel):
|
case let .sender(channel, isFromAdmin):
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
let string: String
|
let string: String
|
||||||
switch state.currency {
|
if isFromAdmin {
|
||||||
case .stars:
|
switch state.currency {
|
||||||
string = "Choose how many Stars you want to offer \(channel.compactDisplayTitle) to publish this message."
|
case .stars:
|
||||||
case .ton:
|
string = "Choose how many Stars you charge for the message."
|
||||||
string = "Choose how many TON you want to offer \(channel.compactDisplayTitle) to publish this 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))
|
let amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(string, attributes: amountMarkdownAttributes, textAlignment: .natural))
|
||||||
amountFooter = AnyComponent(MultilineTextComponent(
|
amountFooter = AnyComponent(MultilineTextComponent(
|
||||||
@ -595,7 +614,9 @@ private final class SheetContent: CombinedComponent {
|
|||||||
let component = state.component
|
let component = state.component
|
||||||
|
|
||||||
let theme = environment.theme
|
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 {
|
guard let state else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -729,6 +750,36 @@ private final class SheetContent: CombinedComponent {
|
|||||||
case let .paidMessages(_, _, _, _, completion):
|
case let .paidMessages(_, _, _, _, completion):
|
||||||
completion(amount.value)
|
completion(amount.value)
|
||||||
case let .suggestedPost(_, _, _, completion):
|
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)
|
completion(CurrencyAmount(amount: amount, currency: state.currency), state.timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -971,7 +1022,7 @@ private final class StarsWithdrawSheetComponent: CombinedComponent {
|
|||||||
public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
||||||
public enum Mode {
|
public enum Mode {
|
||||||
public enum SuggestedPostMode {
|
public enum SuggestedPostMode {
|
||||||
case sender(channel: EnginePeer)
|
case sender(channel: EnginePeer, isFromAdmin: Bool)
|
||||||
case admin
|
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 final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate {
|
||||||
private let currency: CurrencyAmount.Currency
|
private let currency: CurrencyAmount.Currency
|
||||||
private let dateTimeFormat: PresentationDateTimeFormat
|
private let dateTimeFormat: PresentationDateTimeFormat
|
||||||
@ -1479,8 +1214,21 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case .ton:
|
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.")) {
|
if (newText == "0" && !acceptZero) || (newText.count > 1 && newText.hasPrefix("0") && !newText.hasPrefix("0.")) {
|
||||||
newText.removeFirst()
|
newText.removeFirst()
|
||||||
|
fixedText = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if fixedText {
|
||||||
textField.text = newText
|
textField.text = newText
|
||||||
self.onTextChanged(text: newText)
|
self.onTextChanged(text: newText)
|
||||||
return false
|
return false
|
||||||
@ -1705,7 +1453,6 @@ private final class AmountFieldComponent: Component {
|
|||||||
}
|
}
|
||||||
self.tonFormatter = nil
|
self.tonFormatter = nil
|
||||||
self.textField.delegate = self.starsFormatter
|
self.textField.delegate = self.starsFormatter
|
||||||
self.textField.text = ""
|
|
||||||
case .ton:
|
case .ton:
|
||||||
self.textField.keyboardType = .numbersAndPunctuation
|
self.textField.keyboardType = .numbersAndPunctuation
|
||||||
if self.tonFormatter == nil {
|
if self.tonFormatter == nil {
|
||||||
|
@ -1011,8 +1011,15 @@ extension ChatControllerImpl {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} 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(
|
subject = .postSuggestion(
|
||||||
channel: .channel(channel),
|
channel: .channel(channel),
|
||||||
|
isFromAdmin: isFromAdmin,
|
||||||
current: postSuggestionState.price ?? CurrencyAmount(amount: .zero, currency: .stars),
|
current: postSuggestionState.price ?? CurrencyAmount(amount: .zero, currency: .stars),
|
||||||
timestamp: postSuggestionState.timestamp,
|
timestamp: postSuggestionState.timestamp,
|
||||||
completion: { [weak self] price, timestamp in
|
completion: { [weak self] price, timestamp in
|
||||||
|
@ -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 {
|
if chatPresentationInterfaceState.interfaceState.postSuggestionState == nil {
|
||||||
accessoryItems.append(.suggestPost)
|
accessoryItems.append(.suggestPost)
|
||||||
}
|
}
|
||||||
|
@ -3734,8 +3734,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
mode = .accountWithdraw(completion: completion)
|
mode = .accountWithdraw(completion: completion)
|
||||||
case let .enterAmount(current, minValue, fractionAfterCommission, kind, completion):
|
case let .enterAmount(current, minValue, fractionAfterCommission, kind, completion):
|
||||||
mode = .paidMessages(current: current.value, minValue: minValue.value, fractionAfterCommission: fractionAfterCommission, kind: kind, completion: completion)
|
mode = .paidMessages(current: current.value, minValue: minValue.value, fractionAfterCommission: fractionAfterCommission, kind: kind, completion: completion)
|
||||||
case let .postSuggestion(channel, current, timestamp, completion):
|
case let .postSuggestion(channel, isFromAdmin, current, timestamp, completion):
|
||||||
mode = .suggestedPost(mode: .sender(channel: channel), price: current, timestamp: timestamp, completion: completion)
|
mode = .suggestedPost(mode: .sender(channel: channel, isFromAdmin: isFromAdmin), price: current, timestamp: timestamp, completion: completion)
|
||||||
case let .postSuggestionModification(current, timestamp, completion):
|
case let .postSuggestionModification(current, timestamp, completion):
|
||||||
mode = .suggestedPost(mode: .admin, price: current, timestamp: timestamp, completion: completion)
|
mode = .suggestedPost(mode: .admin, price: current, timestamp: timestamp, completion: completion)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user