[WIP] Post suggestions

This commit is contained in:
Isaac 2025-04-28 23:34:03 +02:00
parent d6334b9748
commit 084bb5bcd5
21 changed files with 412 additions and 93 deletions

View File

@ -996,8 +996,13 @@ public enum SendInviteLinkScreenSubject {
} }
public enum StarsWithdrawalScreenSubject { public enum StarsWithdrawalScreenSubject {
public enum PaidMessageKind {
case privacy
case postSuggestion
}
case withdraw case withdraw
case enterAmount(current: StarsAmount) case enterAmount(current: StarsAmount, minValue: StarsAmount, fractionAfterCommission: Int, kind: PaidMessageKind)
} }
public protocol SharedAccountContext: AnyObject { public protocol SharedAccountContext: AnyObject {

View File

@ -376,7 +376,7 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP
if case let .paidMessages(value) = stateValue.with({ $0 }).updatedValue { if case let .paidMessages(value) = stateValue.with({ $0 }).updatedValue {
currentAmount = value currentAmount = value
} }
let starsScreen = context.sharedContext.makeStarsWithdrawalScreen(context: context, subject: .enterAmount(current: currentAmount), completion: { amount in let starsScreen = context.sharedContext.makeStarsWithdrawalScreen(context: context, subject: .enterAmount(current: currentAmount, minValue: StarsAmount(value: 1, nanos: 0), fractionAfterCommission: 80, kind: .privacy), completion: { amount in
updateState { state in updateState { state in
var state = state var state = state
state.updatedValue = .paidMessages(StarsAmount(value: amount, nanos: 0)) state.updatedValue = .paidMessages(StarsAmount(value: amount, nanos: 0))

View File

@ -321,48 +321,88 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
return self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, force: false) return self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, force: false)
} }
private var displayedGiftTooltip = false private var displayedGiftOrSuggestTooltip = false
private func presentGiftTooltip() { private func presentGiftOrSuggestTooltip() {
guard let context = self.context, !self.displayedGiftTooltip, let parentController = self.interfaceInteraction?.chatController() else { guard let context = self.context, !self.displayedGiftOrSuggestTooltip, let parentController = self.interfaceInteraction?.chatController() else {
return return
} }
self.displayedGiftTooltip = true self.displayedGiftOrSuggestTooltip = true
let _ = (ApplicationSpecificNotice.getChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager) let _ = (combineLatest(queue: .mainQueue(),
|> deliverOnMainQueue).start(next: { [weak self] count in ApplicationSpecificNotice.getChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager),
ApplicationSpecificNotice.getChannelSuggestTooltip(accountManager: context.sharedContext.accountManager)
|> deliverOnMainQueue)).start(next: { [weak self] giftCount, suggestCount in
guard let self else { guard let self else {
return return
} }
guard count < 2 else {
return /*#if DEBUG
var giftCount = giftCount
var suggestCount = suggestCount
if "".isEmpty {
giftCount = 2
suggestCount = 0
} }
#endif*/
let _ = ApplicationSpecificNotice.incrementChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager).start() if giftCount < 2 && !self.giftButton.isHidden {
let _ = ApplicationSpecificNotice.incrementChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager).start()
Queue.mainQueue().after(0.4, {
let absoluteFrame = self.giftButton.view.convert(self.giftButton.bounds, to: parentController.view)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 11.0), size: CGSize())
let presentationData = context.sharedContext.currentPresentationData.with { $0 } Queue.mainQueue().after(0.4, {
let text: String = presentationData.strings.Chat_SendGiftTooltip let absoluteFrame = self.giftButton.view.convert(self.giftButton.bounds, to: parentController.view)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 11.0), size: CGSize())
let tooltipController = TooltipScreen(
account: context.account, let presentationData = context.sharedContext.currentPresentationData.with { $0 }
sharedContext: context.sharedContext, let text: String = presentationData.strings.Chat_SendGiftTooltip
text: .plain(text: text),
balancedTextLayout: false, let tooltipController = TooltipScreen(
style: .wide, account: context.account,
arrowStyle: .small, sharedContext: context.sharedContext,
icon: nil, text: .plain(text: text),
location: .point(location, .bottom), balancedTextLayout: false,
displayDuration: .default, style: .wide,
inset: 8.0, arrowStyle: .small,
shouldDismissOnTouch: { _, _ in icon: nil,
return .ignore location: .point(location, .bottom),
} displayDuration: .default,
) inset: 8.0,
self.interfaceInteraction?.presentControllerInCurrent(tooltipController, nil) shouldDismissOnTouch: { _, _ in
}) return .ignore
}
)
self.interfaceInteraction?.presentControllerInCurrent(tooltipController, nil)
})
} else if suggestCount < 2 && !self.suggestedPostButton.isHidden {
let _ = ApplicationSpecificNotice.incrementChannelSuggestTooltip(accountManager: context.sharedContext.accountManager).start()
Queue.mainQueue().after(0.4, {
let absoluteFrame = self.suggestedPostButton.view.convert(self.suggestedPostButton.bounds, to: parentController.view)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 11.0), size: CGSize())
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = presentationData
//TODO:localize
let text: String = "Tap here to suggest a message"
let tooltipController = TooltipScreen(
account: context.account,
sharedContext: context.sharedContext,
text: .plain(text: text),
textBadge: "NEW",
balancedTextLayout: false,
style: .wide,
arrowStyle: .small,
icon: nil,
location: .point(location, .bottom),
displayDuration: .default,
inset: 8.0,
shouldDismissOnTouch: { _, _ in
return .ignore
}
)
self.interfaceInteraction?.presentControllerInCurrent(tooltipController, nil)
})
}
}) })
} }
@ -377,7 +417,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
if previousState?.theme !== interfaceState.theme { if previousState?.theme !== interfaceState.theme {
self.badgeBackground.image = PresentationResourcesChatList.badgeBackgroundActive(interfaceState.theme, diameter: 20.0) self.badgeBackground.image = PresentationResourcesChatList.badgeBackgroundActive(interfaceState.theme, diameter: 20.0)
self.helpButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Help"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal) self.helpButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Help"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal)
self.suggestedPostButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Gift"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal) self.suggestedPostButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/SuggestPost"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal)
self.giftButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Gift"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal) self.giftButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Gift"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal)
} }
@ -431,11 +471,12 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
self.helpButton.isHidden = true self.helpButton.isHidden = true
//TODO:release //TODO:release
self.suggestedPostButton.isHidden = false self.suggestedPostButton.isHidden = false
self.presentGiftTooltip() self.presentGiftOrSuggestTooltip()
} else if case .broadcast = peer.info { } else if case .broadcast = peer.info {
self.giftButton.isHidden = true self.giftButton.isHidden = true
self.helpButton.isHidden = true self.helpButton.isHidden = true
self.suggestedPostButton.isHidden = false self.suggestedPostButton.isHidden = false
self.presentGiftOrSuggestTooltip()
} else if peer.flags.contains(.isGigagroup), self.action == .muteNotifications || self.action == .unmuteNotifications { } else if peer.flags.contains(.isGigagroup), self.action == .muteNotifications || self.action == .unmuteNotifications {
self.giftButton.isHidden = true self.giftButton.isHidden = true
self.helpButton.isHidden = false self.helpButton.isHidden = false

View File

@ -32,6 +32,7 @@ swift_library(
"//submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem", "//submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem",
"//submodules/PremiumUI", "//submodules/PremiumUI",
"//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/LottieComponent",
"//submodules/Components/BundleIconComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -23,6 +23,7 @@ import ChatMediaInputStickerGridItem
import UndoUI import UndoUI
import PremiumUI import PremiumUI
import LottieComponent import LottieComponent
import BundleIconComponent
private protocol ChatEmptyNodeContent { private protocol ChatEmptyNodeContent {
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize
@ -1367,16 +1368,27 @@ public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatE
contentsHeight += iconBackgroundSize contentsHeight += iconBackgroundSize
contentsHeight += iconTextSpacing contentsHeight += iconTextSpacing
let iconSize = self.icon.update( let iconComponent: AnyComponent<Empty>
transition: .immediate, if case let .customChatContents(customChatContents) = interfaceState.subject, case .postSuggestions = customChatContents.kind {
component: AnyComponent( iconComponent = AnyComponent(
BundleIconComponent(
name: "Chat/Empty Chat/PostSuggestions",
tintColor: serviceColor.primaryText
)
)
} else {
iconComponent = AnyComponent(
LottieComponent( LottieComponent(
content: LottieComponent.AppBundleContent(name: "PremiumRequired"), content: LottieComponent.AppBundleContent(name: "PremiumRequired"),
color: serviceColor.primaryText, color: serviceColor.primaryText,
size: CGSize(width: 120.0, height: 120.0), size: CGSize(width: 120.0, height: 120.0),
loop: true loop: true
) )
), )
}
let iconSize = self.icon.update(
transition: .immediate,
component: iconComponent,
environment: {}, environment: {},
containerSize: CGSize(width: maxWidth - sideInset * 2.0, height: 500.0) containerSize: CGSize(width: maxWidth - sideInset * 2.0, height: 500.0)
) )

View File

@ -26,6 +26,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
private let backgroundNode: ASDisplayNode private let backgroundNode: ASDisplayNode
private let contentBackgroundNode: ASDisplayNode private let contentBackgroundNode: ASDisplayNode
private let titleNode: ASTextNode private let titleNode: ASTextNode
private let textNode: ASTextNode?
private let cancelButton: HighlightableButtonNode private let cancelButton: HighlightableButtonNode
private let doneButton: SolidRoundedButtonNode private let doneButton: SolidRoundedButtonNode
private let onlineButton: SolidRoundedButtonNode private let onlineButton: SolidRoundedButtonNode
@ -93,6 +94,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
self.contentBackgroundNode.backgroundColor = backgroundColor self.contentBackgroundNode.backgroundColor = backgroundColor
let title: String let title: String
var text: String?
switch mode { switch mode {
case .scheduledMessages: case .scheduledMessages:
title = self.presentationData.strings.Conversation_ScheduleMessage_Title title = self.presentationData.strings.Conversation_ScheduleMessage_Title
@ -101,6 +103,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
case .suggestPost: case .suggestPost:
//TODO:localize //TODO:localize
title = "Time" title = "Time"
text = "Set the date and time you want\nyour message to be published."
} }
self.titleNode = ASTextNode() self.titleNode = ASTextNode()
@ -108,6 +111,19 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
self.titleNode.accessibilityLabel = title self.titleNode.accessibilityLabel = title
self.titleNode.accessibilityTraits = [.staticText] self.titleNode.accessibilityTraits = [.staticText]
if let text {
let textNode = ASTextNode()
textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: textColor)
textNode.maximumNumberOfLines = 0
textNode.textAlignment = .center
textNode.lineSpacing = 0.2
textNode.accessibilityLabel = text
textNode.accessibilityTraits = [.staticText]
self.textNode = textNode
} else {
self.textNode = nil
}
self.cancelButton = HighlightableButtonNode() self.cancelButton = HighlightableButtonNode()
self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: accentColor, for: .normal) self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: accentColor, for: .normal)
self.cancelButton.accessibilityLabel = self.presentationData.strings.Common_Cancel self.cancelButton.accessibilityLabel = self.presentationData.strings.Common_Cancel
@ -146,6 +162,9 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
self.backgroundNode.addSubnode(self.effectNode) self.backgroundNode.addSubnode(self.effectNode)
self.backgroundNode.addSubnode(self.contentBackgroundNode) self.backgroundNode.addSubnode(self.contentBackgroundNode)
self.contentContainerNode.addSubnode(self.titleNode) self.contentContainerNode.addSubnode(self.titleNode)
if let textNode = self.textNode {
self.contentContainerNode.addSubnode(textNode)
}
self.contentContainerNode.addSubnode(self.cancelButton) self.contentContainerNode.addSubnode(self.cancelButton)
self.contentContainerNode.addSubnode(self.doneButton) self.contentContainerNode.addSubnode(self.doneButton)
if case .scheduledMessages(true) = self.mode { if case .scheduledMessages(true) = self.mode {
@ -422,6 +441,13 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
} }
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0) let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0)
let textControlSpacing: CGFloat = -8.0
let textDoneSpacing: CGFloat = 21.0
let textSize = self.textNode?.measure(CGSize(width: width, height: 1000.0))
if let textSize {
contentHeight += textSize.height + textControlSpacing + textDoneSpacing
}
let sideInset = floor((layout.size.width - width) / 2.0) let sideInset = floor((layout.size.width - width) / 2.0)
let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight)) let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight))
let contentFrame = contentContainerFrame let contentFrame = contentContainerFrame
@ -446,7 +472,13 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
let buttonInset: CGFloat = 16.0 let buttonInset: CGFloat = 16.0
let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - insets.bottom - 16.0 - buttonOffset, width: contentFrame.width, height: doneButtonHeight)) let doneButtonFrame = CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - insets.bottom - 16.0 - buttonOffset, width: contentFrame.width, height: doneButtonHeight)
transition.updateFrame(node: self.doneButton, frame: doneButtonFrame)
if let textNode = self.textNode, let textSize {
let textFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - textSize.width) / 2.0), y: doneButtonFrame.minY - textDoneSpacing - textSize.height), size: textSize)
transition.updateFrame(node: textNode, frame: textFrame)
}
let onlineButtonHeight = self.onlineButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) let onlineButtonHeight = self.onlineButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.onlineButton, frame: CGRect(x: buttonInset, y: contentHeight - onlineButtonHeight - cleanInsets.bottom - 16.0, width: contentFrame.width, height: onlineButtonHeight)) transition.updateFrame(node: self.onlineButton, frame: CGRect(x: buttonInset, y: contentHeight - onlineButtonHeight - cleanInsets.bottom - 16.0, width: contentFrame.width, height: onlineButtonHeight))

View File

@ -23,6 +23,7 @@ swift_library(
"//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/Components/BundleIconComponent", "//submodules/Components/BundleIconComponent",
"//submodules/Components/MultilineTextComponent", "//submodules/Components/MultilineTextComponent",
"//submodules/TelegramUI/Components/ListItemComponentAdaptor",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -12,11 +12,12 @@ import ComponentFlow
import ButtonComponent import ButtonComponent
import BundleIconComponent import BundleIconComponent
import MultilineTextComponent import MultilineTextComponent
import ListItemComponentAdaptor
private let textFont = Font.with(size: 17.0, traits: .monospacedNumbers) private let textFont = Font.with(size: 17.0, traits: .monospacedNumbers)
private let smallTextFont = Font.with(size: 13.0, traits: .monospacedNumbers) private let smallTextFont = Font.with(size: 13.0, traits: .monospacedNumbers)
public final class MessagePriceItem: ListViewItem, ItemListItem { public final class MessagePriceItem: Equatable, ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator {
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings let strings: PresentationStrings
let isEnabled: Bool let isEnabled: Bool
@ -75,11 +76,46 @@ public final class MessagePriceItem: ListViewItem, ItemListItem {
} }
} }
} }
public func item() -> ListViewItem {
return self
}
public static func ==(lhs: MessagePriceItem, rhs: MessagePriceItem) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.isEnabled != rhs.isEnabled {
return false
}
if lhs.minValue != rhs.minValue {
return false
}
if lhs.value != rhs.value {
return false
}
if lhs.price != rhs.price {
return false
}
if (lhs.openSetCustom == nil) != (rhs.openSetCustom == nil) {
return false
}
if (lhs.openPremiumInfo == nil) != (rhs.openPremiumInfo == nil) {
return false
}
return true
}
} }
private class MessagePriceItemNode: ListViewItemNode { private class MessagePriceItemNode: ListViewItemNode {
private struct Amount: Equatable { private struct Amount: Equatable {
private let sliderSteps: [Int] private let sliderSteps: [Int]
private let minRealValue: Int
private let maxRealValue: Int private let maxRealValue: Int
let maxSliderValue: Int let maxSliderValue: Int
private let isLogarithmic: Bool private let isLogarithmic: Bool
@ -87,9 +123,9 @@ private class MessagePriceItemNode: ListViewItemNode {
private(set) var realValue: Int private(set) var realValue: Int
private(set) var sliderValue: Int private(set) var sliderValue: Int
private static func makeSliderSteps(maxRealValue: Int, isLogarithmic: Bool) -> [Int] { private static func makeSliderSteps(minRealValue: Int, maxRealValue: Int, isLogarithmic: Bool) -> [Int] {
if isLogarithmic { if isLogarithmic {
var sliderSteps: [Int] = [ 1, 10, 50, 100, 500, 1_000, 2_000, 5_000, 7_500, 10_000 ] var sliderSteps: [Int] = [ minRealValue, 10, 50, 100, 500, 1_000, 2_000, 5_000, 7_500, 10_000 ]
sliderSteps.removeAll(where: { $0 >= maxRealValue }) sliderSteps.removeAll(where: { $0 >= maxRealValue })
sliderSteps.append(maxRealValue) sliderSteps.append(maxRealValue)
return sliderSteps return sliderSteps
@ -126,8 +162,9 @@ private class MessagePriceItemNode: ListViewItemNode {
} }
} }
init(realValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) { init(realValue: Int, minRealValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) {
self.sliderSteps = Amount.makeSliderSteps(maxRealValue: maxRealValue, isLogarithmic: isLogarithmic) self.sliderSteps = Amount.makeSliderSteps(minRealValue: minRealValue, maxRealValue: maxRealValue, isLogarithmic: isLogarithmic)
self.minRealValue = minRealValue
self.maxRealValue = maxRealValue self.maxRealValue = maxRealValue
self.maxSliderValue = maxSliderValue self.maxSliderValue = maxSliderValue
self.isLogarithmic = isLogarithmic self.isLogarithmic = isLogarithmic
@ -136,8 +173,9 @@ private class MessagePriceItemNode: ListViewItemNode {
self.sliderValue = Amount.remapValueToSlider(realValue: self.realValue, maxSliderValue: self.maxSliderValue, steps: self.sliderSteps) self.sliderValue = Amount.remapValueToSlider(realValue: self.realValue, maxSliderValue: self.maxSliderValue, steps: self.sliderSteps)
} }
init(sliderValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) { init(sliderValue: Int, minRealValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) {
self.sliderSteps = Amount.makeSliderSteps(maxRealValue: maxRealValue, isLogarithmic: isLogarithmic) self.sliderSteps = Amount.makeSliderSteps(minRealValue: minRealValue, maxRealValue: maxRealValue, isLogarithmic: isLogarithmic)
self.minRealValue = minRealValue
self.maxRealValue = maxRealValue self.maxRealValue = maxRealValue
self.maxSliderValue = maxSliderValue self.maxSliderValue = maxSliderValue
self.isLogarithmic = isLogarithmic self.isLogarithmic = isLogarithmic
@ -147,11 +185,11 @@ private class MessagePriceItemNode: ListViewItemNode {
} }
func withRealValue(_ realValue: Int) -> Amount { func withRealValue(_ realValue: Int) -> Amount {
return Amount(realValue: realValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic) return Amount(realValue: realValue, minRealValue: self.minRealValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic)
} }
func withSliderValue(_ sliderValue: Int) -> Amount { func withSliderValue(_ sliderValue: Int) -> Amount {
return Amount(sliderValue: sliderValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic) return Amount(sliderValue: sliderValue, minRealValue: self.minRealValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic)
} }
} }
@ -171,7 +209,7 @@ private class MessagePriceItemNode: ListViewItemNode {
private let button: ComponentView<Empty> private let button: ComponentView<Empty>
private var amount: Amount = Amount(realValue: 1, maxRealValue: 1000, maxSliderValue: 1000, isLogarithmic: true) private var amount: Amount = Amount(realValue: 1, minRealValue: 1, maxRealValue: 1000, maxSliderValue: 1000, isLogarithmic: true)
private var item: MessagePriceItem? private var item: MessagePriceItem?
private var layoutParams: ListViewItemLayoutParams? private var layoutParams: ListViewItemLayoutParams?
@ -195,7 +233,9 @@ private class MessagePriceItemNode: ListViewItemNode {
self.centerTextButtonBackground = UIImageView() self.centerTextButtonBackground = UIImageView()
self.centerLeftTextNode = ImmediateTextNode() self.centerLeftTextNode = ImmediateTextNode()
self.centerLeftTextNode.isUserInteractionEnabled = false self.centerLeftTextNode.isUserInteractionEnabled = false
self.centerLeftTextNode.displaysAsynchronously = false
self.centerRightTextNode = ImmediateTextNode() self.centerRightTextNode = ImmediateTextNode()
self.centerRightTextNode.displaysAsynchronously = false
self.centerRightTextNode.isUserInteractionEnabled = false self.centerRightTextNode.isUserInteractionEnabled = false
self.lockIconNode = ASImageNode() self.lockIconNode = ASImageNode()
@ -226,7 +266,7 @@ private class MessagePriceItemNode: ListViewItemNode {
sliderView.lineSize = 4.0 sliderView.lineSize = 4.0
sliderView.disablesInteractiveTransitionGestureRecognizer = true sliderView.disablesInteractiveTransitionGestureRecognizer = true
if let item = self.item, let params = self.layoutParams { if let item = self.item, let params = self.layoutParams {
self.amount = Amount(realValue: Int(item.value), maxRealValue: Int(item.maxValue), maxSliderValue: 999, isLogarithmic: true) self.amount = Amount(realValue: Int(item.value), minRealValue: Int(item.minValue), maxRealValue: Int(item.maxValue), maxSliderValue: 999, isLogarithmic: true)
sliderView.minimumValue = 0 sliderView.minimumValue = 0
sliderView.startValue = 0 sliderView.startValue = 0
@ -328,7 +368,9 @@ private class MessagePriceItemNode: ListViewItemNode {
strongSelf.leftTextNode.attributedText = NSAttributedString(string: "\(item.minValue)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor) strongSelf.leftTextNode.attributedText = NSAttributedString(string: "\(item.minValue)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor)
strongSelf.rightTextNode.attributedText = NSAttributedString(string: "\(item.maxValue)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor) strongSelf.rightTextNode.attributedText = NSAttributedString(string: "\(item.maxValue)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor)
let centralLeftText = item.strings.Privacy_Messages_Stars(Int32(item.value)) //TODO:localize
let centralLeftText = item.value == 0 ? "Free" : item.strings.Privacy_Messages_Stars(Int32(item.value))
strongSelf.centerLeftTextNode.attributedText = NSAttributedString(string: centralLeftText, font: textFont, textColor: item.openSetCustom != nil ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor) strongSelf.centerLeftTextNode.attributedText = NSAttributedString(string: centralLeftText, font: textFont, textColor: item.openSetCustom != nil ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor)
strongSelf.centerRightTextNode.attributedText = NSAttributedString(string: item.price, font: smallTextFont, textColor: item.openSetCustom != nil ? item.theme.list.itemAccentColor.withMultipliedAlpha(0.5) : item.theme.list.itemSecondaryTextColor) strongSelf.centerRightTextNode.attributedText = NSAttributedString(string: item.price, font: smallTextFont, textColor: item.openSetCustom != nil ? item.theme.list.itemAccentColor.withMultipliedAlpha(0.5) : item.theme.list.itemSecondaryTextColor)
@ -336,7 +378,7 @@ private class MessagePriceItemNode: ListViewItemNode {
let rightTextSize = strongSelf.rightTextNode.updateLayout(CGSize(width: 100.0, height: 100.0)) let rightTextSize = strongSelf.rightTextNode.updateLayout(CGSize(width: 100.0, height: 100.0))
let centerLeftTextSize = strongSelf.centerLeftTextNode.updateLayout(CGSize(width: 200.0, height: 100.0)) let centerLeftTextSize = strongSelf.centerLeftTextNode.updateLayout(CGSize(width: 200.0, height: 100.0))
let centerRightTextSize = strongSelf.centerRightTextNode.updateLayout(CGSize(width: 200.0, height: 100.0)) let centerRightTextSize = strongSelf.centerRightTextNode.updateLayout(CGSize(width: 200.0, height: 100.0))
let centerSpacing: CGFloat = 6.0 let centerSpacing: CGFloat = item.price.isEmpty ? 0.0 : 6.0
let sideInset: CGFloat = 18.0 let sideInset: CGFloat = 18.0
@ -386,7 +428,7 @@ private class MessagePriceItemNode: ListViewItemNode {
} }
if !sliderView.isTracking { if !sliderView.isTracking {
strongSelf.amount = Amount(realValue: Int(item.value), maxRealValue: Int(item.maxValue), maxSliderValue: 999, isLogarithmic: true) strongSelf.amount = Amount(realValue: Int(item.value), minRealValue: Int(item.minValue), maxRealValue: Int(item.maxValue), maxSliderValue: 999, isLogarithmic: true)
sliderView.value = CGFloat(strongSelf.amount.sliderValue) sliderView.value = CGFloat(strongSelf.amount.sliderValue)
} }
} }

View File

@ -2207,7 +2207,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
})) }))
//TODO:localize //TODO:localize
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPostSuggestionsSettings, label: .text("Off"), additionalBadgeLabel: presentationData.strings.Settings_New, text: "Post Suggestions", icon: UIImage(bundleImageName: "Chat/Info/GroupDiscussionIcon"), action: { items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPostSuggestionsSettings, label: .text("Off"), additionalBadgeLabel: presentationData.strings.Settings_New, text: "Post Suggestions", icon: UIImage(bundleImageName: "Chat/Info/PostSuggestionsIcon"), action: {
interaction.editingOpenPostSuggestionsSetup() interaction.editingOpenPostSuggestionsSetup()
})) }))
} }

View File

@ -27,10 +27,11 @@ swift_library(
"//submodules/Components/BundleIconComponent", "//submodules/Components/BundleIconComponent",
"//submodules/TextFormat", "//submodules/TextFormat",
"//submodules/TelegramUI/Components/ListSectionComponent", "//submodules/TelegramUI/Components/ListSectionComponent",
"//submodules/TelegramUI/Components/ListItemSliderSelectorComponent",
"//submodules/TelegramUI/Components/ListSwitchItemComponent", "//submodules/TelegramUI/Components/ListSwitchItemComponent",
"//submodules/TelegramUI/Components/ListActionItemComponent", "//submodules/TelegramUI/Components/ListActionItemComponent",
"//submodules/TelegramStringFormatting", "//submodules/TelegramStringFormatting",
"//submodules/TelegramUI/Components/ListItemComponentAdaptor",
"//submodules/TelegramUI/Components/PeerInfo/MessagePriceItem",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -17,23 +17,27 @@ import ListSectionComponent
import BundleIconComponent import BundleIconComponent
import LottieComponent import LottieComponent
import ListSwitchItemComponent import ListSwitchItemComponent
import ListItemSliderSelectorComponent
import ListSwitchItemComponent import ListSwitchItemComponent
import ListActionItemComponent import ListActionItemComponent
import Markdown import Markdown
import TelegramStringFormatting import TelegramStringFormatting
import MessagePriceItem
import ListItemComponentAdaptor
final class PostSuggestionsSettingsScreenComponent: Component { final class PostSuggestionsSettingsScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext let context: AccountContext
let usdWithdrawRate: Int64
let completion: () -> Void let completion: () -> Void
init( init(
context: AccountContext, context: AccountContext,
usdWithdrawRate: Int64,
completion: @escaping () -> Void completion: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.usdWithdrawRate = usdWithdrawRate
self.completion = completion self.completion = completion
} }
@ -315,26 +319,50 @@ final class PostSuggestionsSettingsScreenComponent: Component {
var contentSectionItems: [AnyComponentWithIdentity<Empty>] = [] var contentSectionItems: [AnyComponentWithIdentity<Empty>] = []
let sliderValueList = (0 ... 10000).map { i -> String in let usdRate = Double(component.usdWithdrawRate) / 1000.0 / 100.0
return "\(i)" let price = self.starCount == 0 ? "" : "\(formatTonUsdValue(Int64(self.starCount), divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))"
}
//TODO:localize
let sliderTitle: String
let sliderSecondaryTitle: String?
let usdAmount = Double(self.starCount) * 0.013
let usdAmountString = formatCurrencyAmount(Int64(usdAmount * 100.0), currency: "USD")
if self.starCount == 0 {
sliderTitle = "Free"
sliderSecondaryTitle = nil
} else if self.starCount == 1 {
sliderTitle = "\(self.starCount) Star"
sliderSecondaryTitle = "~\(usdAmountString)"
} else {
sliderTitle = "\(self.starCount) Stars"
sliderSecondaryTitle = "~\(usdAmountString)"
}
contentSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemSliderSelectorComponent( contentSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor(
itemGenerator: MessagePriceItem(
theme: environment.theme,
strings: environment.strings,
isEnabled: true, minValue: 0, maxValue: 10000,
value: Int64(self.starCount),
price: price,
sectionId: 0,
updated: { [weak self] value, _ in
guard let self else {
return
}
self.starCount = Int(value)
if !self.isUpdating {
self.state?.updated(transition: .immediate)
}
},
openSetCustom: { [weak self] in
guard let self, let component = self.component, let environment = self.environment else {
return
}
let currentAmount: StarsAmount = StarsAmount(value: Int64(self.starCount), nanos: 0)
let starsScreen = component.context.sharedContext.makeStarsWithdrawalScreen(context: component.context, subject: .enterAmount(current: currentAmount, minValue: StarsAmount(value: 0, nanos: 0), fractionAfterCommission: 85, kind: .postSuggestion), completion: { [weak self] amount in
guard let self else {
return
}
self.starCount = Int(amount)
if !self.isUpdating {
self.state?.updated(transition: .immediate)
}
})
environment.controller()?.push(starsScreen)
},
openPremiumInfo: nil
),
params: ListViewItemLayoutParams(width: availableSize.width - sideInset * 2.0, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true)
))))
/*contentSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemSliderSelectorComponent(
theme: environment.theme, theme: environment.theme,
content: .discrete(ListItemSliderSelectorComponent.Discrete( content: .discrete(ListItemSliderSelectorComponent.Discrete(
values: sliderValueList.map { item in values: sliderValueList.map { item in
@ -353,7 +381,7 @@ final class PostSuggestionsSettingsScreenComponent: Component {
self.state?.updated(transition: .immediate) self.state?.updated(transition: .immediate)
} }
)) ))
)))) ))))*/
let contentSectionSize = self.contentSection.update( let contentSectionSize = self.contentSection.update(
transition: transition, transition: transition,
@ -445,8 +473,11 @@ public final class PostSuggestionsSettingsScreen: ViewControllerComponentContain
) { ) {
self.context = context self.context = context
let configuration = StarsSubscriptionConfiguration.with(appConfiguration: context.currentAppConfiguration.with({ $0 }))
super.init(context: context, component: PostSuggestionsSettingsScreenComponent( super.init(context: context, component: PostSuggestionsSettingsScreenComponent(
context: context, context: context,
usdWithdrawRate: configuration.usdWithdrawRate,
completion: completion completion: completion
), navigationBarAppearance: .default, theme: .default, updatedPresentationData: nil) ), navigationBarAppearance: .default, theme: .default, updatedPresentationData: nil)

View File

@ -156,13 +156,18 @@ private final class SheetContent: CombinedComponent {
minAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMinAmount, nanos: 0) minAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMinAmount, nanos: 0)
maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxAmount, nanos: 0) maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxAmount, nanos: 0)
amountLabel = nil amountLabel = nil
case .paidMessages: case let .paidMessages(_, minAmountValue, _, kind):
//TODO:localize //TODO:localize
titleString = "Price per Message" switch kind {
case .privacy:
titleString = "Price per Message"
case .postSuggestion:
titleString = "Price for each Suggestion"
}
amountTitle = "PRICE IN STARS" amountTitle = "PRICE IN STARS"
amountPlaceholder = "Enter Price" amountPlaceholder = "Enter Price"
minAmount = StarsAmount(value: 1, nanos: 0) minAmount = StarsAmount(value: minAmountValue, nanos: 0)
maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0) maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0)
amountLabel = nil amountLabel = nil
} }
@ -289,10 +294,10 @@ private final class SheetContent: CombinedComponent {
text: .plain(amountInfoString), text: .plain(amountInfoString),
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)) ))
case .paidMessages: case let .paidMessages(_, _, fractionAfterCommission, _):
let amountInfoString: NSAttributedString let amountInfoString: NSAttributedString
if let value = state.amount?.value, value > 0 { if let value = state.amount?.value, value > 0 {
let fullValue: Int64 = Int64(value) * 1_000_000_000 * 80 / 100 let fullValue: Int64 = Int64(value) * 1_000_000_000 * Int64(fractionAfterCommission) / 100
let amountValue = StarsAmount(value: fullValue / 1_000_000_000, nanos: Int32(fullValue % 1_000_000_000)) let amountValue = StarsAmount(value: fullValue / 1_000_000_000, nanos: Int32(fullValue % 1_000_000_000))
amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("You will receive **\(amountValue) Stars**.", attributes: amountMarkdownAttributes, textAlignment: .natural)) amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("You will receive **\(amountValue) Stars**.", attributes: amountMarkdownAttributes, textAlignment: .natural))
} else { } else {
@ -457,7 +462,7 @@ private final class SheetContent: CombinedComponent {
amount = nil amount = nil
case .starGiftResell: case .starGiftResell:
amount = nil amount = nil
case let .paidMessages(initialValue): case let .paidMessages(initialValue, _, _, _):
amount = StarsAmount(value: initialValue, nanos: 0) amount = StarsAmount(value: initialValue, nanos: 0)
} }
@ -580,7 +585,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
case paidMedia(Int64?) case paidMedia(Int64?)
case reaction(Int64?) case reaction(Int64?)
case starGiftResell(Bool) case starGiftResell(Bool)
case paidMessages(Int64) case paidMessages(current: Int64, minValue: Int64, fractionAfterCommission: Int, kind: StarsWithdrawalScreenSubject.PaidMessageKind)
} }
private let context: AccountContext private let context: AccountContext

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "postsuggestions.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_discussion.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "postsuggestionsbutton.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -2111,6 +2111,36 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
case .postSuggestions: case .postSuggestions:
//TODO:release //TODO:release
actions.removeAll() actions.removeAll()
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_MessageDialogEdit, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor)
}, action: { c, f in
interfaceInteraction.setupEditMessage(messages[0].id, { transition in
f(.custom(transition))
})
})))
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_EditTime, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Schedule"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
controllerInteraction.editScheduledMessagesTime(messages.map { $0.id })
f(.dismissWithoutContent)
})))
actions.append(.action(ContextMenuActionItem(text: "Edit Price", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Tag"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.dismissWithoutContent)
})))
actions.append(.action(ContextMenuActionItem(text: "Delete", textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { controller, f in
interfaceInteraction.deleteMessages(messages, controller, f)
})))
actions.append(.separator)
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let action: ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)? = nil
actions.append(.action(ContextMenuActionItem(text: "Deleting suggested post will auto-refund your order.", textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
return nil
}, iconSource: nil, action: action)))
} }
} }

View File

@ -26,6 +26,7 @@ swift_library(
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
"//submodules/Components/BalancedTextComponent", "//submodules/Components/BalancedTextComponent",
"//submodules/Components/MultilineTextWithEntitiesComponent", "//submodules/Components/MultilineTextWithEntitiesComponent",
"//submodules/Components/MultilineTextComponent",
"//submodules/ShimmerEffect", "//submodules/ShimmerEffect",
], ],
visibility = [ visibility = [

View File

@ -17,6 +17,7 @@ import AvatarStoryIndicatorComponent
import AccountContext import AccountContext
import Markdown import Markdown
import BalancedTextComponent import BalancedTextComponent
import MultilineTextComponent
import MultilineTextWithEntitiesComponent import MultilineTextWithEntitiesComponent
import ShimmerEffect import ShimmerEffect
@ -113,6 +114,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
private let context: AccountContext? private let context: AccountContext?
private let text: TooltipScreen.Text private let text: TooltipScreen.Text
private let textBadge: String?
private let textAlignment: TooltipScreen.Alignment private let textAlignment: TooltipScreen.Alignment
private let balancedTextLayout: Bool private let balancedTextLayout: Bool
private let constrainWidth: CGFloat? private let constrainWidth: CGFloat?
@ -148,6 +150,8 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
private var avatarNode: AvatarNode? private var avatarNode: AvatarNode?
private var avatarStoryIndicator: ComponentView<Empty>? private var avatarStoryIndicator: ComponentView<Empty>?
private let textView = ComponentView<Empty>() private let textView = ComponentView<Empty>()
private var textBadgeView: ComponentView<Empty>?
private var textBadgeBackgroundView: ComponentView<Empty>?
private var closeButtonNode: HighlightableButtonNode? private var closeButtonNode: HighlightableButtonNode?
private var actionButtonNode: HighlightableButtonNode? private var actionButtonNode: HighlightableButtonNode?
@ -166,6 +170,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
account: Account, account: Account,
sharedContext: SharedAccountContext, sharedContext: SharedAccountContext,
text: TooltipScreen.Text, text: TooltipScreen.Text,
textBadge: String?,
textAlignment: TooltipScreen.Alignment, textAlignment: TooltipScreen.Alignment,
balancedTextLayout: Bool, balancedTextLayout: Bool,
constrainWidth: CGFloat?, constrainWidth: CGFloat?,
@ -390,6 +395,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
self.fontSize = fontSize self.fontSize = fontSize
self.text = text self.text = text
self.textBadge = textBadge
self.textAlignment = textAlignment self.textAlignment = textAlignment
self.balancedTextLayout = balancedTextLayout self.balancedTextLayout = balancedTextLayout
self.constrainWidth = constrainWidth self.constrainWidth = constrainWidth
@ -658,18 +664,48 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
) )
} }
let textBadgeSpacing: CGFloat = 9.0
let textBadgeRightInset: CGFloat = 5.0
var textContentSize = textSize
var textBadgeSize: CGSize?
if let textBadge = self.textBadge {
let textBadgeView: ComponentView<Empty>
if let current = self.textBadgeView {
textBadgeView = current
} else {
textBadgeView = ComponentView()
self.textBadgeView = textBadgeView
}
let textBadgeSizeValue = textBadgeView.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: textBadge, font: Font.semibold(floor(self.fontSize * 0.8)), textColor: textColor))
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
textBadgeSize = textBadgeSizeValue
textContentSize.width += textBadgeSpacing + textBadgeSizeValue.width + textBadgeRightInset
} else {
if let textBadgeView = self.textBadgeView {
self.textBadgeView = nil
textBadgeView.view?.removeFromSuperview()
}
}
var backgroundFrame: CGRect var backgroundFrame: CGRect
var backgroundHeight: CGFloat var backgroundHeight: CGFloat
switch self.tooltipStyle { switch self.tooltipStyle {
case .default, .gradient: case .default, .gradient:
backgroundHeight = max(animationSize.height, textSize.height) + contentVerticalInset * 2.0 backgroundHeight = max(animationSize.height, textContentSize.height) + contentVerticalInset * 2.0
case .wide: case .wide:
backgroundHeight = max(animationSize.height, textSize.height) + contentVerticalInset * 2.0 + 4.0 backgroundHeight = max(animationSize.height, textContentSize.height) + contentVerticalInset * 2.0 + 4.0
case let .customBlur(_, inset): case let .customBlur(_, inset):
backgroundHeight = max(animationSize.height, textSize.height) + contentVerticalInset * 2.0 + inset * 2.0 backgroundHeight = max(animationSize.height, textContentSize.height) + contentVerticalInset * 2.0 + inset * 2.0
case .light: case .light:
backgroundHeight = max(28.0, max(animationSize.height, textSize.height) + 4.0 * 2.0) backgroundHeight = max(28.0, max(animationSize.height, textContentSize.height) + 4.0 * 2.0)
} }
if self.actionButtonNode != nil { if self.actionButtonNode != nil {
backgroundHeight += 4.0 backgroundHeight += 4.0
@ -678,7 +714,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
var invertArrow = false var invertArrow = false
switch self.location { switch self.location {
case let .point(rect, arrowPosition): case let .point(rect, arrowPosition):
var backgroundWidth = textSize.width + contentInset * 2.0 + animationSize.width + animationSpacing var backgroundWidth = textContentSize.width + contentInset * 2.0 + animationSize.width + animationSpacing
if self.closeButtonNode != nil || self.actionButtonNode != nil { if self.closeButtonNode != nil || self.actionButtonNode != nil {
backgroundWidth += buttonInset backgroundWidth += buttonInset
} }
@ -769,7 +805,6 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
} }
let textFrame = CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: floor((backgroundHeight - textSize.height) / 2.0)), size: textSize) let textFrame = CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: floor((backgroundHeight - textSize.height) / 2.0)), size: textSize)
if let textComponentView = self.textView.view { if let textComponentView = self.textView.view {
if textComponentView.superview == nil { if textComponentView.superview == nil {
textComponentView.layer.anchorPoint = CGPoint() textComponentView.layer.anchorPoint = CGPoint()
@ -779,6 +814,49 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
transition.updateBounds(layer: textComponentView.layer, bounds: CGRect(origin: CGPoint(), size: textFrame.size)) transition.updateBounds(layer: textComponentView.layer, bounds: CGRect(origin: CGPoint(), size: textFrame.size))
} }
if let textBadgeView = self.textBadgeView, let textBadgeSize {
let textBadgeFrame = CGRect(origin: CGPoint(x: textFrame.maxX + textBadgeSpacing, y: textFrame.minY + 2.0), size: textBadgeSize)
if let textBadgeComponentView = textBadgeView.view {
if textBadgeComponentView.superview == nil {
textBadgeComponentView.layer.anchorPoint = CGPoint()
self.containerNode.view.addSubview(textBadgeComponentView)
}
transition.updatePosition(layer: textBadgeComponentView.layer, position: textBadgeFrame.origin)
transition.updateBounds(layer: textBadgeComponentView.layer, bounds: CGRect(origin: CGPoint(), size: textBadgeFrame.size))
}
var textBadgeBackgroundFrame = textBadgeFrame.insetBy(dx: -4.0, dy: -3.0)
textBadgeBackgroundFrame.size.height -= UIScreenPixel
textBadgeBackgroundFrame.size.width -= UIScreenPixel
let textBadgeBackgroundView: ComponentView<Empty>
if let current = self.textBadgeBackgroundView {
textBadgeBackgroundView = current
} else {
textBadgeBackgroundView = ComponentView()
self.textBadgeBackgroundView = textBadgeBackgroundView
}
let _ = textBadgeBackgroundView.update(
transition: .immediate,
component: AnyComponent(FilledRoundedRectangleComponent(
color: UIColor(white: 1.0, alpha: 0.1),
cornerRadius: .value(5.0),
smoothCorners: true
)),
environment: {},
containerSize: textBadgeBackgroundFrame.size
)
if let textBadgeBackgroundComponentView = textBadgeBackgroundView.view {
if textBadgeBackgroundComponentView.superview == nil, let textBadgeComponentView = textBadgeView.view {
self.containerNode.view.insertSubview(textBadgeBackgroundComponentView, belowSubview: textBadgeComponentView)
}
textBadgeBackgroundComponentView.frame = textBadgeBackgroundFrame
}
} else if let textBadgeBackgroundView = self.textBadgeBackgroundView {
self.textBadgeBackgroundView = nil
textBadgeBackgroundView.view?.removeFromSuperview()
}
if let closeButtonNode = self.closeButtonNode { if let closeButtonNode = self.closeButtonNode {
let closeSize = CGSize(width: 44.0, height: 44.0) let closeSize = CGSize(width: 44.0, height: 44.0)
transition.updateFrame(node: closeButtonNode, frame: CGRect(origin: CGPoint(x: textFrame.maxX - 6.0, y: floor((backgroundHeight - closeSize.height) / 2.0)), size: closeSize)) transition.updateFrame(node: closeButtonNode, frame: CGRect(origin: CGPoint(x: textFrame.maxX - 6.0, y: floor((backgroundHeight - closeSize.height) / 2.0)), size: closeSize))
@ -1120,6 +1198,7 @@ public final class TooltipScreen: ViewController {
private let account: Account private let account: Account
private let sharedContext: SharedAccountContext private let sharedContext: SharedAccountContext
public let text: TooltipScreen.Text public let text: TooltipScreen.Text
private let textBadge: String?
public let textAlignment: TooltipScreen.Alignment public let textAlignment: TooltipScreen.Alignment
private let balancedTextLayout: Bool private let balancedTextLayout: Bool
private let constrainWidth: CGFloat? private let constrainWidth: CGFloat?
@ -1160,6 +1239,7 @@ public final class TooltipScreen: ViewController {
account: Account, account: Account,
sharedContext: SharedAccountContext, sharedContext: SharedAccountContext,
text: TooltipScreen.Text, text: TooltipScreen.Text,
textBadge: String? = nil,
textAlignment: TooltipScreen.Alignment = .natural, textAlignment: TooltipScreen.Alignment = .natural,
balancedTextLayout: Bool = false, balancedTextLayout: Bool = false,
constrainWidth: CGFloat? = nil, constrainWidth: CGFloat? = nil,
@ -1179,6 +1259,7 @@ public final class TooltipScreen: ViewController {
self.account = account self.account = account
self.sharedContext = sharedContext self.sharedContext = sharedContext
self.text = text self.text = text
self.textBadge = textBadge
self.textAlignment = textAlignment self.textAlignment = textAlignment
self.balancedTextLayout = balancedTextLayout self.balancedTextLayout = balancedTextLayout
self.constrainWidth = constrainWidth self.constrainWidth = constrainWidth
@ -1250,7 +1331,7 @@ public final class TooltipScreen: ViewController {
} }
override public func loadDisplayNode() { override public func loadDisplayNode() {
self.displayNode = TooltipScreenNode(context: self.context, account: self.account, sharedContext: self.sharedContext, text: self.text, textAlignment: self.textAlignment, balancedTextLayout: self.balancedTextLayout, constrainWidth: self.constrainWidth, style: self.style, arrowStyle: self.arrowStyle, icon: self.icon, action: self.action, location: self.location, displayDuration: self.displayDuration, inset: self.inset, cornerRadius: self.cornerRadius, isShimmering: self.isShimmering, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in self.displayNode = TooltipScreenNode(context: self.context, account: self.account, sharedContext: self.sharedContext, text: self.text, textBadge: self.textBadge, textAlignment: self.textAlignment, balancedTextLayout: self.balancedTextLayout, constrainWidth: self.constrainWidth, style: self.style, arrowStyle: self.arrowStyle, icon: self.icon, action: self.action, location: self.location, displayDuration: self.displayDuration, inset: self.inset, cornerRadius: self.cornerRadius, isShimmering: self.isShimmering, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }