diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index c217c4ee6f..1fbf3888c4 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -996,8 +996,13 @@ public enum SendInviteLinkScreenSubject { } public enum StarsWithdrawalScreenSubject { + public enum PaidMessageKind { + case privacy + case postSuggestion + } + case withdraw - case enterAmount(current: StarsAmount) + case enterAmount(current: StarsAmount, minValue: StarsAmount, fractionAfterCommission: Int, kind: PaidMessageKind) } public protocol SharedAccountContext: AnyObject { diff --git a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift index 6db96282fd..006d640616 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift @@ -376,7 +376,7 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP if case let .paidMessages(value) = stateValue.with({ $0 }).updatedValue { 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 var state = state state.updatedValue = .paidMessages(StarsAmount(value: amount, nanos: 0)) diff --git a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift index a1add0ec6b..993fab330b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift @@ -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) } - private var displayedGiftTooltip = false - private func presentGiftTooltip() { - guard let context = self.context, !self.displayedGiftTooltip, let parentController = self.interfaceInteraction?.chatController() else { + private var displayedGiftOrSuggestTooltip = false + private func presentGiftOrSuggestTooltip() { + guard let context = self.context, !self.displayedGiftOrSuggestTooltip, let parentController = self.interfaceInteraction?.chatController() else { return } - self.displayedGiftTooltip = true + self.displayedGiftOrSuggestTooltip = true - let _ = (ApplicationSpecificNotice.getChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager) - |> deliverOnMainQueue).start(next: { [weak self] count in + let _ = (combineLatest(queue: .mainQueue(), + ApplicationSpecificNotice.getChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager), + ApplicationSpecificNotice.getChannelSuggestTooltip(accountManager: context.sharedContext.accountManager) + |> deliverOnMainQueue)).start(next: { [weak self] giftCount, suggestCount in guard let self else { 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() - - 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()) + if giftCount < 2 && !self.giftButton.isHidden { + let _ = ApplicationSpecificNotice.incrementChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager).start() - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let text: String = presentationData.strings.Chat_SendGiftTooltip - - let tooltipController = TooltipScreen( - account: context.account, - sharedContext: context.sharedContext, - text: .plain(text: text), - 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) - }) + 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 } + let text: String = presentationData.strings.Chat_SendGiftTooltip + + let tooltipController = TooltipScreen( + account: context.account, + sharedContext: context.sharedContext, + text: .plain(text: text), + 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) + }) + } 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 { 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.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) } @@ -431,11 +471,12 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { self.helpButton.isHidden = true //TODO:release self.suggestedPostButton.isHidden = false - self.presentGiftTooltip() + self.presentGiftOrSuggestTooltip() } else if case .broadcast = peer.info { self.giftButton.isHidden = true self.helpButton.isHidden = true self.suggestedPostButton.isHidden = false + self.presentGiftOrSuggestTooltip() } else if peer.flags.contains(.isGigagroup), self.action == .muteNotifications || self.action == .unmuteNotifications { self.giftButton.isHidden = true self.helpButton.isHidden = false diff --git a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD index b374c14152..0d443277ba 100644 --- a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD @@ -32,6 +32,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem", "//submodules/PremiumUI", "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/Components/BundleIconComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift index 9bdc20deaf..f577d0ffc1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift @@ -23,6 +23,7 @@ import ChatMediaInputStickerGridItem import UndoUI import PremiumUI import LottieComponent +import BundleIconComponent private protocol ChatEmptyNodeContent { 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 += iconTextSpacing - let iconSize = self.icon.update( - transition: .immediate, - component: AnyComponent( + let iconComponent: AnyComponent + if case let .customChatContents(customChatContents) = interfaceState.subject, case .postSuggestions = customChatContents.kind { + iconComponent = AnyComponent( + BundleIconComponent( + name: "Chat/Empty Chat/PostSuggestions", + tintColor: serviceColor.primaryText + ) + ) + } else { + iconComponent = AnyComponent( LottieComponent( content: LottieComponent.AppBundleContent(name: "PremiumRequired"), color: serviceColor.primaryText, size: CGSize(width: 120.0, height: 120.0), loop: true ) - ), + ) + } + let iconSize = self.icon.update( + transition: .immediate, + component: iconComponent, environment: {}, containerSize: CGSize(width: maxWidth - sideInset * 2.0, height: 500.0) ) diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift index 88c6b9a864..5c117b39ca 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift @@ -26,6 +26,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel private let backgroundNode: ASDisplayNode private let contentBackgroundNode: ASDisplayNode private let titleNode: ASTextNode + private let textNode: ASTextNode? private let cancelButton: HighlightableButtonNode private let doneButton: SolidRoundedButtonNode private let onlineButton: SolidRoundedButtonNode @@ -93,6 +94,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel self.contentBackgroundNode.backgroundColor = backgroundColor let title: String + var text: String? switch mode { case .scheduledMessages: title = self.presentationData.strings.Conversation_ScheduleMessage_Title @@ -101,6 +103,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel case .suggestPost: //TODO:localize title = "Time" + text = "Set the date and time you want\nyour message to be published." } self.titleNode = ASTextNode() @@ -108,6 +111,19 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel self.titleNode.accessibilityLabel = title 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.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: accentColor, for: .normal) 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.contentBackgroundNode) self.contentContainerNode.addSubnode(self.titleNode) + if let textNode = self.textNode { + self.contentContainerNode.addSubnode(textNode) + } self.contentContainerNode.addSubnode(self.cancelButton) self.contentContainerNode.addSubnode(self.doneButton) if case .scheduledMessages(true) = self.mode { @@ -422,6 +441,13 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel } 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 contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight)) let contentFrame = contentContainerFrame @@ -446,7 +472,13 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel let buttonInset: CGFloat = 16.0 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) transition.updateFrame(node: self.onlineButton, frame: CGRect(x: buttonInset, y: contentHeight - onlineButtonHeight - cleanInsets.bottom - 16.0, width: contentFrame.width, height: onlineButtonHeight)) diff --git a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/BUILD b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/BUILD index adbe07190b..78ff393298 100644 --- a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/BUILD @@ -23,6 +23,7 @@ swift_library( "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/Components/BundleIconComponent", "//submodules/Components/MultilineTextComponent", + "//submodules/TelegramUI/Components/ListItemComponentAdaptor", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift index 09043395e5..d907298c7e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift @@ -12,11 +12,12 @@ import ComponentFlow import ButtonComponent import BundleIconComponent import MultilineTextComponent +import ListItemComponentAdaptor private let textFont = Font.with(size: 17.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 strings: PresentationStrings 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 struct Amount: Equatable { private let sliderSteps: [Int] + private let minRealValue: Int private let maxRealValue: Int let maxSliderValue: Int private let isLogarithmic: Bool @@ -87,9 +123,9 @@ private class MessagePriceItemNode: ListViewItemNode { private(set) var realValue: 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 { - 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.append(maxRealValue) return sliderSteps @@ -126,8 +162,9 @@ private class MessagePriceItemNode: ListViewItemNode { } } - init(realValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) { - self.sliderSteps = Amount.makeSliderSteps(maxRealValue: maxRealValue, isLogarithmic: isLogarithmic) + init(realValue: Int, minRealValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) { + self.sliderSteps = Amount.makeSliderSteps(minRealValue: minRealValue, maxRealValue: maxRealValue, isLogarithmic: isLogarithmic) + self.minRealValue = minRealValue self.maxRealValue = maxRealValue self.maxSliderValue = maxSliderValue self.isLogarithmic = isLogarithmic @@ -136,8 +173,9 @@ private class MessagePriceItemNode: ListViewItemNode { self.sliderValue = Amount.remapValueToSlider(realValue: self.realValue, maxSliderValue: self.maxSliderValue, steps: self.sliderSteps) } - init(sliderValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) { - self.sliderSteps = Amount.makeSliderSteps(maxRealValue: maxRealValue, isLogarithmic: isLogarithmic) + init(sliderValue: Int, minRealValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) { + self.sliderSteps = Amount.makeSliderSteps(minRealValue: minRealValue, maxRealValue: maxRealValue, isLogarithmic: isLogarithmic) + self.minRealValue = minRealValue self.maxRealValue = maxRealValue self.maxSliderValue = maxSliderValue self.isLogarithmic = isLogarithmic @@ -147,11 +185,11 @@ private class MessagePriceItemNode: ListViewItemNode { } 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 { - 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 - 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 layoutParams: ListViewItemLayoutParams? @@ -195,7 +233,9 @@ private class MessagePriceItemNode: ListViewItemNode { self.centerTextButtonBackground = UIImageView() self.centerLeftTextNode = ImmediateTextNode() self.centerLeftTextNode.isUserInteractionEnabled = false + self.centerLeftTextNode.displaysAsynchronously = false self.centerRightTextNode = ImmediateTextNode() + self.centerRightTextNode.displaysAsynchronously = false self.centerRightTextNode.isUserInteractionEnabled = false self.lockIconNode = ASImageNode() @@ -226,7 +266,7 @@ private class MessagePriceItemNode: ListViewItemNode { sliderView.lineSize = 4.0 sliderView.disablesInteractiveTransitionGestureRecognizer = true 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.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.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.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 centerLeftTextSize = strongSelf.centerLeftTextNode.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 @@ -386,7 +428,7 @@ private class MessagePriceItemNode: ListViewItemNode { } 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) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 2a7a6e720b..c8c38b1516 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -2207,7 +2207,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL })) //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() })) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/BUILD index eb9501ea4d..4166ff8413 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/BUILD @@ -27,10 +27,11 @@ swift_library( "//submodules/Components/BundleIconComponent", "//submodules/TextFormat", "//submodules/TelegramUI/Components/ListSectionComponent", - "//submodules/TelegramUI/Components/ListItemSliderSelectorComponent", "//submodules/TelegramUI/Components/ListSwitchItemComponent", "//submodules/TelegramUI/Components/ListActionItemComponent", "//submodules/TelegramStringFormatting", + "//submodules/TelegramUI/Components/ListItemComponentAdaptor", + "//submodules/TelegramUI/Components/PeerInfo/MessagePriceItem", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift index 7f29cb8160..7da2229daa 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift @@ -17,23 +17,27 @@ import ListSectionComponent import BundleIconComponent import LottieComponent import ListSwitchItemComponent -import ListItemSliderSelectorComponent import ListSwitchItemComponent import ListActionItemComponent import Markdown import TelegramStringFormatting +import MessagePriceItem +import ListItemComponentAdaptor final class PostSuggestionsSettingsScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let usdWithdrawRate: Int64 let completion: () -> Void init( context: AccountContext, + usdWithdrawRate: Int64, completion: @escaping () -> Void ) { self.context = context + self.usdWithdrawRate = usdWithdrawRate self.completion = completion } @@ -315,26 +319,50 @@ final class PostSuggestionsSettingsScreenComponent: Component { var contentSectionItems: [AnyComponentWithIdentity] = [] - let sliderValueList = (0 ... 10000).map { i -> String in - return "\(i)" - } - //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)" - } + let usdRate = Double(component.usdWithdrawRate) / 1000.0 / 100.0 + let price = self.starCount == 0 ? "" : "≈\(formatTonUsdValue(Int64(self.starCount), divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))" - 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, content: .discrete(ListItemSliderSelectorComponent.Discrete( values: sliderValueList.map { item in @@ -353,7 +381,7 @@ final class PostSuggestionsSettingsScreenComponent: Component { self.state?.updated(transition: .immediate) } )) - )))) + ))))*/ let contentSectionSize = self.contentSection.update( transition: transition, @@ -445,8 +473,11 @@ public final class PostSuggestionsSettingsScreen: ViewControllerComponentContain ) { self.context = context + let configuration = StarsSubscriptionConfiguration.with(appConfiguration: context.currentAppConfiguration.with({ $0 })) + super.init(context: context, component: PostSuggestionsSettingsScreenComponent( context: context, + usdWithdrawRate: configuration.usdWithdrawRate, completion: completion ), navigationBarAppearance: .default, theme: .default, updatedPresentationData: nil) diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 3b385b4213..5912a035b5 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -156,13 +156,18 @@ private final class SheetContent: CombinedComponent { minAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMinAmount, nanos: 0) maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxAmount, nanos: 0) amountLabel = nil - case .paidMessages: + case let .paidMessages(_, minAmountValue, _, kind): //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" amountPlaceholder = "Enter Price" - minAmount = StarsAmount(value: 1, nanos: 0) + minAmount = StarsAmount(value: minAmountValue, nanos: 0) maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0) amountLabel = nil } @@ -289,10 +294,10 @@ private final class SheetContent: CombinedComponent { text: .plain(amountInfoString), maximumNumberOfLines: 0 )) - case .paidMessages: + case let .paidMessages(_, _, fractionAfterCommission, _): let amountInfoString: NSAttributedString 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)) amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("You will receive **\(amountValue) Stars**.", attributes: amountMarkdownAttributes, textAlignment: .natural)) } else { @@ -457,7 +462,7 @@ private final class SheetContent: CombinedComponent { amount = nil case .starGiftResell: amount = nil - case let .paidMessages(initialValue): + case let .paidMessages(initialValue, _, _, _): amount = StarsAmount(value: initialValue, nanos: 0) } @@ -580,7 +585,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer { case paidMedia(Int64?) case reaction(Int64?) case starGiftResell(Bool) - case paidMessages(Int64) + case paidMessages(current: Int64, minValue: Int64, fractionAfterCommission: Int, kind: StarsWithdrawalScreenSubject.PaidMessageKind) } private let context: AccountContext diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/PostSuggestions.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/PostSuggestions.imageset/Contents.json new file mode 100644 index 0000000000..e4f0fa4d43 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/PostSuggestions.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "postsuggestions.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/PostSuggestions.imageset/postsuggestions.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/PostSuggestions.imageset/postsuggestions.pdf new file mode 100644 index 0000000000..84791cdcb9 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/PostSuggestions.imageset/postsuggestions.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Info/PostSuggestionsIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Info/PostSuggestionsIcon.imageset/Contents.json new file mode 100644 index 0000000000..a88c7d45f3 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Info/PostSuggestionsIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_discussion.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Info/PostSuggestionsIcon.imageset/ic_discussion.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Info/PostSuggestionsIcon.imageset/ic_discussion.pdf new file mode 100644 index 0000000000..ab7bf2f754 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Info/PostSuggestionsIcon.imageset/ic_discussion.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/SuggestPost.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/SuggestPost.imageset/Contents.json new file mode 100644 index 0000000000..b4dafac96c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/SuggestPost.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "postsuggestionsbutton.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/SuggestPost.imageset/postsuggestionsbutton.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/SuggestPost.imageset/postsuggestionsbutton.pdf new file mode 100644 index 0000000000..6adbed6a54 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/SuggestPost.imageset/postsuggestionsbutton.pdf differ diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 3115665dee..63b7c1c3d4 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -2111,6 +2111,36 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState case .postSuggestions: //TODO:release 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))) } } diff --git a/submodules/TooltipUI/BUILD b/submodules/TooltipUI/BUILD index 3233d2069b..a747e4fd7c 100644 --- a/submodules/TooltipUI/BUILD +++ b/submodules/TooltipUI/BUILD @@ -26,6 +26,7 @@ swift_library( "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", "//submodules/Components/BalancedTextComponent", "//submodules/Components/MultilineTextWithEntitiesComponent", + "//submodules/Components/MultilineTextComponent", "//submodules/ShimmerEffect", ], visibility = [ diff --git a/submodules/TooltipUI/Sources/TooltipScreen.swift b/submodules/TooltipUI/Sources/TooltipScreen.swift index 85d4e81d37..e94c4ddce0 100644 --- a/submodules/TooltipUI/Sources/TooltipScreen.swift +++ b/submodules/TooltipUI/Sources/TooltipScreen.swift @@ -17,6 +17,7 @@ import AvatarStoryIndicatorComponent import AccountContext import Markdown import BalancedTextComponent +import MultilineTextComponent import MultilineTextWithEntitiesComponent import ShimmerEffect @@ -113,6 +114,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { private let context: AccountContext? private let text: TooltipScreen.Text + private let textBadge: String? private let textAlignment: TooltipScreen.Alignment private let balancedTextLayout: Bool private let constrainWidth: CGFloat? @@ -148,6 +150,8 @@ private final class TooltipScreenNode: ViewControllerTracingNode { private var avatarNode: AvatarNode? private var avatarStoryIndicator: ComponentView? private let textView = ComponentView() + private var textBadgeView: ComponentView? + private var textBadgeBackgroundView: ComponentView? private var closeButtonNode: HighlightableButtonNode? private var actionButtonNode: HighlightableButtonNode? @@ -166,6 +170,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { account: Account, sharedContext: SharedAccountContext, text: TooltipScreen.Text, + textBadge: String?, textAlignment: TooltipScreen.Alignment, balancedTextLayout: Bool, constrainWidth: CGFloat?, @@ -390,6 +395,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { self.fontSize = fontSize self.text = text + self.textBadge = textBadge self.textAlignment = textAlignment self.balancedTextLayout = balancedTextLayout 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 + 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 backgroundHeight: CGFloat switch self.tooltipStyle { case .default, .gradient: - backgroundHeight = max(animationSize.height, textSize.height) + contentVerticalInset * 2.0 + backgroundHeight = max(animationSize.height, textContentSize.height) + contentVerticalInset * 2.0 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): - 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: - 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 { backgroundHeight += 4.0 @@ -678,7 +714,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { var invertArrow = false switch self.location { 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 { 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) - if let textComponentView = self.textView.view { if textComponentView.superview == nil { 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)) } + 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 + 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 { 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)) @@ -1120,6 +1198,7 @@ public final class TooltipScreen: ViewController { private let account: Account private let sharedContext: SharedAccountContext public let text: TooltipScreen.Text + private let textBadge: String? public let textAlignment: TooltipScreen.Alignment private let balancedTextLayout: Bool private let constrainWidth: CGFloat? @@ -1160,6 +1239,7 @@ public final class TooltipScreen: ViewController { account: Account, sharedContext: SharedAccountContext, text: TooltipScreen.Text, + textBadge: String? = nil, textAlignment: TooltipScreen.Alignment = .natural, balancedTextLayout: Bool = false, constrainWidth: CGFloat? = nil, @@ -1179,6 +1259,7 @@ public final class TooltipScreen: ViewController { self.account = account self.sharedContext = sharedContext self.text = text + self.textBadge = textBadge self.textAlignment = textAlignment self.balancedTextLayout = balancedTextLayout self.constrainWidth = constrainWidth @@ -1250,7 +1331,7 @@ public final class TooltipScreen: ViewController { } 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 { return }