diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 41cc2943d5..8c386fe18d 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -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..."; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 8ee52a8839..1c8fad66a4 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -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 diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 6a6d3306d2..349fbfa725 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -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) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 2b628cdf7d..f1ba822484 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -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 { diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index f0c25a3f16..5eb557c240 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -61,8 +61,9 @@ final class ChatListSearchInteraction { let getSelectedMessageIds: () -> Set? 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?, 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?, 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 diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index ebf73ea3ab..c052301330 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -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: { diff --git a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift index e7d6cfc338..bf3f6756d4 100644 --- a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift @@ -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 diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 07ff564746..a4879d5143 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -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) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index e6d75143ab..c7ba701710 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -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) }) diff --git a/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift b/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift index 80b3dbd10f..cba245039f 100644 --- a/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift +++ b/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift @@ -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 diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 2f6c753e4a..b5610e2dea 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -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() diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 1d953a2db4..06edf2f4fe 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -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() diff --git a/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/Sources/ChatButtonKeyboardInputNode.swift b/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/Sources/ChatButtonKeyboardInputNode.swift index f58859eb8e..5383690a2c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/Sources/ChatButtonKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/Sources/ChatButtonKeyboardInputNode.swift @@ -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: diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift index 3a524f7488..9e7703d01b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift @@ -585,7 +585,7 @@ public final class ChatInlineSearchResultsListComponent: Component { animationRenderer: component.context.animationRenderer, activateSearch: { }, - peerSelected: { _, _, _, _ in + peerSelected: { _, _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift index ec74e1c526..0a346026bc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift @@ -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: diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index f27b14029e..185247b9f4 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -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) diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 176791b187..54ef391e71 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -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) }) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift index 1759c73b7f..bbf345b6d4 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift @@ -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 }, diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index c101a966e1..51531704cd 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -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) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift index 4ccb715b37..49a8cd1bf3 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift @@ -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) diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift index a038c5413b..4704c3fbd3 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift @@ -130,7 +130,7 @@ final class GreetingMessageListItemComponent: Component { animationRenderer: component.context.animationRenderer, activateSearch: { }, - peerSelected: { _, _, _, _ in + peerSelected: { _, _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift index 1475fb6bbb..aecb9b0677 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift index 2955564155..547bc03589 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift @@ -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() diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift index fafb206d39..1d2d81398d 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 891e34fe54..693a426bec 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -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 } diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 870ffcb25a..aa4e4ca88b 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 9878fcc499..103bd38194 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -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: diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 21892f43af..5931ca0819 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift index 719a632d4e..af087f630f 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift @@ -98,7 +98,7 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable { animationRenderer: context.animationRenderer, activateSearch: { }, - peerSelected: { _, _, _, _ in + peerSelected: { _, _, _, _, _ in commandSelected(.shortcut(shortcut), true) }, disabledPeerSelected: { _, _, _ in diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index b81147da2c..447c7a04b8 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -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 } diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 6d879d92f7..c91b004358 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -645,7 +645,6 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String) default: break } - break } } } diff --git a/submodules/WebUI/Sources/FileDownload.swift b/submodules/WebUI/Sources/FileDownload.swift index 815a109a61..7de8729237 100644 --- a/submodules/WebUI/Sources/FileDownload.swift +++ b/submodules/WebUI/Sources/FileDownload.swift @@ -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(0.0) + var progressSignal: Signal { + 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) } } diff --git a/submodules/WebUI/Sources/FullscreenControlsComponent.swift b/submodules/WebUI/Sources/FullscreenControlsComponent.swift index a055b51e1d..e7a693b4b9 100644 --- a/submodules/WebUI/Sources/FullscreenControlsComponent.swift +++ b/submodules/WebUI/Sources/FullscreenControlsComponent.swift @@ -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) { diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 9d249fd6c7..fb7f715de7 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -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 + if let activeDownload { + activeDownloadProgress = activeDownload.progressSignal + |> map(Optional.init) + |> mapToThrottled { next -> Signal 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) diff --git a/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift b/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift index 3230a718bf..62eb845139 100644 --- a/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift +++ b/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift @@ -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( diff --git a/versions.json b/versions.json index dc9b8e2579..5532708914 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "11.4", + "app": "11.4.1", "xcode": "16.0", "bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff", "macos": "15.0"