Various fixes

This commit is contained in:
Ilya Laktyushin 2024-11-20 17:09:25 +04:00
parent 10984d7ce4
commit 44f02c258f
36 changed files with 279 additions and 108 deletions

View File

@ -13290,5 +13290,10 @@ Sorry for the inconvenience.";
"MediaGallery.ToastVideoPip.Title" = "Video Minimized";
"MediaGallery.ToastVideoPip.Text" = "Swipe down on a video to close it.";
"Chat.ToastSubscribedToScheduledLiveStream.Text" = "You will be notified when the liver stream starts.";
"Chat.ToastSubscribedToScheduledLiveStream.Text" = "You will be notified when the live stream starts.";
"Chat.TitleVideochatPanel.NotifyScheduledButton" = "Notify Me";
"WebApp.ShareMessage.Title" = "Share Message";
"WebApp.ShareMessage.PreviewTitle" = "MESSAGE PREVIEW";
"WebApp.ShareMessage.Info" = "%@ mini app suggests you to send this message to a chat you select.";
"WebApp.ShareMessage.Share" = "Share With...";

View File

@ -1000,7 +1000,7 @@ public protocol SharedAccountContext: AnyObject {
func makeStarsGiftController(context: AccountContext, birthdays: [EnginePeer.Id: TelegramBirthday]?, completion: @escaping (([EnginePeer.Id]) -> Void)) -> ViewController
func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (([EnginePeer.Id]) -> Void)?) -> ViewController
func makeGiftOptionsController(context: AccountContext, peerId: EnginePeer.Id, premiumOptions: [CachedPremiumGiftOption]) -> ViewController
func makeGiftOptionsController(context: AccountContext, peerId: EnginePeer.Id, premiumOptions: [CachedPremiumGiftOption], hasBirthday: Bool) -> ViewController
func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController
func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, subject: BoostSubject, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController

View File

@ -1256,6 +1256,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
self.chatListDisplayNode.dismissSearch = { [weak self] in
if let self {
self.deactivateSearch(animated: true)
}
}
self.chatListDisplayNode.requestOpenRecentPeerOptions = { [weak self] peer in
if let strongSelf = self {
strongSelf.view.window?.endEditing(true)

View File

@ -1095,7 +1095,8 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
var isEmptyUpdated: ((Bool) -> Void)?
var emptyListAction: ((EnginePeer.Id?) -> Void)?
var cancelEditing: (() -> Void)?
var dismissSearch: (() -> Void)?
let debugListView = ListView()
init(context: AccountContext, location: ChatListControllerLocation, previewing: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, controller: ChatListControllerImpl) {
@ -1668,6 +1669,9 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
}, navigationController: navigationController, parentController: { [weak self] in
return self?.controller
})
contentNode.dismissSearch = { [weak self] in
self?.dismissSearch?()
}
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, contentNode: contentNode, cancel: { [weak self] in
if let requestDeactivateSearch = self?.requestDeactivateSearch {

View File

@ -61,8 +61,9 @@ final class ChatListSearchInteraction {
let getSelectedMessageIds: () -> Set<EngineMessage.Id>?
let openStories: ((PeerId, ASDisplayNode) -> Void)?
let switchToFilter: (ChatListSearchPaneKey) -> Void
let dismissSearch: () -> Void
init(openPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, openMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, openUrl: @escaping (String) -> Void, clearRecentSearch: @escaping () -> Void, addContact: @escaping (String) -> Void, toggleMessageSelection: @escaping (EngineMessage.Id, Bool) -> Void, messageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void), mediaMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void), peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, getSelectedMessageIds: @escaping () -> Set<EngineMessage.Id>?, openStories: ((PeerId, ASDisplayNode) -> Void)?, switchToFilter: @escaping (ChatListSearchPaneKey) -> Void) {
init(openPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, openMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, openUrl: @escaping (String) -> Void, clearRecentSearch: @escaping () -> Void, addContact: @escaping (String) -> Void, toggleMessageSelection: @escaping (EngineMessage.Id, Bool) -> Void, messageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void), mediaMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void), peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, getSelectedMessageIds: @escaping () -> Set<EngineMessage.Id>?, openStories: ((PeerId, ASDisplayNode) -> Void)?, switchToFilter: @escaping (ChatListSearchPaneKey) -> Void, dismissSearch: @escaping () -> Void) {
self.openPeer = openPeer
self.openDisabledPeer = openDisabledPeer
self.openMessage = openMessage
@ -78,6 +79,7 @@ final class ChatListSearchInteraction {
self.getSelectedMessageIds = getSelectedMessageIds
self.openStories = openStories
self.switchToFilter = switchToFilter
self.dismissSearch = dismissSearch
}
}
@ -100,6 +102,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private let openMessage: (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void
private let navigationController: NavigationController?
var dismissSearch: (() -> Void)?
private let dimNode: ASDisplayNode
let filterContainerNode: ChatListSearchFiltersContainerNode
private let paneContainerNode: ChatListSearchPaneContainerNode
@ -298,6 +302,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
Queue.mainQueue().justDispatch {
self.paneContainerNode.requestSelectPane(filter)
}
}, dismissSearch: { [weak self] in
self?.dismissSearch?()
})
self.paneContainerNode.interaction = interaction

View File

@ -614,7 +614,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
})
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .generalSearch(isSavedMessages: false), peer: .thread(peer: peer, title: threadInfo.info.title, icon: threadInfo.info.icon, color: threadInfo.info.iconColor), status: .none, badge: nil, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
interaction.peerSelected(peer, nil, threadInfo.id, nil)
interaction.peerSelected(peer, nil, threadInfo.id, nil, false)
}, contextAction: nil, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer)
case let .recentlySearchedPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, storyStats, requiresPremiumForMessaging):
let primaryPeer: EnginePeer
@ -667,6 +667,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
badge = ContactsPeerItemBadge(count: unreadBadge.0, type: unreadBadge.1 ? .inactive : .active)
}
var buttonAction: ContactsPeerItemButtonAction?
let header: ChatListSearchItemHeader?
if filter.contains(.removeSearchHeader) {
header = nil
@ -676,15 +677,24 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
headerType = .chats
} else {
headerType = .recentPeers
if case .chats = key, case let .user(user) = primaryPeer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) {
buttonAction = ContactsPeerItemButtonAction(
title: presentationData.strings.ChatList_Search_Open,
action: { peer, _, _ in
interaction.peerSelected(primaryPeer, nil, nil, nil, true)
}
)
}
}
header = ChatListSearchItemHeader(type: headerType, theme: theme, strings: strings, actionTitle: nil, action: nil)
}
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: false), peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: .none, badge: badge, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { contactPeer in
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: false), peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: .none, badge: badge, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), buttonAction: buttonAction, index: nil, header: header, action: { contactPeer in
if case let .peer(maybePeer, maybeChatPeer) = contactPeer, let peer = maybePeer, let chatPeer = maybeChatPeer {
interaction.peerSelected(chatPeer, peer, nil, nil)
interaction.peerSelected(chatPeer, peer, nil, nil, false)
} else {
interaction.peerSelected(peer, nil, nil, nil)
interaction.peerSelected(peer, nil, nil, nil, false)
}
}, disabledAction: { _ in
interaction.disabledPeerSelected(peer, nil, requiresPremiumForMessaging ? .premiumRequired : .generic)
@ -803,12 +813,22 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
status = .custom(string: presentationData.strings.Bot_GenericBotStatus, multiline: false, isActive: false, icon: nil)
}
}
var buttonAction: ContactsPeerItemButtonAction?
if case .chats = key, case let .user(user) = primaryPeer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) {
buttonAction = ContactsPeerItemButtonAction(
title: presentationData.strings.ChatList_Search_Open,
action: { peer, _, _ in
interaction.peerSelected(primaryPeer, nil, nil, nil, true)
}
)
}
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: isSavedMessages), peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: status, badge: badge, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { contactPeer in
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: isSavedMessages), peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: status, badge: badge, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), buttonAction: buttonAction, index: nil, header: header, action: { contactPeer in
if case let .peer(maybePeer, maybeChatPeer) = contactPeer, let peer = maybePeer, let chatPeer = maybeChatPeer {
interaction.peerSelected(chatPeer, peer, nil, nil)
interaction.peerSelected(chatPeer, peer, nil, nil, false)
} else {
interaction.peerSelected(peer, nil, nil, nil)
interaction.peerSelected(peer, nil, nil, nil, false)
}
}, disabledAction: { _ in
interaction.disabledPeerSelected(peer, nil, requiresPremiumForMessaging ? .premiumRequired : .generic)
@ -891,7 +911,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
}
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: isSavedMessages), peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .addressName(suffixString), badge: badge, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, searchQuery: query, action: { _ in
interaction.peerSelected(EnginePeer(peer.peer), nil, nil, nil)
interaction.peerSelected(EnginePeer(peer.peer), nil, nil, nil, false)
}, disabledAction: { _ in
interaction.disabledPeerSelected(EnginePeer(peer.peer), nil, requiresPremiumForMessaging ? .premiumRequired : .generic)
}, contextAction: peerContextAction.flatMap { peerContextAction in
@ -2883,9 +2903,29 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}
let chatListInteraction = ChatListNodeInteraction(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {
}, peerSelected: { [weak self] peer, chatPeer, threadId, _ in
}, peerSelected: { [weak self] peer, chatPeer, threadId, _, openApp in
interaction.dismissInput()
interaction.openPeer(peer, chatPeer, threadId, false)
if openApp, let self {
if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp), let parentController = self.parentController {
context.sharedContext.openWebApp(
context: context,
parentController: parentController,
updatedPresentationData: nil,
botPeer: peer,
chatPeer: nil,
threadId: nil,
buttonText: "",
url: "",
simple: true,
source: .generic,
skipTermsOfService: true,
payload: nil
)
interaction.dismissSearch()
}
} else {
interaction.openPeer(peer, chatPeer, threadId, false)
}
switch location {
case .chatList, .forum:
let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).startStandalone()
@ -3851,6 +3891,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
skipTermsOfService: true,
payload: nil
)
interaction.dismissSearch()
} else {
interaction.openPeer(peer, nil, threadId, true)
}
@ -4853,7 +4894,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
let timestamp1: Int32 = 100000
var peers: [EnginePeer.Id: EnginePeer] = [:]
peers[peer1.id] = peer1
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {

View File

@ -153,7 +153,7 @@ public final class ChatListShimmerNode: ASDisplayNode {
let peer1: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil))
let timestamp1: Int32 = 100000
let peers: [EnginePeer.Id: EnginePeer] = [:]
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, openStarsTopup: { _ in

View File

@ -503,9 +503,9 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour {
}
self.interaction.messageSelected(peer, threadId, message, peerData.promoInfo)
} else if let peer = peerData.peer.peer {
self.interaction.peerSelected(peer, nil, nil, peerData.promoInfo)
self.interaction.peerSelected(peer, nil, nil, peerData.promoInfo, false)
} else if let peer = peerData.peer.peers[peerData.peer.peerId] {
self.interaction.peerSelected(peer, nil, nil, peerData.promoInfo)
self.interaction.peerSelected(peer, nil, nil, peerData.promoInfo, false)
}
case let .groupReference(groupReferenceData):
self.interaction.groupSelected(groupReferenceData.groupId)

View File

@ -73,7 +73,7 @@ public final class ChatListNodeInteraction {
}
let activateSearch: () -> Void
let peerSelected: (EnginePeer, EnginePeer?, Int64?, ChatListNodeEntryPromoInfo?) -> Void
let peerSelected: (EnginePeer, EnginePeer?, Int64?, ChatListNodeEntryPromoInfo?, Bool) -> Void
let disabledPeerSelected: (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void
let togglePeerSelected: (EnginePeer, Int64?) -> Void
let togglePeersSelection: ([PeerEntry], Bool) -> Void
@ -129,7 +129,7 @@ public final class ChatListNodeInteraction {
animationCache: AnimationCache,
animationRenderer: MultiAnimationRenderer,
activateSearch: @escaping () -> Void,
peerSelected: @escaping (EnginePeer, EnginePeer?, Int64?, ChatListNodeEntryPromoInfo?) -> Void,
peerSelected: @escaping (EnginePeer, EnginePeer?, Int64?, ChatListNodeEntryPromoInfo?, Bool) -> Void,
disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void,
togglePeerSelected: @escaping (EnginePeer, Int64?) -> Void,
togglePeersSelection: @escaping ([PeerEntry], Bool) -> Void,
@ -613,7 +613,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
if editing {
nodeInteraction.togglePeerSelected(chatPeer, threadId)
} else {
nodeInteraction.peerSelected(chatPeer, nil, threadId, nil)
nodeInteraction.peerSelected(chatPeer, nil, threadId, nil, false)
}
}
}, disabledAction: (isForum && editing) && !peerEntry.requiresPremiumForMessaging ? nil : { _ in
@ -653,7 +653,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
if editing {
nodeInteraction.togglePeerSelected(chatPeer, nil)
} else {
nodeInteraction.peerSelected(chatPeer, nil, nil, nil)
nodeInteraction.peerSelected(chatPeer, nil, nil, nil, false)
}
}
}, disabledAction: peerEntry.requiresPremiumForMessaging ? { _ in
@ -719,7 +719,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
index: nil,
header: header,
action: { _ in
nodeInteraction.peerSelected(contactEntry.peer, nil, nil, nil)
nodeInteraction.peerSelected(contactEntry.peer, nil, nil, nil, false)
},
disabledAction: nil,
animationCache: nodeInteraction.animationCache,
@ -957,7 +957,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
if editing {
nodeInteraction.togglePeerSelected(chatPeer, threadId)
} else {
nodeInteraction.peerSelected(chatPeer, nil, threadId, nil)
nodeInteraction.peerSelected(chatPeer, nil, threadId, nil, false)
}
}
}, disabledAction: (isForum && editing) && !peerEntry.requiresPremiumForMessaging ? nil : { _ in
@ -997,7 +997,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
if editing {
nodeInteraction.togglePeerSelected(chatPeer, nil)
} else {
nodeInteraction.peerSelected(chatPeer, nil, nil, nil)
nodeInteraction.peerSelected(chatPeer, nil, nil, nil, false)
}
}
}, disabledAction: peerEntry.requiresPremiumForMessaging ? { _ in
@ -1063,7 +1063,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
index: nil,
header: header,
action: { _ in
nodeInteraction.peerSelected(contactEntry.peer, nil, nil, nil)
nodeInteraction.peerSelected(contactEntry.peer, nil, nil, nil, false)
},
disabledAction: nil,
animationCache: nodeInteraction.animationCache,
@ -1395,7 +1395,7 @@ public final class ChatListNode: ListView {
if let strongSelf = self, let activateSearch = strongSelf.activateSearch {
activateSearch()
}
}, peerSelected: { [weak self] peer, _, threadId, promoInfo in
}, peerSelected: { [weak self] peer, _, threadId, promoInfo, _ in
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
peerSelected(peer, threadId, true, true, promoInfo)
}
@ -1727,7 +1727,7 @@ public final class ChatListNode: ListView {
|> filter { !$0.isEmpty }
|> deliverOnMainQueue).start(next: { giftOptions in
let premiumOptions = giftOptions.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) }
let controller = self.context.sharedContext.makeGiftOptionsController(context: self.context, peerId: peerId, premiumOptions: premiumOptions)
let controller = self.context.sharedContext.makeGiftOptionsController(context: self.context, peerId: peerId, premiumOptions: premiumOptions, hasBirthday: true)
controller.navigationPresentation = .modal
self.push?(controller)
})

View File

@ -108,7 +108,10 @@ class EmojiHeaderComponent: Component {
self.statusView.isHidden = false
if containerView.subviews.count > 1 && containerView.subviews[1].subviews.count > 1 {
containerView = containerView.subviews[1].subviews[1]
let candidateView = containerView.subviews[1].subviews[1]
if !(candidateView is UIVisualEffectView) {
containerView = candidateView
}
}
let initialPosition = self.statusView.center

View File

@ -218,7 +218,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
var items: [ChatListItem] = []
let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in
}, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel()

View File

@ -366,7 +366,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate {
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
var items: [ChatListItem] = []
let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in
}, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel()

View File

@ -378,7 +378,7 @@ public final class ChatButtonKeyboardInputNode: ChatInputNode {
self.controllerInteraction.sendMessage(markupButton.title)
dismissIfOnce = true
case let .url(url):
self.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: true))
self.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: true, progress: Promise()))
case .requestMap:
self.controllerInteraction.shareCurrentLocation()
case .requestPhone:

View File

@ -585,7 +585,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
animationRenderer: component.context.animationRenderer,
activateSearch: {
},
peerSelected: { _, _, _, _ in
peerSelected: { _, _, _, _, _ in
},
disabledPeerSelected: { _, _, _ in
},

View File

@ -814,7 +814,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
if url.hasPrefix("tg://") {
concealed = false
}
item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: concealed))
item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: concealed, progress: Promise()))
case .requestMap:
item.controllerInteraction.shareCurrentLocation()
case .requestPhone:

View File

@ -35,6 +35,7 @@ final class GiftOptionsScreenComponent: Component {
let starsContext: StarsContext
let peerId: EnginePeer.Id
let premiumOptions: [CachedPremiumGiftOption]
let hasBirthday: Bool
let completion: (() -> Void)?
init(
@ -42,12 +43,14 @@ final class GiftOptionsScreenComponent: Component {
starsContext: StarsContext,
peerId: EnginePeer.Id,
premiumOptions: [CachedPremiumGiftOption],
hasBirthday: Bool,
completion: (() -> Void)?
) {
self.context = context
self.starsContext = starsContext
self.peerId = peerId
self.premiumOptions = premiumOptions
self.hasBirthday = hasBirthday
self.completion = completion
}
@ -61,6 +64,9 @@ final class GiftOptionsScreenComponent: Component {
if lhs.premiumOptions != rhs.premiumOptions {
return false
}
if lhs.hasBirthday != rhs.hasBirthday {
return false
}
return true
}
@ -127,32 +133,43 @@ final class GiftOptionsScreenComponent: Component {
private var _effectiveStarGifts: ([StarGift], StarsFilter)?
private var effectiveStarGifts: [StarGift]? {
get {
if case .all = self.starsFilter {
return self.state?.starGifts
} else {
if let (currentGifts, currentFilter) = self._effectiveStarGifts, currentFilter == self.starsFilter {
return currentGifts
} else if let allGifts = self.state?.starGifts {
let filteredGifts: [StarGift] = allGifts.filter {
switch self.starsFilter {
case .all:
return true
case .limited:
if $0.availability != nil {
return true
}
case let .stars(stars):
if $0.price == stars {
return true
}
if let (currentGifts, currentFilter) = self._effectiveStarGifts, currentFilter == self.starsFilter {
return currentGifts
} else if let allGifts = self.state?.starGifts {
var sortedGifts = allGifts
if self.component?.hasBirthday == true {
var updatedGifts: [StarGift] = []
for gift in allGifts {
if gift.flags.contains(.isBirthdayGift) {
updatedGifts.append(gift)
}
return false
}
self._effectiveStarGifts = (filteredGifts, self.starsFilter)
return filteredGifts
} else {
return nil
for gift in allGifts {
if !gift.flags.contains(.isBirthdayGift) {
updatedGifts.append(gift)
}
}
sortedGifts = updatedGifts
}
let filteredGifts: [StarGift] = sortedGifts.filter {
switch self.starsFilter {
case .all:
return true
case .limited:
if $0.availability != nil {
return true
}
case let .stars(stars):
if $0.price == stars {
return true
}
}
return false
}
self._effectiveStarGifts = (filteredGifts, self.starsFilter)
return filteredGifts
} else {
return nil
}
}
}
@ -236,7 +253,8 @@ final class GiftOptionsScreenComponent: Component {
transition.setAlpha(view: topSeparator, alpha: topPanelAlpha)
}
let topInset: CGFloat = environment.navigationHeight - 56.0
let topInset: CGFloat = 0.0
let headerTopInset: CGFloat = environment.navigationHeight - 56.0
let premiumTitleInitialPosition = (topInset + 160.0)
let premiumTitleOffsetDelta = premiumTitleInitialPosition - (environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0)
@ -261,7 +279,11 @@ final class GiftOptionsScreenComponent: Component {
}
let starsTitleScale = 1.0 - starsTitleFraction * 0.36
if let starsTitleView = self.starsTitle.view {
transition.setPosition(view: starsTitleView, position: CGPoint(x: availableWidth / 2.0, y: max(topInset + 455.0 - starsTitleOffset, environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0)))
var starsTitlePosition: CGFloat = 455.0
if let descriptionPosition = self.starsDescription.view?.frame.minY {
starsTitlePosition = descriptionPosition - 28.0
}
transition.setPosition(view: starsTitleView, position: CGPoint(x: availableWidth / 2.0, y: max(topInset + starsTitlePosition - starsTitleOffset, environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0)))
transition.setScale(view: starsTitleView, scale: starsTitleScale)
}
@ -271,7 +293,7 @@ final class GiftOptionsScreenComponent: Component {
}
if let headerView = self.header.view {
transition.setPosition(view: headerView, position: CGPoint(x: availableWidth / 2.0, y: topInset + headerView.bounds.height / 2.0 - 30.0 - premiumTitleOffset * premiumTitleScale))
transition.setPosition(view: headerView, position: CGPoint(x: availableWidth / 2.0, y: headerTopInset + headerView.bounds.height / 2.0 - 30.0 - premiumTitleOffset * premiumTitleScale))
transition.setScale(view: headerView, scale: premiumTitleScale)
}
@ -1073,6 +1095,7 @@ open class GiftOptionsScreen: ViewControllerComponentContainer, GiftOptionsScree
starsContext: StarsContext,
peerId: EnginePeer.Id,
premiumOptions: [CachedPremiumGiftOption],
hasBirthday: Bool,
completion: (() -> Void)? = nil
) {
self.context = context
@ -1082,6 +1105,7 @@ open class GiftOptionsScreen: ViewControllerComponentContainer, GiftOptionsScree
starsContext: starsContext,
peerId: peerId,
premiumOptions: premiumOptions,
hasBirthday: hasBirthday,
completion: completion
), navigationBarAppearance: .none, theme: .default, updatedPresentationData: nil)

View File

@ -335,7 +335,7 @@ private final class GiftViewSheetContent: CombinedComponent {
component.openStarsIntro()
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
context.add(description
@ -1110,7 +1110,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|> filter { !$0.isEmpty }
|> deliverOnMainQueue).start(next: { giftOptions in
let premiumOptions = giftOptions.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) }
let controller = context.sharedContext.makeGiftOptionsController(context: context, peerId: peerId, premiumOptions: premiumOptions)
let controller = context.sharedContext.makeGiftOptionsController(context: context, peerId: peerId, premiumOptions: premiumOptions, hasBirthday: false)
self.push(controller)
})
}

View File

@ -179,7 +179,7 @@ public final class LoadingOverlayNode: ASDisplayNode {
let peer1: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil))
let timestamp1: Int32 = 100000
let peers: [EnginePeer.Id: EnginePeer] = [:]
let interaction = ChatListNodeInteraction(context: context, animationCache: context.animationCache, animationRenderer: context.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
let interaction = ChatListNodeInteraction(context: context, animationCache: context.animationCache, animationRenderer: context.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, openStarsTopup: { _ in
@ -452,7 +452,7 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod
animationRenderer: item.context.animationRenderer,
activateSearch: {
},
peerSelected: { _, _, _, _ in
peerSelected: { _, _, _, _, _ in
},
disabledPeerSelected: { _, _, _ in
},

View File

@ -9907,7 +9907,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
let controller = self.context.sharedContext.makeGiftOptionsController(
context: self.context,
peerId: self.peerId,
premiumOptions: premiumOptions
premiumOptions: premiumOptions,
hasBirthday: false
)
self.controller?.push(controller)
}

View File

@ -1200,7 +1200,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
chatControllerInteraction.toggleMessagesSelection(messageId, selected)
},
openUrl: { url, concealed, external, message in
chatControllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: concealed, external: external, message: message))
chatControllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: concealed, external: external, message: message, progress: Promise()))
},
openInstantPage: { message, data in
chatControllerInteraction.openInstantPage(message, data)

View File

@ -130,7 +130,7 @@ final class GreetingMessageListItemComponent: Component {
animationRenderer: component.context.animationRenderer,
activateSearch: {
},
peerSelected: { _, _, _, _ in
peerSelected: { _, _, _, _, _ in
},
disabledPeerSelected: { _, _, _ in
},

View File

@ -119,7 +119,7 @@ final class QuickReplySetupScreenComponent: Component {
animationRenderer: listNode.context.animationRenderer,
activateSearch: {
},
peerSelected: { [weak listNode] _, _, _, _ in
peerSelected: { [weak listNode] _, _, _, _, _ in
guard let listNode, let parentView = listNode.parentView else {
return
}

View File

@ -860,7 +860,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
var items: [ChatListItem] = []
let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in
}, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel()

View File

@ -127,8 +127,13 @@ func openWebAppImpl(
}
}
var hasWebApp = false
if case let .user(user) = botPeer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) {
hasWebApp = true
}
var presentImpl: ((ViewController, Any?) -> Void)?
let params = WebAppParameters(source: .menu, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", appName: nil, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: fullSize, isFullscreen: isFullscreen, appSettings: appSettings)
let params = WebAppParameters(source: .menu, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", appName: hasWebApp ? "" : nil, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: fullSize, isFullscreen: isFullscreen, appSettings: appSettings)
let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in
ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botPeer.id, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
@ -267,8 +272,14 @@ func openWebAppImpl(
guard let parentController else {
return
}
var hasWebApp = false
if case let .user(user) = botPeer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) {
hasWebApp = true
}
var presentImpl: ((ViewController, Any?) -> Void)?
let params = WebAppParameters(source: .button, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", appName: nil, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), appSettings: appSettings)
let params = WebAppParameters(source: .button, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", appName: hasWebApp ? "" : nil, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), appSettings: appSettings)
let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in
ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botPeer.id, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
presentImpl?(c, a)

View File

@ -591,7 +591,7 @@ extension ChatControllerImpl {
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer, let starsContext = context.starsContext {
let premiumGiftOptions = strongSelf.presentationInterfaceState.premiumGiftOptions
if !premiumGiftOptions.isEmpty {
let controller = PremiumGiftAttachmentScreen(context: context, starsContext: starsContext, peerId: peer.id, premiumOptions: premiumGiftOptions, completion: { [weak self] in
let controller = PremiumGiftAttachmentScreen(context: context, starsContext: starsContext, peerId: peer.id, premiumOptions: premiumGiftOptions, hasBirthday: true, completion: { [weak self] in
guard let self else {
return
}

View File

@ -202,7 +202,7 @@ extension ListMessageItemInteraction {
}, toggleMessagesSelection: { messageId, selected in
controllerInteraction.toggleMessagesSelection(messageId, selected)
}, openUrl: { url, param1, param2, message in
controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: param1, external: param2, message: message))
controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: param1, external: param2, message: message, progress: Promise()))
}, openInstantPage: { message, data in
controllerInteraction.openInstantPage(message, data)
}, longTap: { action, message in

View File

@ -917,7 +917,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
if url.hasPrefix("tg://") {
isConcealed = false
}
controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: isConcealed))
controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: isConcealed, progress: Promise()))
case .requestMap:
controllerInteraction.shareCurrentLocation()
case .requestPhone:

View File

@ -227,7 +227,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, ASScrollViewDe
}
let interaction = ChatListNodeInteraction(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {
}, peerSelected: { _, _, _, _ in
}, peerSelected: { _, _, _, _, _ in
}, disabledPeerSelected: { _, _, _ in
}, togglePeerSelected: { _, _ in
}, togglePeersSelection: { _, _ in

View File

@ -98,7 +98,7 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable {
animationRenderer: context.animationRenderer,
activateSearch: {
},
peerSelected: { _, _, _, _ in
peerSelected: { _, _, _, _, _ in
commandSelected(.shortcut(shortcut), true)
},
disabledPeerSelected: { _, _, _ in

View File

@ -2336,7 +2336,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
.startStandalone(next: { [weak controller] result, options in
if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer, let starsContext = context.starsContext {
let premiumOptions = options.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) }
let giftController = GiftOptionsScreen(context: context, starsContext: starsContext, peerId: peer.id, premiumOptions: premiumOptions)
let giftController = GiftOptionsScreen(context: context, starsContext: starsContext, peerId: peer.id, premiumOptions: premiumOptions, hasBirthday: currentBirthdays?[peer.id] != nil)
giftController.navigationPresentation = .modal
controller?.push(giftController)
}
@ -2402,11 +2402,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return controller
}
public func makeGiftOptionsController(context: AccountContext, peerId: EnginePeer.Id, premiumOptions: [CachedPremiumGiftOption]) -> ViewController {
public func makeGiftOptionsController(context: AccountContext, peerId: EnginePeer.Id, premiumOptions: [CachedPremiumGiftOption], hasBirthday: Bool) -> ViewController {
guard let starsContext = context.starsContext else {
fatalError()
}
let controller = GiftOptionsScreen(context: context, starsContext: starsContext, peerId: peerId, premiumOptions: premiumOptions)
let controller = GiftOptionsScreen(context: context, starsContext: starsContext, peerId: peerId, premiumOptions: premiumOptions, hasBirthday: hasBirthday)
controller.navigationPresentation = .modal
return controller
}

View File

@ -645,7 +645,6 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String)
default:
break
}
break
}
}
}

View File

@ -2,24 +2,39 @@ import Foundation
import SwiftSignalKit
final class FileDownload: NSObject, URLSessionDownloadDelegate {
private let fileSize: Int64?
let fileName: String
let fileSize: Int64?
let isMedia: Bool
private var urlSession: URLSession!
private var completion: ((URL?, Error?) -> Void)?
private var progressHandler: ((Double) -> Void)?
private var task: URLSessionDownloadTask!
init(from url: URL, fileSize: Int64?, progressHandler: @escaping (Double) -> Void, completion: @escaping (URL?, Error?) -> Void) {
self.progressHandler = progressHandler
private let progressPromise = ValuePromise<Double>(0.0)
var progressSignal: Signal<Double, NoError> {
return self.progressPromise.get()
}
init(from url: URL, fileName: String, fileSize: Int64?, isMedia: Bool, progressHandler: @escaping (Double) -> Void, completion: @escaping (URL?, Error?) -> Void) {
self.fileName = fileName
self.fileSize = fileSize
self.isMedia = isMedia
self.completion = completion
self.progressHandler = progressHandler
super.init()
let configuration = URLSessionConfiguration.default
urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
self.urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
let downloadTask = self.urlSession.downloadTask(with: url)
downloadTask.resume()
self.task = downloadTask
}
func cancel() {
self.task.cancel()
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
@ -27,17 +42,18 @@ final class FileDownload: NSObject, URLSessionDownloadDelegate {
if totalBytesExpectedToWrite == -1, let fileSize = self.fileSize {
totalBytesExpectedToWrite = fileSize
}
let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
progressHandler?(progress)
let progress = max(0.0, min(1.0, Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)))
self.progressHandler?(progress)
self.progressPromise.set(progress)
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
completion?(location, nil)
self.completion?(location, nil)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
completion?(nil, error)
self.completion?(nil, error)
}
}

View File

@ -205,7 +205,7 @@ final class FullscreenControlsComponent: Component {
let buttonTitleUpdated = (previousComponent?.hasBack ?? false) != component.hasBack
let animationMultiplier = !component.hasBack ? -1.0 : 1.0
if buttonTitleUpdated {
if buttonTitleUpdated && !self.displayTitle {
isAnimatingTextTransition = true
if let view = self.buttonTitle.view, let snapshotView = view.snapshotView(afterScreenUpdates: false) {

View File

@ -145,6 +145,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
public var isContainerPanning: () -> Bool = { return false }
public var isContainerExpanded: () -> Bool = { return false }
static var activeDownloads: [FileDownload] = []
fileprivate class Node: ViewControllerTracingNode, WKNavigationDelegate, WKUIDelegate, ASScrollViewDelegate {
private weak var controller: WebAppController?
@ -369,6 +371,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
context.fillPath()
})!
strongSelf.placeholderIcon = (image.withRenderingMode(.alwaysTemplate), false)
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
}
}))
}
@ -2400,17 +2405,19 @@ public final class WebAppController: ViewController, AttachmentContainable {
})
}
private var fileDownload: FileDownload?
private weak var fileDownloadTooltip: UndoOverlayController?
fileprivate weak var fileDownloadTooltip: UndoOverlayController?
fileprivate func startDownload(url: String, fileName: String, fileSize: Int64?, isMedia: Bool) {
guard let controller = self.controller else {
return
}
self.webView?.sendEvent(name: "file_download_requested", data: "{status: \"downloading\"}")
self.fileDownload = FileDownload(
var removeImpl: (() -> Void)?
let fileDownload = FileDownload(
from: URL(string: url)!,
fileName: fileName,
fileSize: fileSize,
isMedia: isMedia,
progressHandler: { [weak self] progress in
guard let self else {
return
@ -2432,6 +2439,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
},
completion: { [weak self] resultUrl, _ in
if let resultUrl, let self {
removeImpl?()
let tooltipContent: UndoOverlayContent = .actionSucceeded(title: fileName, text: isMedia ? self.presentationData.strings.WebApp_Download_SavedToPhotos : self.presentationData.strings.WebApp_Download_SavedToFiles, cancel: nil, destructive: false)
if isMedia {
let saveToPhotos: (URL, Bool) -> Void = { url, isVideo in
@ -2496,6 +2505,13 @@ public final class WebAppController: ViewController, AttachmentContainable {
}
}
)
WebAppController.activeDownloads.append(fileDownload)
removeImpl = { [weak fileDownload] in
if let fileDownload {
WebAppController.activeDownloads.removeAll(where: { $0 === fileDownload })
}
}
let text: String
if let fileSize {
@ -2514,7 +2530,11 @@ public final class WebAppController: ViewController, AttachmentContainable {
),
elevatedLayout: false,
position: .top,
action: { _ in
action: { [weak fileDownload] action in
if case .undo = action, let fileDownload {
fileDownload.cancel()
removeImpl?()
}
return true
}
)
@ -2708,8 +2728,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
}
let url = URL(string: "\(scheme)://t.me/\(addressName)\(appName)?startapp&addToHomeScreen")!
UIApplication.shared.open(url)
controller.dismiss()
})
}
@ -3072,18 +3090,55 @@ public final class WebAppController: ViewController, AttachmentContainable {
let hasSettings = self.hasSettings
let activeDownload = WebAppController.activeDownloads.first
let activeDownloadProgress: Signal<Double?, NoError>
if let activeDownload {
activeDownloadProgress = activeDownload.progressSignal
|> map(Optional.init)
|> mapToThrottled { next -> Signal<Double?, NoError> in
return .single(next) |> then(.complete() |> delay(0.2, queue: Queue.mainQueue()))
}
} else {
activeDownloadProgress = .single(nil)
}
let items = combineLatest(queue: Queue.mainQueue(),
context.engine.messages.attachMenuBots(),
context.engine.messages.attachMenuBots() |> take(1),
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.botId)),
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.BotCommands(id: self.botId)),
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.BotPrivacyPolicyUrl(id: self.botId))
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.BotPrivacyPolicyUrl(id: self.botId)),
activeDownloadProgress
)
|> take(1)
|> map { [weak self] attachMenuBots, botPeer, botCommands, privacyPolicyUrl -> ContextController.Items in
|> map { [weak self] attachMenuBots, botPeer, botCommands, privacyPolicyUrl, activeDownloadProgress -> ContextController.Items in
var items: [ContextMenuItem] = []
let attachMenuBot = attachMenuBots.first(where: { $0.peer.id == botId && !$0.flags.contains(.notActivated) })
if let activeDownload, let progress = activeDownloadProgress {
let isActive = progress < 1.0 - .ulpOfOne
let progressString: String
if isActive {
if let fileSize = activeDownload.fileSize {
let downloadedSize = Int64(Double(fileSize) * progress)
progressString = "\(dataSizeString(downloadedSize, formatting: DataSizeStringFormatting(presentationData: presentationData))) / \(dataSizeString(fileSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))"
} else {
progressString = "\(Int32(progress))%"
}
} else {
progressString = activeDownload.isMedia ? presentationData.strings.WebApp_Download_SavedToPhotos : presentationData.strings.WebApp_Download_SavedToFiles
}
items.append(.action(ContextMenuActionItem(text: activeDownload.fileName, textLayout: .secondLineWithValue(progressString), icon: { theme in return isActive ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.primaryColor) : nil }, iconPosition: .right, action: isActive ? { [weak self, weak activeDownload] _, f in
f(.default)
WebAppController.activeDownloads.removeAll(where: { $0 === activeDownload })
activeDownload?.cancel()
if let fileDownloadTooltip = self?.controllerNode.fileDownloadTooltip {
fileDownloadTooltip.dismissWithCommitAction()
}
} : nil)))
items.append(.separator)
}
let attachMenuBot = attachMenuBots.first(where: { $0.peer.id == botId && !$0.flags.contains(.notActivated) })
if hasSettings {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_Settings, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor)

View File

@ -76,7 +76,7 @@ private final class SheetContent: CombinedComponent {
let closeButton = closeButton.update(
component: Button(
content: AnyComponent(Text(text: "Cancel", font: Font.regular(17.0), color: theme.actionSheet.controlAccentColor)),
content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: theme.actionSheet.controlAccentColor)),
action: {
component.dismiss()
}
@ -89,7 +89,7 @@ private final class SheetContent: CombinedComponent {
)
let title = title.update(
component: Text(text: "Share Message", font: Font.bold(17.0), color: theme.list.itemPrimaryTextColor),
component: Text(text: environment.strings.WebApp_ShareMessage_Title, font: Font.bold(17.0), color: theme.list.itemPrimaryTextColor),
availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
transition: .immediate
)
@ -105,7 +105,7 @@ private final class SheetContent: CombinedComponent {
return (TelegramTextAttributes.URL, contents)
})
let amountInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("\(component.botName) mini app suggests you to send this message to a chat you select.", attributes: amountMarkdownAttributes, textAlignment: .natural))
let amountInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.WebApp_ShareMessage_Info(component.botName).string, attributes: amountMarkdownAttributes, textAlignment: .natural))
let amountFooter = AnyComponent(MultilineTextComponent(
text: .plain(amountInfoString),
maximumNumberOfLines: 0,
@ -192,7 +192,7 @@ private final class SheetContent: CombinedComponent {
theme: theme,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Message Preview".uppercased(),
string: environment.strings.WebApp_ShareMessage_PreviewTitle.uppercased(),
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: theme.list.freeTextColor
)),
@ -230,7 +230,7 @@ private final class SheetContent: CombinedComponent {
contentSize.height += amountSection.size.height
contentSize.height += 32.0
let buttonString: String = "Share With..."
let buttonString: String = environment.strings.WebApp_ShareMessage_Share
let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
let button = button.update(

View File

@ -1,5 +1,5 @@
{
"app": "11.4",
"app": "11.4.1",
"xcode": "16.0",
"bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff",
"macos": "15.0"