Various improvements

This commit is contained in:
Isaac 2025-07-01 15:49:15 +02:00
parent af3d7ada55
commit c5f2953e90
9 changed files with 188 additions and 100 deletions

View File

@ -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.";

View File

@ -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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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<Empty>
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<Empty>
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

View File

@ -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
}

View File

@ -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
}