From b5c3d6f34fc92e865f86ffbc510b1a41f7f356b1 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Thu, 5 Jun 2025 19:09:18 +0800 Subject: [PATCH] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 4 +- .../Sources/AccountContext.swift | 22 +++ .../Sources/ChatListController.swift | 3 + .../Sources/ChatListController.swift | 6 + .../ChatPresentationInterfaceState.swift | 24 --- .../TelegramEngine/Payments/GiftCodes.swift | 8 +- .../Sources/ChatLoadingNode.swift | 18 +- .../Sources/ChatMessageBubbleItemNode.swift | 14 +- .../Sources/ForumSettingsScreen.swift | 174 ++++++++++++------ .../PostSuggestionsSettingsScreen.swift | 10 +- .../Sources/StarsWithdrawalScreen.swift | 51 +++-- .../TelegramUI/Sources/ChatController.swift | 3 + .../Sources/ChatControllerContentData.swift | 32 +++- .../Sources/ChatControllerNode.swift | 2 +- 14 files changed, 248 insertions(+), 123 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 90d84f161d..f5381fdff4 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14381,7 +14381,7 @@ Sorry for the inconvenience."; "ChannelMessages.Info" = "Allow users to send messages to your channel, with the option to charge a fee for each message."; "ChannelMessages.SwitchTitle" = "Allow Channel Messages"; "ChannelMessages.PriceSectionTitle" = "PRICE FOR EACH MESSAGE"; -"ChannelMessages.PriceSectionFooter" = "You will receive 85% of the selected fee for each incoming message."; +"ChannelMessages.PriceSectionFooterValue" = "You will receive %1$@% of the selected fee for each incoming message."; "ChatList.MonoforumLabel" = "DIRECT"; "ChatList.MonoforumEmptyText" = "No messages here yet..."; @@ -14408,7 +14408,7 @@ Sorry for the inconvenience."; "Stars.SendMessage.AdjustmentPlaceholder" = "Price for each Message"; "Stars.SendMessage.AdjustmentSectionHeader" = "PRICE IN STARS"; "Stars.SendMessage.AdjustmentSectionFooterValue" = "You will receive **%@ Stars**."; -"Stars.SendMessage.AdjustmentSectionFooterEmpty" = "You will receive **80%**."; +"Stars.SendMessage.AdjustmentSectionFooterEmptyValue" = "You will receive **%1$@%**."; "Stars.SendMessage.AdjustmentAction" = "OK"; "Stars.SendMessage.PriceFree" = "Free"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index b4c716e0f0..f56c8dbc76 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -425,6 +425,28 @@ public extension ChatLocation { return .replyThread(message: message.normalized) } } + + var peerId: PeerId? { + switch self { + case let .peer(peerId): + return peerId + case let .replyThread(replyThreadMessage): + return replyThreadMessage.peerId + case .customChatContents: + return nil + } + } + + var threadId: Int64? { + switch self { + case .peer: + return nil + case let .replyThread(replyThreadMessage): + return replyThreadMessage.threadId + case .customChatContents: + return nil + } + } } public enum ChatControllerActivateInput { diff --git a/submodules/AccountContext/Sources/ChatListController.swift b/submodules/AccountContext/Sources/ChatListController.swift index e46041d20e..68a72e1ea2 100644 --- a/submodules/AccountContext/Sources/ChatListController.swift +++ b/submodules/AccountContext/Sources/ChatListController.swift @@ -11,6 +11,7 @@ public enum ChatListControllerLocation: Equatable { public protocol ChatListController: ViewController { var context: AccountContext { get } + var location: ChatListControllerLocation { get } var lockViewFrame: CGRect? { get } var isSearchActive: Bool { get } @@ -25,4 +26,6 @@ public protocol ChatListController: ViewController { func openStories(peerId: EnginePeer.Id) func openStoriesFromNotification(peerId: EnginePeer.Id, storyId: Int32) + + func resetForumStackIfOpen() } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 08e7ef5261..3b158005fe 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -3601,6 +3601,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return true } + public func resetForumStackIfOpen() { + if self.secondaryContext != nil { + self.setInlineChatList(location: nil, animated: false) + } + } + public func setInlineChatList(location: ChatListControllerLocation?, animated: Bool = true) { if let location { let inlineNode = self.chatListDisplayNode.makeInlineChatList(location: location) diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift index 86aca43a95..6b67783148 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift @@ -10,30 +10,6 @@ import ChatInterfaceState import ChatContextQuery import AudioWaveform -public extension ChatLocation { - var peerId: PeerId? { - switch self { - case let .peer(peerId): - return peerId - case let .replyThread(replyThreadMessage): - return replyThreadMessage.peerId - case .customChatContents: - return nil - } - } - - var threadId: Int64? { - switch self { - case .peer: - return nil - case let .replyThread(replyThreadMessage): - return replyThreadMessage.threadId - case .customChatContents: - return nil - } - } -} - public enum ChatMediaInputMode { case gif case other diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift index 646e3d6cc0..d5a9b8e415 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift @@ -182,10 +182,6 @@ func _internal_premiumGiftCodeOptions(account: Account, peerId: EnginePeer.Id?, return .single([]) } |> switchToLatest - var flags: Int32 = 0 - if let _ = peerId { - flags |= 1 << 0 - } let remote = account.postbox.transaction { transaction -> Peer? in if let peerId = peerId { return transaction.getPeer(peerId) @@ -194,6 +190,10 @@ func _internal_premiumGiftCodeOptions(account: Account, peerId: EnginePeer.Id?, } |> mapToSignal { peer in let inputPeer = peer.flatMap(apiInputPeer) + var flags: Int32 = 0 + if let _ = inputPeer { + flags |= 1 << 0 + } return account.network.request(Api.functions.payments.getPremiumGiftCodeOptions(flags: flags, boostPeer: inputPeer)) |> map(Optional.init) |> `catch` { _ -> Signal<[Api.PremiumGiftCodeOption]?, NoError> in diff --git a/submodules/TelegramUI/Components/Chat/ChatLoadingNode/Sources/ChatLoadingNode.swift b/submodules/TelegramUI/Components/Chat/ChatLoadingNode/Sources/ChatLoadingNode.swift index 9ca1f0c736..432fb81590 100644 --- a/submodules/TelegramUI/Components/Chat/ChatLoadingNode/Sources/ChatLoadingNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatLoadingNode/Sources/ChatLoadingNode.swift @@ -416,10 +416,22 @@ public final class ChatLoadingPlaceholderNode: ASDisplayNode { } else if peer is TelegramGroup { chatType = .group } else if let channel = peer as? TelegramChannel { - if case .group = channel.info { - chatType = .group + if channel.isMonoForum { + if let mainChannel = chatPresentationInterfaceState.renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel, mainChannel.hasPermission(.sendSomething) { + if chatPresentationInterfaceState.chatLocation.threadId == nil { + chatType = .group + } else { + chatType = .user + } + } else { + chatType = .user + } } else { - chatType = .channel + if case .group = channel.info { + chatType = .group + } else { + chatType = .channel + } } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 4da0fa0ed5..3bba5b17e1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -2545,8 +2545,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } nameNodeOriginY += 5.0 } + + var headerSizeWidth = nameAvatarSpaceWidth + nameNodeSizeApply.0.width + 8.0 + credibilityIconWidth + boostBadgeWidth + closeButtonWidth + bubbleWidthInsets + if hasTitleTopicNavigation { + } else { + headerSizeWidth += adminBadgeSizeAndApply.0.size.width + } - headerSize.width = max(headerSize.width, nameAvatarSpaceWidth + nameNodeSizeApply.0.width + 8.0 + adminBadgeSizeAndApply.0.size.width + credibilityIconWidth + boostBadgeWidth + closeButtonWidth + bubbleWidthInsets) + headerSize.width = max(headerSize.width, headerSizeWidth) headerSize.height += nameNodeSizeApply.0.height } @@ -3881,12 +3887,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } strongSelf.clippingNode.addSubnode(adminBadgeNode) adminBadgeNode.frame = adminBadgeFrame + adminBadgeNode.alpha = hasTitleTopicNavigation ? 0.0 : 1.0 - if animation.isAnimated { - adminBadgeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + if animation.isAnimated, adminBadgeNode.alpha != 0.0 { + adminBadgeNode.layer.animateAlpha(from: 0.0, to: adminBadgeNode.alpha, duration: 0.2) } } else { animation.animator.updateFrame(layer: adminBadgeNode.layer, frame: adminBadgeFrame, completion: nil) + animation.animator.updateAlpha(layer: adminBadgeNode.layer, alpha: hasTitleTopicNavigation ? 0.0 : 1.0, completion: nil) } } else { strongSelf.adminBadgeNode?.removeFromSupernode() diff --git a/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift b/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift index 524a291a23..7588ac73b8 100644 --- a/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift +++ b/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift @@ -72,6 +72,7 @@ final class ForumSettingsScreenComponent: Component { private var isOn = false private var mode: ForumModeComponent.Mode = .tabs + private var initialOnMode: ForumModeComponent.Mode? override init(frame: CGRect) { self.scrollView = ScrollView() @@ -143,64 +144,114 @@ final class ForumSettingsScreenComponent: Component { } func toggleTopicsEnabled(_ enabled: Bool) { - guard let component = self.component, let peer = self.peer else { - return - } self.isOn = enabled - let displayForumAsTabs = self.mode == .tabs - - if self.isOn { - if case .legacyGroup = peer { - let context = component.context - let signal: Signal = context.engine.peers.convertGroupToSupergroup(peerId: peer.id, additionalProcessing: { upgradedPeerId -> Signal in - return context.engine.peers.setChannelForumMode(id: upgradedPeerId, isForum: true, displayForumAsTabs: displayForumAsTabs) - }) - |> map(Optional.init) - |> `catch` { [weak self] error -> Signal in - guard let self, let controller = self.environment?.controller() else { - return .single(nil) - } - switch error { - case .tooManyChannels: - Queue.mainQueue().async { - let oldChannelsController = context.sharedContext.makeOldChannelsController(context: context, updatedPresentationData: nil, intent: .upgrade, completed: { result in - - }) - controller.push(oldChannelsController) - } - default: - break - } - return .single(nil) - } - |> mapToSignal { upgradedPeerId -> Signal in - guard let upgradedPeerId = upgradedPeerId else { - return .single(nil) - } - return .single(upgradedPeerId) - } - |> deliverOnMainQueue - - let _ = signal.startStandalone(next: { [weak self] resultPeerId in - guard let self else { - return - } - if let resultPeerId { - self.peerIdPromise.set(resultPeerId) - } else { - self.isOn = false - self.state?.updated(transition: .easeInOut(duration: 0.2)) - } - }) - } else { - let _ = component.context.engine.peers.setChannelForumMode(id: peer.id, isForum: true, displayForumAsTabs: displayForumAsTabs).startStandalone() - } - } else { - let _ = component.context.engine.peers.setChannelForumMode(id: peer.id, isForum: false, displayForumAsTabs: displayForumAsTabs).startStandalone() - } self.state?.updated(transition: .spring(duration: 0.4)) } + + func attemptNavigation(complete: @escaping () -> Void) -> Bool { + guard let component = self.component, let peer = self.peer else { + return true + } + + var isUpdated = false + if self.isOn { + if let initialOnMode = self.initialOnMode { + if initialOnMode != self.mode { + isUpdated = true + } + } else { + isUpdated = true + } + } else { + if self.initialOnMode != nil { + isUpdated = true + } + } + + if isUpdated { + if let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController { + var viewControllers = navigationController.viewControllers + + if self.isOn && self.mode == .list { + for i in 0 ..< viewControllers.count { + if let chatController = viewControllers[i] as? ChatController, chatController.chatLocation.peerId == component.peerId { + let chatListController = component.context.sharedContext.makeChatListController(context: component.context, location: .forum(peerId: component.peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false) + viewControllers[i] = chatListController + } + } + navigationController.setViewControllers(viewControllers, animated: false) + } else { + for i in (0 ..< viewControllers.count).reversed() { + if let chatListController = viewControllers[i] as? ChatListController, chatListController.location == .forum(peerId: component.peerId) { + viewControllers.remove(at: i) + } + } + navigationController.setViewControllers(viewControllers, animated: false) + + if let baseController = navigationController as? TelegramRootControllerInterface, let chatListController = baseController.getChatsController() as? ChatListController { + chatListController.resetForumStackIfOpen() + } + } + } + if self.isOn { + if case .legacyGroup = peer { + let context = component.context + let mode = self.mode + let signal: Signal = context.engine.peers.convertGroupToSupergroup(peerId: peer.id, additionalProcessing: { upgradedPeerId -> Signal in + return context.engine.peers.setChannelForumMode(id: upgradedPeerId, isForum: true, displayForumAsTabs: mode == .tabs) + }) + |> map(Optional.init) + |> `catch` { [weak self] error -> Signal in + guard let self, let controller = self.environment?.controller() else { + return .single(nil) + } + switch error { + case .tooManyChannels: + Queue.mainQueue().async { + let oldChannelsController = context.sharedContext.makeOldChannelsController(context: context, updatedPresentationData: nil, intent: .upgrade, completed: { result in + }) + controller.push(oldChannelsController) + } + default: + break + } + return .single(nil) + } + |> mapToSignal { upgradedPeerId -> Signal in + guard let upgradedPeerId = upgradedPeerId else { + return .single(nil) + } + return .single(upgradedPeerId) + } + |> deliverOnMainQueue + + let _ = signal.startStandalone(next: { [weak self] resultPeerId in + guard let self else { + return + } + if let resultPeerId { + self.peerIdPromise.set(resultPeerId) + } else { + self.isOn = false + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + + self.environment?.controller()?.dismiss() + }) + + return false + } else { + let _ = component.context.engine.peers.setChannelForumMode(id: component.peerId, isForum: true, displayForumAsTabs: self.mode == .tabs).startStandalone() + } + } else { + let _ = component.context.engine.peers.setChannelForumMode(id: component.peerId, isForum: false, displayForumAsTabs: false).startStandalone() + } + } + + return true + } + func update(component: ForumSettingsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true defer { @@ -212,7 +263,7 @@ final class ForumSettingsScreenComponent: Component { self.peerDisposable = (self.peerIdPromise.get() |> mapToSignal { peerId in - component.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let self else { @@ -223,6 +274,9 @@ final class ForumSettingsScreenComponent: Component { self.isOn = channel.flags.contains(.isForum) if self.isOn { self.mode = channel.flags.contains(.displayForumAsTabs) ? .tabs : .list + self.initialOnMode = self.mode + } else { + self.initialOnMode = nil } } self.state?.updated() @@ -421,8 +475,6 @@ final class ForumSettingsScreenComponent: Component { return } self.mode = mode - let displayForumAsTabs = self.mode == .tabs - let _ = component.context.engine.peers.setChannelForumMode(id: component.peerId, isForum: true, displayForumAsTabs: displayForumAsTabs).startStandalone() self.state?.updated(transition: .spring(duration: 0.4)) } ) @@ -514,6 +566,14 @@ public final class ForumSettingsScreen: ViewControllerComponentContainer { } componentView.scrollToTop() } + + self.attemptNavigation = { [weak self] complete in + guard let self, let componentView = self.node.hostView.componentView as? ForumSettingsScreenComponent.View else { + return true + } + + return componentView.attemptNavigation(complete: complete) + } } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift index fd64e787d7..8b7111d47b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift @@ -29,6 +29,7 @@ final class PostSuggestionsSettingsScreenComponent: Component { let context: AccountContext let usdWithdrawRate: Int64 + let paidMessageCommissionPermille: Int let peer: EnginePeer? let initialPrice: StarsAmount? let completion: () -> Void @@ -36,12 +37,14 @@ final class PostSuggestionsSettingsScreenComponent: Component { init( context: AccountContext, usdWithdrawRate: Int64, + paidMessageCommissionPermille: Int, peer: EnginePeer?, initialPrice: StarsAmount?, completion: @escaping () -> Void ) { self.context = context self.usdWithdrawRate = usdWithdrawRate + self.paidMessageCommissionPermille = paidMessageCommissionPermille self.peer = peer self.initialPrice = initialPrice self.completion = completion @@ -367,7 +370,7 @@ final class PostSuggestionsSettingsScreenComponent: Component { } let currentAmount: StarsAmount = StarsAmount(value: Int64(self.starCount), nanos: 0) - let starsScreen = component.context.sharedContext.makeStarsWithdrawalScreen(context: component.context, subject: .enterAmount(current: currentAmount, minValue: StarsAmount(value: 0, nanos: 0), fractionAfterCommission: 85, kind: .postSuggestion), completion: { [weak self] amount in + let starsScreen = component.context.sharedContext.makeStarsWithdrawalScreen(context: component.context, subject: .enterAmount(current: currentAmount, minValue: StarsAmount(value: 0, nanos: 0), fractionAfterCommission: component.paidMessageCommissionPermille / 10, kind: .postSuggestion), completion: { [weak self] amount in guard let self else { return } @@ -398,9 +401,9 @@ final class PostSuggestionsSettingsScreenComponent: Component { )), footer: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: environment.strings.ChannelMessages_PriceSectionFooter, + string: environment.strings.ChannelMessages_PriceSectionFooterValue("\(component.paidMessageCommissionPermille / 10)").string, font: Font.regular(13.0), - textColor: environment.theme.list.freeTextColor + textColor: self.starCount == 0 ? .clear : environment.theme.list.freeTextColor )), maximumNumberOfLines: 0 )), @@ -497,6 +500,7 @@ public final class PostSuggestionsSettingsScreen: ViewControllerComponentContain super.init(context: context, component: PostSuggestionsSettingsScreenComponent( context: context, usdWithdrawRate: configuration.usdWithdrawRate, + paidMessageCommissionPermille: Int(configuration.paidMessageCommissionPermille), peer: peer, initialPrice: initialPrice, completion: completion diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 4a0d292bb4..cb6a9b1f03 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -295,7 +295,7 @@ private final class SheetContent: CombinedComponent { let amountValue = StarsAmount(value: fullValue / 1_000_000_000, nanos: Int32(fullValue % 1_000_000_000)) amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SendMessage_AdjustmentSectionFooterValue("\(amountValue)").string, attributes: amountMarkdownAttributes, textAlignment: .natural)) } else { - amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SendMessage_AdjustmentSectionFooterEmpty, attributes: amountMarkdownAttributes, textAlignment: .natural)) + amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SendMessage_AdjustmentSectionFooterEmptyValue("\(fractionAfterCommission)").string, attributes: amountMarkdownAttributes, textAlignment: .natural)) } amountFooter = AnyComponent(MultilineTextComponent( text: .plain(amountInfoString), @@ -389,6 +389,16 @@ private final class SheetContent: CombinedComponent { buttonAttributedString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: buttonAttributedString.string)) } + var isButtonEnabled = false + let amount = state.amount ?? StarsAmount.zero + if amount > StarsAmount.zero { + isButtonEnabled = true + } else if case let .paidMessages(_, minValue, _, _) = context.component.mode { + if minValue <= 0 { + isButtonEnabled = true + } + } + let button = button.update( component: ButtonComponent( background: ButtonComponent.Background( @@ -401,7 +411,7 @@ private final class SheetContent: CombinedComponent { id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))) ), - isEnabled: (state.amount ?? StarsAmount.zero) > StarsAmount.zero, + isEnabled: isButtonEnabled, displaysProgress: false, action: { [weak state] in if let controller = controller() as? StarsWithdrawScreen, let amount = state?.amount { @@ -815,31 +825,40 @@ private final class AmountFieldComponent: Component { } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + guard let component = self.component else { + return false + } + if string.rangeOfCharacter(from: invalidAmountCharacters) != nil { return false } + + var acceptZero = false + if let minValue = component.minValue, minValue <= 0 { + acceptZero = true + } + var newText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string) - if newText == "0" || (newText.count > 1 && newText.hasPrefix("0")) { + if (newText == "0" && !acceptZero) || (newText.count > 1 && newText.hasPrefix("0")) { newText.removeFirst() textField.text = newText self.textChanged(self.textField) return false } - if let component = self.component { - let amount: Int64? - if !newText.isEmpty, let value = Int64(normalizeArabicNumeralString(newText, type: .western)) { - amount = value - } else { - amount = nil - } - if let amount, let maxAmount = component.maxValue, amount > maxAmount { - textField.text = "\(maxAmount)" - self.textChanged(self.textField) - self.animateError() - return false - } + let amount: Int64? + if !newText.isEmpty, let value = Int64(normalizeArabicNumeralString(newText, type: .western)) { + amount = value + } else { + amount = nil } + if let amount, let maxAmount = component.maxValue, amount > maxAmount { + textField.text = "\(maxAmount)" + self.textChanged(self.textField) + self.animateError() + return false + } + return true } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 7c3cec8063..9ecee0313e 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4804,6 +4804,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if strongSelf.navigationBar?.contentNode != nil { return false } + if strongSelf.chatDisplayNode.leftPanel != nil { + return false + } return true } diff --git a/submodules/TelegramUI/Sources/ChatControllerContentData.swift b/submodules/TelegramUI/Sources/ChatControllerContentData.swift index 8695f99b10..d435fbe174 100644 --- a/submodules/TelegramUI/Sources/ChatControllerContentData.swift +++ b/submodules/TelegramUI/Sources/ChatControllerContentData.swift @@ -694,7 +694,10 @@ extension ChatControllerImpl { upgradedToPeerId = migrationReference.peerId } if let previous = strongSelf.state.peerView, let channel = previous.peers[previous.peerId] as? TelegramChannel, !channel.isForumOrMonoForum, let updatedChannel = peerView.peers[peerView.peerId] as? TelegramChannel, updatedChannel.isForumOrMonoForum { - movedToForumTopics = true + if updatedChannel.isForum && updatedChannel.flags.contains(.displayForumAsTabs) { + } else { + movedToForumTopics = true + } } var shouldDismiss = false @@ -761,6 +764,7 @@ extension ChatControllerImpl { } var starGiftsAvailable = false var peerDiscussionId: PeerId? + var peerMonoforumId: PeerId? var peerGeoLocation: PeerGeoLocation? if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { if case .broadcast = peer.info { @@ -771,6 +775,9 @@ extension ChatControllerImpl { if case let .known(value) = cachedData.linkedDiscussionPeerId { peerDiscussionId = value } + if !peer.isMonoForum { + peerMonoforumId = peer.linkedMonoforumId + } } var renderedPeer: RenderedPeer? var contactStatus: ChatContactStatus? @@ -921,13 +928,14 @@ extension ChatControllerImpl { explicitelyCanPinMessages = true } - if strongSelf.preloadHistoryPeerId != peerDiscussionId { - strongSelf.preloadHistoryPeerId = peerDiscussionId - if let peerDiscussionId = peerDiscussionId, let channel = peerView.peers[peerView.peerId] as? TelegramChannel, case .broadcast = channel.info { + let preloadHistoryPeerId = peerMonoforumId ?? peerDiscussionId + if strongSelf.preloadHistoryPeerId != preloadHistoryPeerId { + strongSelf.preloadHistoryPeerId = preloadHistoryPeerId + if let preloadHistoryPeerId, let channel = peerView.peers[peerView.peerId] as? TelegramChannel, case .broadcast = channel.info { let combinedDisposable = DisposableSet() strongSelf.preloadHistoryPeerIdDisposable.set(combinedDisposable) - combinedDisposable.add(context.account.viewTracker.polledChannel(peerId: peerDiscussionId).startStrict()) - combinedDisposable.add(context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId)) + combinedDisposable.add(context.account.viewTracker.polledChannel(peerId: preloadHistoryPeerId).startStrict()) + combinedDisposable.add(context.account.addAdditionalPreloadHistoryPeerId(peerId: preloadHistoryPeerId)) } else { strongSelf.preloadHistoryPeerIdDisposable.set(nil) } @@ -1559,6 +1567,7 @@ extension ChatControllerImpl { strongSelf.state.threadInfo = messageAndTopic.threadData?.info var peerDiscussionId: PeerId? + var peerMonoforumId: PeerId? var peerGeoLocation: PeerGeoLocation? var currentSendAsPeerId: PeerId? if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { @@ -1569,6 +1578,8 @@ extension ChatControllerImpl { currentSendAsPeerId = nil } } else { + peerMonoforumId = peer.linkedMonoforumId + currentSendAsPeerId = cachedData.sendAsPeerId if case .group = peer.info { peerGeoLocation = cachedData.peerGeoLocation @@ -1610,10 +1621,11 @@ extension ChatControllerImpl { explicitelyCanPinMessages = true } - if strongSelf.preloadHistoryPeerId != peerDiscussionId { - strongSelf.preloadHistoryPeerId = peerDiscussionId - if let peerDiscussionId = peerDiscussionId { - strongSelf.preloadHistoryPeerIdDisposable.set(context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId)) + let preloadHistoryPeerId = peerMonoforumId ?? peerDiscussionId + if strongSelf.preloadHistoryPeerId != preloadHistoryPeerId { + strongSelf.preloadHistoryPeerId = preloadHistoryPeerId + if let preloadHistoryPeerId { + strongSelf.preloadHistoryPeerIdDisposable.set(context.account.addAdditionalPreloadHistoryPeerId(peerId: preloadHistoryPeerId)) } else { strongSelf.preloadHistoryPeerIdDisposable.set(nil) } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 1a403c63e6..f39184e345 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -233,7 +233,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { private var chatTranslationPanel: ChatTranslationPanelNode? private var leftPanelContainer: ChatControllerTitlePanelNodeContainer - private var leftPanel: (component: AnyComponentWithIdentity, view: ComponentView)? + private(set) var leftPanel: (component: AnyComponentWithIdentity, view: ComponentView)? private(set) var inputPanelNode: ChatInputPanelNode? private(set) var inputPanelOverscrollNode: ChatInputPanelOverscrollNode?