From c5f2953e9093b5d237ad690ed53f26e6a6821444 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 1 Jul 2025 15:49:15 +0200 Subject: [PATCH] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 + .../Sources/AccountContext.swift | 7 +- .../Sources/AttachmentPanel.swift | 2 + .../Sources/CallListController.swift | 7 +- .../Sources/PresentationGroupCall.swift | 2 +- .../Sources/PeerInfoScreen.swift | 6 +- .../Sources/SendInviteLinkScreen.swift | 246 +++++++++++------- .../TelegramUI/Sources/ChatController.swift | 10 +- .../Sources/SharedAccountContext.swift | 6 +- 9 files changed, 188 insertions(+), 100 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 032ee57efd..4ac08046aa 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14667,3 +14667,5 @@ Sorry for the inconvenience."; "Chat.Todo.Message.Completed_any" = "%@ of {count} completed"; "Chat.Todo.Message.CompletedBy_1" = "%@ of {count} completed by {name}"; "Chat.Todo.Message.CompletedBy_any" = "%@ of {count} completed by {name}"; + +"SendInviteLink.TextCallsRestrictedSendOneInviteLink" = "**%@** restricts calling them. You can send them an invite link to call instead."; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index e7f0cf797a..1c5d42aa39 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1026,8 +1026,13 @@ public enum OldChannelsControllerIntent { } public enum SendInviteLinkScreenSubject { + public enum GroupCall { + case existing(link: String) + case create + } + case chat(peer: EnginePeer, link: String?) - case groupCall(link: String) + case groupCall(GroupCall) } public enum StarsWithdrawalScreenSubject { diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 4ba6792a0b..fe9de52114 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -1306,6 +1306,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { } else if let channel = peerViewMainPeer(view) as? TelegramChannel { if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = view.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { return nil + } else if let cachedData = view.cachedData as? CachedChannelData, let sendPaidMessageStarsValue = cachedData.sendPaidMessageStars, sendPaidMessageStarsValue == .zero { + return nil } else { return channel.sendPaidMessageStars } diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index 2fddede855..6c72ed9965 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -680,8 +680,11 @@ public final class CallListController: TelegramBaseController { } if let cachedUserData = view.cachedData as? CachedUserData, cachedUserData.callsPrivate { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + strongSelf.push(strongSelf.context.sharedContext.makeSendInviteLinkScreen(context: strongSelf.context, subject: .groupCall(.create), peers: [TelegramForbiddenInvitePeer( + peer: EnginePeer(peer), + canInviteWithPremium: false, + premiumRequiredToContact: false + )], theme: strongSelf.presentationData.theme)) return } diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 68f829c7f9..2f4d097495 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -3659,7 +3659,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { case let .privacy(peer): if let peer { if let currentInviteLinks = self.currentInviteLinks { - let inviteLinkScreen = self.accountContext.sharedContext.makeSendInviteLinkScreen(context: self.accountContext, subject: .groupCall(link: currentInviteLinks.listenerLink), peers: [TelegramForbiddenInvitePeer(peer: peer, canInviteWithPremium: false, premiumRequiredToContact: false)], theme: defaultDarkColorPresentationTheme) + let inviteLinkScreen = self.accountContext.sharedContext.makeSendInviteLinkScreen(context: self.accountContext, subject: .groupCall(.existing(link: currentInviteLinks.listenerLink)), peers: [TelegramForbiddenInvitePeer(peer: peer, canInviteWithPremium: false, premiumRequiredToContact: false)], theme: defaultDarkColorPresentationTheme) if let navigationController = self.accountContext.sharedContext.mainWindow?.viewController as? NavigationController { navigationController.pushViewController(inviteLinkScreen) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 082ec3e597..39b970207a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -7603,7 +7603,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } if cachedUserData.callsPrivate { - self.controller?.present(textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: self.presentationData.strings.Call_ConnectionErrorTitle, text: self.presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.controller?.push(self.context.sharedContext.makeSendInviteLinkScreen(context: self.context, subject: .groupCall(.create), peers: [TelegramForbiddenInvitePeer( + peer: EnginePeer(peer), + canInviteWithPremium: false, + premiumRequiredToContact: false + )], theme: self.presentationData.theme)) return } diff --git a/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift b/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift index 52d93e4eb3..3db8d0b7bc 100644 --- a/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift +++ b/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift @@ -118,6 +118,9 @@ private final class SendInviteLinkScreenComponent: Component { private var topOffsetDistance: CGFloat? + private var createCallDisposable: Disposable? + private var isInProgress: Bool = false + override init(frame: CGRect) { self.bottomOverscrollLimit = 200.0 @@ -178,6 +181,10 @@ private final class SendInviteLinkScreenComponent: Component { fatalError("init(coder:) has not been implemented") } + deinit { + self.createCallDisposable?.dispose() + } + func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.ignoreScrolling { self.updateScrolling(transition: .immediate) @@ -790,8 +797,18 @@ private final class SendInviteLinkScreenComponent: Component { } } } - case .groupCall: - text = environment.strings.SendInviteLink_TextCallsRestrictedSendInviteLink + case let .groupCall(groupCall): + switch groupCall { + case .create: + if component.peers.count == 1 { + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + text = environment.strings.SendInviteLink_TextCallsRestrictedSendOneInviteLink(component.peers[0].peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)).string + } else { + text = environment.strings.SendInviteLink_TextCallsRestrictedSendInviteLink + } + case .existing: + text = environment.strings.SendInviteLink_TextCallsRestrictedSendInviteLink + } } let body = MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor) @@ -828,79 +845,81 @@ private final class SendInviteLinkScreenComponent: Component { var itemsHeight: CGFloat = 0.0 var validIds: [AnyHashable] = [] - for i in 0 ..< component.peers.count { - let peer = component.peers[i] - - for _ in 0 ..< 1 { - //let id: AnyHashable = AnyHashable("\(peer.id)_\(j)") - let id = AnyHashable(peer.peer.id) - validIds.append(id) + if case .chat = component.subject { + for i in 0 ..< component.peers.count { + let peer = component.peers[i] - let item: ComponentView - var itemTransition = transition - if let current = self.items[id] { - item = current - } else { - itemTransition = .immediate - item = ComponentView() - self.items[id] = item - } - - let itemSubtitle: PeerListItemComponent.Subtitle - let canBeSelected : Bool - switch component.subject { - case let .chat(_, link): - canBeSelected = link != nil && !peer.premiumRequiredToContact - case .groupCall: - canBeSelected = true - } - if peer.premiumRequiredToContact { - itemSubtitle = .text(text: environment.strings.SendInviteLink_StatusAvailableToPremiumOnly, icon: .lock) - } else { - itemSubtitle = .presence(component.peerPresences[peer.peer.id]) - } - - let itemSize = item.update( - transition: itemTransition, - component: AnyComponent(PeerListItemComponent( - context: component.context, - theme: environment.theme, - strings: environment.strings, - sideInset: 0.0, - title: peer.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast), - subtitle: itemSubtitle, - peer: peer.peer, - selectionState: !canBeSelected ? .none : .editing(isSelected: self.selectedItems.contains(peer.peer.id)), - hasNext: i != component.peers.count - 1, - action: { [weak self] peer in - guard let self else { - return - } - if !canBeSelected { - return - } - if self.selectedItems.contains(peer.id) { - self.selectedItems.remove(peer.id) - } else { - self.selectedItems.insert(peer.id) - } - self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) - } - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) - ) - let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: itemsHeight), size: itemSize) - - if let itemView = item.view { - if itemView.superview == nil { - self.itemContainerView.addSubview(itemView) + for _ in 0 ..< 1 { + //let id: AnyHashable = AnyHashable("\(peer.id)_\(j)") + let id = AnyHashable(peer.peer.id) + validIds.append(id) + + let item: ComponentView + var itemTransition = transition + if let current = self.items[id] { + item = current + } else { + itemTransition = .immediate + item = ComponentView() + self.items[id] = item } - itemTransition.setFrame(view: itemView, frame: itemFrame) + + let itemSubtitle: PeerListItemComponent.Subtitle + let canBeSelected : Bool + switch component.subject { + case let .chat(_, link): + canBeSelected = link != nil && !peer.premiumRequiredToContact + case .groupCall: + canBeSelected = true + } + if peer.premiumRequiredToContact { + itemSubtitle = .text(text: environment.strings.SendInviteLink_StatusAvailableToPremiumOnly, icon: .lock) + } else { + itemSubtitle = .presence(component.peerPresences[peer.peer.id]) + } + + let itemSize = item.update( + transition: itemTransition, + component: AnyComponent(PeerListItemComponent( + context: component.context, + theme: environment.theme, + strings: environment.strings, + sideInset: 0.0, + title: peer.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast), + subtitle: itemSubtitle, + peer: peer.peer, + selectionState: !canBeSelected ? .none : .editing(isSelected: self.selectedItems.contains(peer.peer.id)), + hasNext: i != component.peers.count - 1, + action: { [weak self] peer in + guard let self else { + return + } + if !canBeSelected { + return + } + if self.selectedItems.contains(peer.id) { + self.selectedItems.remove(peer.id) + } else { + self.selectedItems.insert(peer.id) + } + self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) + ) + let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: itemsHeight), size: itemSize) + + if let itemView = item.view { + if itemView.superview == nil { + self.itemContainerView.addSubview(itemView) + } + itemTransition.setFrame(view: itemView, frame: itemFrame) + } + + itemsHeight += itemSize.height + singleItemHeight = itemSize.height } - - itemsHeight += itemSize.height - singleItemHeight = itemSize.height } } var removeIds: [AnyHashable] = [] @@ -917,9 +936,13 @@ private final class SendInviteLinkScreenComponent: Component { initialContentHeight += min(itemsHeight, floor(singleItemHeight * 2.5)) - contentHeight += itemsHeight - contentHeight += 24.0 - initialContentHeight += 24.0 + if itemsHeight != 0.0 { + contentHeight += itemsHeight + contentHeight += 24.0 + initialContentHeight += 24.0 + } else { + contentHeight += 4.0 + } let actionButtonTitle: String let actionButtonBadge: String? @@ -933,7 +956,7 @@ private final class SendInviteLinkScreenComponent: Component { actionButtonBadge = (self.selectedItems.isEmpty || link == nil) ? nil : "\(self.selectedItems.count)" case .groupCall: actionButtonTitle = environment.strings.SendInviteLink_ActionInvite - actionButtonBadge = self.selectedItems.isEmpty ? nil : "\(self.selectedItems.count)" + actionButtonBadge = nil } let actionButtonSize = actionButton.update( transition: transition, @@ -949,6 +972,7 @@ private final class SendInviteLinkScreenComponent: Component { animationName: nil, iconPosition: .right, iconSpacing: 4.0, + isLoading: self.isInProgress, action: { [weak self] in guard let self, let component = self.component, let controller = self.environment?.controller() else { return @@ -958,8 +982,62 @@ private final class SendInviteLinkScreenComponent: Component { switch component.subject { case let .chat(_, linkValue): link = linkValue - case let .groupCall(linkValue): - link = linkValue + case let .groupCall(groupCall): + switch groupCall { + case .create: + self.isInProgress = true + self.state?.updated(transition: .immediate) + + self.createCallDisposable = (component.context.engine.calls.createConferenceCall() + |> deliverOnMainQueue).startStrict(next: { [weak self] call in + guard let self, let component = self.component, let controller = self.environment?.controller() else { + return + } + + if self.selectedItems.isEmpty { + controller.dismiss() + } else { + let link = call.link + let selectedPeers = component.peers.filter { self.selectedItems.contains($0.peer.id) } + + self.presentPaidMessageAlertIfNeeded( + peers: selectedPeers.map { EngineRenderedPeer(peer: $0.peer) }, + requiresStars: component.sendPaidMessageStars, + completion: { [weak self] in + guard let self, let component = self.component, let controller = self.environment?.controller() else { + return + } + + for peerId in Array(self.selectedItems) { + var messageAttributes: [EngineMessage.Attribute] = [] + if let sendPaidMessageStars = component.sendPaidMessageStars[peerId] { + messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) + } + let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: [.message(text: link, attributes: messageAttributes, inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).startStandalone() + } + + let text: String + if selectedPeers.count == 1 { + text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string + } else if selectedPeers.count == 2 { + text = environment.strings.Conversation_ShareLinkTooltip_TwoChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), selectedPeers[1].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string + } else { + text = environment.strings.Conversation_ShareLinkTooltip_ManyChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), "\(selectedPeers.count - 1)").string + } + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + controller.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: false, text: text), elevatedLayout: false, action: { _ in return false }), in: .window(.root)) + + controller.dismiss() + } + ) + } + }) + + return + case let .existing(linkValue): + link = linkValue + } } if self.selectedItems.isEmpty { @@ -1082,7 +1160,6 @@ private final class SendInviteLinkScreenComponent: Component { public class SendInviteLinkScreen: ViewControllerComponentContainer { private let context: AccountContext - private let link: String? private let peers: [TelegramForbiddenInvitePeer] private var isDismissed: Bool = false @@ -1092,17 +1169,6 @@ public class SendInviteLinkScreen: ViewControllerComponentContainer { public init(context: AccountContext, subject: SendInviteLinkScreenSubject, peers: [TelegramForbiddenInvitePeer], theme: PresentationTheme? = nil) { self.context = context - switch subject { - case let .chat(peer, link): - var link = link - if link == nil, let addressName = peer.addressName { - link = "https://t.me/\(addressName)" - } - self.link = link - case let .groupCall(link): - self.link = link - } - #if DEBUG && false var peers = peers diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 2a5fa16522..1e3dc481c6 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2994,14 +2994,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return peerViewMainPeer(view) } |> deliverOnMainQueue).startStandalone(next: { peer in - guard let peer = peer else { + guard let peer else { return } if let cachedUserData = strongSelf.contentData?.state.peerView?.cachedData as? CachedUserData, cachedUserData.callsPrivate { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + strongSelf.push(strongSelf.context.sharedContext.makeSendInviteLinkScreen(context: strongSelf.context, subject: .groupCall(.create), peers: [TelegramForbiddenInvitePeer( + peer: EnginePeer(peer), + canInviteWithPremium: false, + premiumRequiredToContact: false + )], theme: strongSelf.presentationData.theme)) return } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index cc39fe338d..9a99810855 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2007,7 +2007,11 @@ public final class SharedAccountContextImpl: SharedAccountContext { if let cachedUserData = view.cachedData as? CachedUserData, cachedUserData.callsPrivate { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - parentController.present(textAlertController(context: context, title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + parentController.push(context.sharedContext.makeSendInviteLinkScreen(context: context, subject: .groupCall(.create), peers: [TelegramForbiddenInvitePeer( + peer: EnginePeer(peer), + canInviteWithPremium: false, + premiumRequiredToContact: false + )], theme: presentationData.theme)) return }