From 43bb74677688dd592d32e18569b887b61ae81657 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Sun, 20 Jul 2025 19:26:13 +0200 Subject: [PATCH 1/9] Hide in-progress features --- MODULE.bazel.lock | 2 +- .../Sources/PeerInfoHeaderNode.swift | 2 +- .../Sources/PeerInfoScreen.swift | 4 +- .../Sources/PeerInfoStoryPaneNode.swift | 960 ++---------------- 4 files changed, 96 insertions(+), 872 deletions(-) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 9cf530b698..cf536336c5 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -159,7 +159,7 @@ "moduleExtensions": { "@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": { "general": { - "bzlTransitiveDigest": "Ync9nL0AbHC6ondeEY7fBjBjLxojTsiXcJh65ZDTRlA=", + "bzlTransitiveDigest": "IK7QnlhcNBu2jc4wZoGZeDTu3keF2LldFiFUINRcKvo=", "usagesDigest": "lfcV4HxPD+NLaRIT/v7BtSGFgE7c9xrWU7jDiwBAxzo=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index 42bec125e2..794b13e9e7 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -1917,7 +1917,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { let apparentBackgroundHeight = (1.0 - transitionFraction) * backgroundHeight + transitionFraction * transitionSourceHeight var subtitleRatingSize: CGSize? - if let cachedData = cachedData as? CachedUserData, let starRating = cachedData.starRating { + if !"".isEmpty, let cachedData = cachedData as? CachedUserData, let starRating = cachedData.starRating { let subtitleRating: ComponentView var subtitleRatingTransition = ComponentTransition(transition) if let current = self.subtitleRating { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 400e9140a8..8987ea9da8 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -11710,7 +11710,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } }))) - if pane.canReorder() { + /*if pane.canReorder() { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuReorder, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { [weak pane] _, a in @@ -11742,7 +11742,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro pane.presentDeleteCurrentStoryFolder() } }))) - } + }*/ if let language = pane.currentBotPreviewLanguage { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuDeleteLanguage(language.name).string, textColor: .destructive, icon: { theme in diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index 6814d08189..4335317e2d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -44,8 +44,6 @@ import MultilineTextComponent import LocationUI import TabSelectorComponent import LanguageSelectionScreen -import PromptUI -import BottomButtonPanelComponent private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6) private let mediaBadgeTextColor = UIColor.white @@ -1570,7 +1568,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr private var mapInfoNode: LocationInfoListItemNode? private var searchHeader: ComponentView? - private var folderTab: ComponentView? + private var botPreviewLanguageTab: ComponentView? private var botPreviewFooter: ComponentView? private var barBackgroundLayer: SimpleLayer? @@ -1583,10 +1581,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr private var reorderedIds: [StoryId]? private var itemCount: Int? private var didUpdateItemsOnce: Bool = false - private var itemTabId: AnyHashable? private var selectionPanel: ComponentView? - private var actionPanel: ComponentView? private var isDeceleratingAfterTracking = false @@ -1652,8 +1648,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr public var tabBarOffset: CGFloat { if case .botPreview = self.scope { return 0.0 - } else if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded { - return 0.0 } else { return self.itemGrid.coveringInsetOffset } @@ -1667,9 +1661,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr private var currentBotPreviewLanguages: [StoryListContext.State.Language] = [] private var removedBotPreviewLanguages = Set() - private var currentStoryFolders: [StoryListContext.State.Folder] = [] - private var removedStoryFolders = Set() - private var numberOfItemsToRequest: Int = 50 private var isRequestingView: Bool = false private var isFirstHistoryView: Bool = true @@ -1684,7 +1675,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr private let maxBotPreviewCount: Int private let defaultListSource: StoryListContext - private var cachedListSources: [AnyHashable: StoryListContext] = [:] + private var cachedListSources: [String: StoryListContext] = [:] public var currentBotPreviewLanguage: (id: String, name: String)? { guard let listSource = self.listSource as? BotPreviewStoryListContext else { @@ -1698,19 +1689,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } return (language.id, language.name) } - - public var currentStoryFolder: (id: Int64, title: String)? { - guard let listSource = self.listSource as? PeerStoryListContext else { - return nil - } - guard let id = listSource.folderId else { - return nil - } - guard let folder = self.currentStoryFolders.first(where: { $0.id == id }) else { - return nil - } - return (folder.id, folder.title) - } public var openCurrentDate: (() -> Void)? public var paneDidScroll: (() -> Void)? @@ -1822,7 +1800,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr return } - if self.isProfileEmbedded || !self.shouldOpenItemsWhileInSelectionMode { + if self.isProfileEmbedded { if let selectedIds = self.itemInteraction.selectedIds { self.itemInteraction.toggleSelection(item.story.id, !selectedIds.contains(item.story.id)) return @@ -2471,122 +2449,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr var items: [ContextMenuItem] = [] if canManage, case let .peer(peerId, _, isArchived) = self.scope { - if peerId == self.context.account.peerId && self.isProfileEmbedded { - if let folder = self.currentStoryFolder { - //TODO:localize - items.append(.action(ContextMenuActionItem(text: "Remove from Album", textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] _, f in - guard let self else { - f(.default) - return - } - - if let listSource = self.listSource as? PeerStoryListContext { - listSource.removeFromFolder(id: folder.id, itemIds: [item.id]) - } - - f(.dismissWithoutContent) - }))) - } else { - //TODO:localize - items.append(.action(ContextMenuActionItem(text: "Add to Album", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Folder"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in - guard let self, let c else { - f(.default) - return - } - - Task { @MainActor [weak self, weak c] in - guard let self, let c else { - return - } - - let (peerReference, folderPreviews) = await PeerStoryListContext.folderPreviews(peerId: peerId, account: self.context.account).get() - - var items: [ContextMenuItem] = [] - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Common_Back, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) - }, iconPosition: .left, action: { c ,f in - c?.popItems() - }))) - items.append(.separator) - - items.append(.action(ContextMenuActionItem(text: "New Album", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Folder"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { [weak self] c, f in - guard let self else { - f(.default) - return - } - - c?.dismiss(completion: { [weak self] in - guard let self else { - return - } - self.presentAddStoryFolder(addItems: [item]) - }) - }))) - - for folderPreview in folderPreviews { - var iconSource: ContextMenuActionItemIconSource? - if let story = folderPreview.item { - var imageSignal: Signal? - - var selectedMedia: Media? - if let image = story.media._asMedia() as? TelegramMediaImage { - selectedMedia = image - } else if let file = story.media._asMedia() as? TelegramMediaFile { - selectedMedia = file - } - - if let selectedMedia { - if let result = self.directMediaImageCache.getImage(peer: peerReference, story: story, media: selectedMedia, width: 24, aspectRatio: 1.0, possibleWidths: [24], includeBlurred: false, synchronous: true) { - if let loadSignal = result.loadSignal { - imageSignal = .single(result.image) |> then(loadSignal) - } else { - imageSignal = .single(result.image) - } - } - } - - if let imageSignal { - iconSource = ContextMenuActionItemIconSource( - size: CGSize(width: 24.0, height: 24.0), - cornerRadius: 5.0, - signal: imageSignal - ) - } - } - - var icon: (PresentationTheme) -> UIImage? = { _ in nil } - if iconSource == nil { - icon = { theme in - return generateImage(CGSize(width: 24.0, height: 24.0), opaque: false, scale: nil, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(theme.contextMenu.primaryColor.withMultipliedAlpha(0.1).cgColor) - context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: 5.0).cgPath) - context.fillPath() - }) - } - } - - items.append(.action(ContextMenuActionItem(text: folderPreview.folder.title, icon: icon, iconSource: iconSource, iconPosition: .left, action: { [weak self] c, f in - guard let self else { - f(.default) - return - } - - c?.dismiss(completion: {}) - - if let listSource = self.listSource as? PeerStoryListContext { - listSource.addToFolder(id: folderPreview.folder.id, items: [item]) - } - }))) - } - - c.pushItems(items: .single(ContextController.Items(content: .list(items)))) - } - }))) - } - items.append(.separator) - } - items.append(.action(ContextMenuActionItem(text: !isArchived ? self.presentationData.strings.StoryList_ItemAction_Archive : self.presentationData.strings.StoryList_ItemAction_Unarchive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isArchived ? "Chat/Context Menu/Archive" : "Chat/Context Menu/Unarchive"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in guard let self else { f(.default) @@ -2883,12 +2745,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr title = self.presentationData.strings.BotPreviews_SubtitleEmpty } } else { - if state.isLoading { - title = self.presentationData.strings.BotPreviews_SubtitleLoading - } else { - //TODO:localize - title = "no stories" - } + title = "" } } else if case let .peer(_, isSaved, isArchived) = self.scope { if isSaved { @@ -2929,13 +2786,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } botPreviewLanguages.sort(by: { $0.name < $1.name }) - var storyFolders = self.currentStoryFolders - for folder in state.availableFolders { - if !storyFolders.contains(where: { $0.id == folder.id }) && !self.removedStoryFolders.contains(folder.id) { - storyFolders.append(folder) - } - } - var hadLocalItems = false if let currentListState = self.currentListState { for item in currentListState.items { @@ -2963,9 +2813,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr self.updateItemsFromState(state: state, firstTime: firstTime, reloadAtTop: reloadAtTop, synchronous: synchronous, animated: false) - if self.currentBotPreviewLanguages != botPreviewLanguages || self.currentStoryFolders != storyFolders || reloadAtTop { + if self.currentBotPreviewLanguages != botPreviewLanguages || reloadAtTop { self.currentBotPreviewLanguages = botPreviewLanguages - self.currentStoryFolders = storyFolders if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams { self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: synchronous, transition: .immediate) } @@ -3023,11 +2872,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr isReorderable = !item.storyItem.isPending case let .peer(id, _, _): if id == self.context.account.peerId { - if self.currentStoryFolder != nil { - isReorderable = true - } else { - isReorderable = state.pinnedIds.contains(item.storyItem.id) - } + isReorderable = state.pinnedIds.contains(item.storyItem.id) } case let .search(peerId, _): if peerId != nil { @@ -3075,40 +2920,13 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr ) self.itemCount = state.totalCount - - var itemTabId: AnyHashable? - if let folder = self.currentStoryFolder { - itemTabId = AnyHashable(folder.id) - } - let previousItemTabId = self.itemTabId - self.itemTabId = itemTabId - - var animateDirection: Bool? - if firstTime { - if previousItemTabId == nil && itemTabId == nil { - } else if let previousItemTabId, let itemTabId { - let previousIndex = self.currentStoryFolders.firstIndex(where: { AnyHashable($0.id) == previousItemTabId }) - let updatedIndex = self.currentStoryFolders.firstIndex(where: { AnyHashable($0.id) == itemTabId }) - if let previousIndex, let updatedIndex { - animateDirection = updatedIndex > previousIndex - } - } else { - if previousItemTabId != nil { - if itemTabId == nil { - animateDirection = false - } - } else if itemTabId != nil { - animateDirection = true - } - } - } let currentSynchronous = synchronous && firstTime let currentReloadAtTop = reloadAtTop && firstTime - self.updateHistory(items: items, pinnedIds: Set(state.pinnedIds), synchronous: currentSynchronous, reloadAtTop: currentReloadAtTop, animated: animated, animateDirection: animateDirection) + self.updateHistory(items: items, pinnedIds: Set(state.pinnedIds), synchronous: currentSynchronous, reloadAtTop: currentReloadAtTop, animated: animated) } - private func updateHistory(items: SparseItemGrid.Items, pinnedIds: Set, synchronous: Bool, reloadAtTop: Bool, animated: Bool, animateDirection: Bool?) { + private func updateHistory(items: SparseItemGrid.Items, pinnedIds: Set, synchronous: Bool, reloadAtTop: Bool, animated: Bool) { var transition: ContainedViewLayoutTransition = .immediate if case .location = self.scope, let previousItems = self.items, previousItems.items.count == 0, previousItems.count != 0, items.items.count == 0, items.count == 0 { transition = .animated(duration: 0.3, curve: .spring) @@ -3119,58 +2937,18 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams { var gridSnapshot: UIView? - var emptyStateSnapshot: UIView? - if animateDirection != nil { + if case .botPreview = scope { + } else if reloadAtTop { gridSnapshot = self.itemGrid.view.snapshotView(afterScreenUpdates: false) - - if let emptyStateView = self.emptyStateView?.view { - emptyStateSnapshot = emptyStateView.snapshotView(afterScreenUpdates: false) - emptyStateSnapshot?.frame = emptyStateView.frame - } - } else { - if case .botPreview = self.scope { - } else if !self.currentStoryFolders.isEmpty { - } else if case let .peer(id, _, isArchived) = self.scope, id == self.context.account.peerId, !isArchived { - } else if reloadAtTop { - gridSnapshot = self.itemGrid.view.snapshotView(afterScreenUpdates: false) - } } - - self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: false, transition: transition, animateGridItems: animated, animateBottomPanel: animateDirection != nil) + self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: false, transition: transition, animateGridItems: animated) self.updateSelectedItems(animated: false) - - if let gridSnapshot { - self.view.insertSubview(gridSnapshot, aboveSubview: self.contextGestureContainerNode.view) - gridSnapshot.frame = self.itemGrid.frame - if let animateDirection { - let directionFactor: CGFloat = animateDirection ? 1.0 : -1.0 - - let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) - transition.animatePositionAdditive(node: self.itemGrid, offset: CGPoint(x: size.width * directionFactor, y: 0.0)) - transition.animatePosition(layer: gridSnapshot.layer, from: CGPoint(), to: CGPoint(x: size.width * (-directionFactor), y: 0.0), removeOnCompletion: false, additive: true, completion: { [weak gridSnapshot] _ in - gridSnapshot?.removeFromSuperview() - }) - } else { - gridSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak gridSnapshot] _ in - gridSnapshot?.removeFromSuperview() - }) - } - } - if let emptyStateSnapshot, let animateDirection { - self.view.insertSubview(emptyStateSnapshot, belowSubview: self.contextGestureContainerNode.view) - let directionFactor: CGFloat = animateDirection ? 1.0 : -1.0 - - let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) - transition.animatePosition(layer: emptyStateSnapshot.layer, from: CGPoint(), to: CGPoint(x: size.width * (-directionFactor), y: 0.0), removeOnCompletion: false, additive: true, completion: { [weak emptyStateSnapshot] _ in - emptyStateSnapshot?.removeFromSuperview() + if let gridSnapshot = gridSnapshot { + self.view.addSubview(gridSnapshot) + gridSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak gridSnapshot] _ in + gridSnapshot?.removeFromSuperview() }) } - if let emptyStateView = self.emptyStateView?.view, let animateDirection { - let directionFactor: CGFloat = animateDirection ? 1.0 : -1.0 - - let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) - transition.animatePositionAdditive(layer: emptyStateView.layer, offset: CGPoint(x: size.width * directionFactor, y: 0.0)) - } } self.isEmptyUpdated(self.isEmpty) @@ -3187,14 +2965,11 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr if case .botPreview = self.scope { } else if case let .peer(id, _, _) = self.scope { if id == self.context.account.peerId { - if self.currentStoryFolder != nil { + let maxPinnedIndex = items.items.lastIndex(where: { ($0 as? VisualMediaItem)?.isPinned == true }) + if let maxPinnedIndex { + toIndex = min(toIndex, maxPinnedIndex) } else { - let maxPinnedIndex = items.items.lastIndex(where: { ($0 as? VisualMediaItem)?.isPinned == true }) - if let maxPinnedIndex { - toIndex = min(toIndex, maxPinnedIndex) - } else { - return - } + return } } } else { @@ -3606,10 +3381,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr if let _ = self.mapNode { self.updateMapLayout(size: currentParams.size, topInset: currentParams.topInset, bottomInset: currentParams.bottomInset, deviceMetrics: currentParams.deviceMetrics, transition: transition) } - if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded { - self.updateFolderTab(size: currentParams.size, topInset: currentParams.topInset, transition: transition) - } else if case .botPreview = self.scope, self.canManageStories { - self.updateFolderTab(size: currentParams.size, topInset: currentParams.topInset, transition: transition) + if case .botPreview = self.scope, self.canManageStories { + self.updateBotPreviewLanguageTab(size: currentParams.size, topInset: currentParams.topInset, transition: transition) self.updateBotPreviewFooter(size: currentParams.size, bottomInset: 0.0, transition: transition) } } @@ -3765,137 +3538,40 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } } - private func updateFolderTab(size: CGSize, topInset: CGFloat, transition: ContainedViewLayoutTransition) { - var displayFolderTab = false - if case .botPreview = self.scope, self.canManageStories { - displayFolderTab = true - } else if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded { - displayFolderTab = true - } - - if !displayFolderTab { + private func updateBotPreviewLanguageTab(size: CGSize, topInset: CGFloat, transition: ContainedViewLayoutTransition) { + guard case .botPreview = self.scope, self.canManageStories else { return } - let folderTab: ComponentView - if let current = self.folderTab { - folderTab = current + let botPreviewLanguageTab: ComponentView + if let current = self.botPreviewLanguageTab { + botPreviewLanguageTab = current } else { - folderTab = ComponentView() - self.folderTab = folderTab + botPreviewLanguageTab = ComponentView() + self.botPreviewLanguageTab = botPreviewLanguageTab } - var folderItems: [TabSelectorComponent.Item] = [] - let mainTitle: String - let addTitle: String - if case .botPreview = self.scope { - mainTitle = self.presentationData.strings.BotPreviews_LanguageTab_Main - addTitle = self.presentationData.strings.BotPreviews_LanguageTab_Add - } else { - //TODO:localize - mainTitle = "All Stories" - addTitle = "+ Add Album" - } - folderItems.append(TabSelectorComponent.Item( + var languageItems: [TabSelectorComponent.Item] = [] + languageItems.append(TabSelectorComponent.Item( id: AnyHashable("_main"), - title: mainTitle + title: self.presentationData.strings.BotPreviews_LanguageTab_Main )) - - if case .botPreview = self.scope { - for language in self.currentBotPreviewLanguages { - folderItems.append(TabSelectorComponent.Item( - id: AnyHashable(language.id), - title: language.name - )) - } - } else { - for folder in self.currentStoryFolders { - folderItems.append(TabSelectorComponent.Item( - id: AnyHashable(folder.id), - title: folder.title, - isReorderable: self.canManageStories, - contextAction: self.canManageStories ? { [weak self] sourceNode, gesture in - guard let self else { - return - } - guard let sourceNode = sourceNode as? ContextExtractedContentContainingNode else { - return - } - guard let controller = self.parentController else { - return - } - - var items: [ContextMenuItem] = [] - - //TODO:localize - items.append(.action(ContextMenuActionItem(text: "Add Stories", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddStoryIcon"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - guard let self else { - a(.default) - return - } - - a(.default) - - self.presentAddStoriesToFolder() - }))) - - //TODO:localize - items.append(.action(ContextMenuActionItem(text: "Reorder", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - guard let self else { - a(.default) - return - } - - a(.default) - - self.beginReordering() - }))) - - //TODO:localize - items.append(.action(ContextMenuActionItem(text: "Delete Album", textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] _, f in - guard let self else { - f(.default) - return - } - - f(.dismissWithoutContent) - - self.presentDeleteStoryFolder(id: folder.id) - }))) - - let presentationData = self.presentationData - let contextController = ContextController( - presentationData: presentationData, - source: .extracted(ItemExtractedContentSource( - sourceNode: sourceNode, - containerView: controller.view, - keepInPlace: false - )), - items: .single(ContextController.Items(content: .list(items))), - recognizer: nil, - gesture: gesture - ) - controller.presentInGlobalOverlay(contextController) - } : nil - )) - } + for language in self.currentBotPreviewLanguages { + languageItems.append(TabSelectorComponent.Item( + id: AnyHashable(language.id), + title: language.name + )) } - folderItems.append(TabSelectorComponent.Item( + languageItems.append(TabSelectorComponent.Item( id: AnyHashable("_add"), - title: addTitle + title: self.presentationData.strings.BotPreviews_LanguageTab_Add )) - var selectedId = AnyHashable("_main") + var selectedLanguageId = "_main" if let listSource = self.listSource as? BotPreviewStoryListContext, let language = listSource.language { - selectedId = AnyHashable(language) - } else if let listSource = self.listSource as? PeerStoryListContext, let folderId = listSource.folderId { - selectedId = AnyHashable(folderId) + selectedLanguageId = language } - let folderTabSize = folderTab.update( + let botPreviewLanguageTabSize = botPreviewLanguageTab.update( transition: ComponentTransition(transition), component: AnyComponent(TabSelectorComponent( colors: TabSelectorComponent.Colors( @@ -3908,95 +3584,39 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr spacing: 9.0, verticalInset: 11.0 ), - items: folderItems, - selectedId: selectedId, - reorderItem: self.isReordering ? { [weak self] fromId, toId in - guard let self else { - return - } - guard let sourceId = fromId.base as? Int64 else { - return - } - guard let targetId = toId.base as? Int64 else { - return - } - guard let sourceIndex = self.currentStoryFolders.firstIndex(where: { $0.id == sourceId }), let targetIndex = self.currentStoryFolders.firstIndex(where: { $0.id == targetId }) else { - return - } - let folder = self.currentStoryFolders[sourceIndex] - if targetIndex < sourceIndex { - self.currentStoryFolders.remove(at: sourceIndex) - self.currentStoryFolders.insert(folder, at: targetIndex) - } else { - self.currentStoryFolders.insert(folder, at: targetIndex + 1) - self.currentStoryFolders.remove(at: sourceIndex) - } - - self.update(transition: .animated(duration: 0.2, curve: .easeInOut)) - } : nil, + items: languageItems, + selectedId: AnyHashable(selectedLanguageId), setSelectedId: { [weak self] id in - guard let self else { + guard let self, let id = id.base as? String else { return } - - self.expandIfNeeded?() - - if let id = id.base as? String { - if id == "_add" { - if case .botPreview = self.scope { - self.presentAddBotPreviewLanguage() - } else { - self.presentAddStoryFolder() - } - } else if id == "_main" { - if case .botPreview = self.scope { - self.setBotPreviewLanguage(id: nil, assumeEmpty: false) - } else { - self.setStoryFolder(id: nil, assumeEmpty: false) - } - } else if let language = self.currentBotPreviewLanguages.first(where: { $0.id == id }) { - self.setBotPreviewLanguage(id: language.id, assumeEmpty: false) - } - } else if let id = id.base as? Int64 { - if let folder = self.currentStoryFolders.first(where: { $0.id == id }) { - self.setStoryFolder(id: folder.id, assumeEmpty: false) - } + if id == "_add" { + self.presentAddBotPreviewLanguage() + } else if id == "_main" { + self.setBotPreviewLanguage(id: nil, assumeEmpty: false) + } else if let language = self.currentBotPreviewLanguages.first(where: { $0.id == id }) { + self.setBotPreviewLanguage(id: language.id, assumeEmpty: false) } } )), environment: {}, containerSize: CGSize(width: size.width, height: 44.0) ) - var folderTabFrame = CGRect(origin: CGPoint(x: floor((size.width - folderTabSize.width) * 0.5), y: topInset - 11.0), size: folderTabSize) + var botPreviewLanguageTabFrame = CGRect(origin: CGPoint(x: floor((size.width - botPreviewLanguageTabSize.width) * 0.5), y: topInset - 11.0), size: botPreviewLanguageTabSize) let effectiveScrollingOffset: CGFloat effectiveScrollingOffset = self.itemGrid.scrollingOffset - folderTabFrame.origin.y -= effectiveScrollingOffset + botPreviewLanguageTabFrame.origin.y -= effectiveScrollingOffset let isSelectingOrReordering = self.isReordering || self.itemInteraction.selectedIds != nil - if let folderTabView = folderTab.view { - if folderTabView.superview == nil { - self.view.addSubview(folderTabView) + if let botPreviewLanguageTabView = botPreviewLanguageTab.view { + if botPreviewLanguageTabView.superview == nil { + self.view.addSubview(botPreviewLanguageTabView) } - transition.updateFrame(view: folderTabView, frame: folderTabFrame) - - var areTabsDisabled = false - - if case .botPreview = self.scope { - if isSelectingOrReordering { - areTabsDisabled = true - } - } else { - if self.itemInteraction.selectedIds != nil { - areTabsDisabled = true - } - } - - transition.updateAlpha(layer: folderTabView.layer, alpha: areTabsDisabled ? 0.5 : 1.0) - folderTabView.isUserInteractionEnabled = !areTabsDisabled - - folderTabView.disablesInteractiveTransitionGestureRecognizer = self.isReordering + transition.updateFrame(view: botPreviewLanguageTabView, frame: botPreviewLanguageTabFrame) + transition.updateAlpha(layer: botPreviewLanguageTabView.layer, alpha: isSelectingOrReordering ? 0.5 : 1.0) + botPreviewLanguageTabView.isUserInteractionEnabled = !isSelectingOrReordering } } @@ -4072,10 +3692,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { - self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: synchronous, transition: transition, animateGridItems: false, animateBottomPanel: transition.isAnimated) + self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: synchronous, transition: transition, animateGridItems: false) } - private func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition, animateGridItems: Bool, animateBottomPanel: Bool) { + private func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition, animateGridItems: Bool) { self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) var gridTopInset = topInset @@ -4109,15 +3729,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr mapOptionsNode.updateLayout(size: mapOptionsFrame.size, leftInset: sideInset, rightInset: sideInset, transition: transition) } - var hasBarBackground = false - if self.isProfileEmbedded { - if case .botPreview = self.scope { - hasBarBackground = true - } else if case let .peer(id, _, isArchived) = self.scope, id == self.context.account.peerId, !isArchived { - hasBarBackground = true - } - } - if hasBarBackground { + if self.isProfileEmbedded, case .botPreview = self.scope { let barBackgroundLayer: SimpleLayer if let current = self.barBackgroundLayer { barBackgroundLayer = current @@ -4133,29 +3745,15 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr var listBottomInset = bottomInset var bottomInset = bottomInset - var displayFolderTab = false if case .botPreview = self.scope, self.canManageStories { - displayFolderTab = true - } else if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded { - displayFolderTab = true - } - - if displayFolderTab { - var folderTabsTransition = transition - if animateBottomPanel && !folderTabsTransition.isAnimated { - folderTabsTransition = .animated(duration: 0.4, curve: .spring) - } - - updateFolderTab(size: size, topInset: topInset, transition: folderTabsTransition) + updateBotPreviewLanguageTab(size: size, topInset: topInset, transition: transition) gridTopInset += 50.0 - if case .botPreview = self.scope { - updateBotPreviewFooter(size: size, bottomInset: 0.0, transition: transition) - if let botPreviewFooterView = self.botPreviewFooter?.view { + updateBotPreviewFooter(size: size, bottomInset: 0.0, transition: transition) + if let botPreviewFooterView = self.botPreviewFooter?.view { listBottomInset += 18.0 + botPreviewFooterView.bounds.height } } - } if self.isProfileEmbedded, let selectedIds = self.itemInteraction.selectedIds, self.canManageStories, case let .peer(peerId, _, isArchived) = self.scope { let selectionPanel: ComponentView @@ -4335,206 +3933,13 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } else if let selectionPanel = self.selectionPanel { self.selectionPanel = nil if let selectionPanelView = selectionPanel.view { - transition.updateFrame(view: selectionPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height), size: selectionPanelView.bounds.size), completion: { [weak selectionPanelView] _ in - selectionPanelView?.removeFromSuperview() - }) - } - } - - var actionPanelGeneralTransition: ComponentTransition - if animateBottomPanel && !transition.isAnimated { - actionPanelGeneralTransition = .spring(duration: 0.4) - } else { - actionPanelGeneralTransition = ComponentTransition(transition) - } - if self.selectionPanel == nil, self.isProfileEmbedded, self.canManageStories, case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded, self.currentStoryFolder != nil, let items = self.items, !items.items.isEmpty { - let actionPanel: ComponentView - var actionPanelTransition = ComponentTransition(transition) - if let current = self.actionPanel { - actionPanel = current - } else { - actionPanelTransition = actionPanelTransition.withAnimation(.none) - actionPanel = ComponentView() - self.actionPanel = actionPanel - } - - //TODO:localize - let actionPanelSize = actionPanel.update( - transition: actionPanelTransition, - component: AnyComponent(BottomButtonPanelComponent( - theme: presentationData.theme, - title: "Add Stories", - label: nil, - isEnabled: true, - insets: UIEdgeInsets(top: 0.0, left: sideInset + 12.0, bottom: bottomInset, right: sideInset + 12.0), - action: { [weak self] in - guard let self else { - return - } - self.presentAddStoriesToFolder() - } - )), - environment: {}, - containerSize: size - ) - let actionPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - actionPanelSize.height), size: actionPanelSize) - if let actionPanelView = actionPanel.view { - if actionPanelView.superview == nil { - self.view.addSubview(actionPanelView) - actionPanelGeneralTransition.animatePosition(layer: actionPanelView.layer, from: CGPoint(x: 0.0, y: actionPanelFrame.height), to: CGPoint(), additive: true) - } - actionPanelTransition.setFrame(view: actionPanelView, frame: actionPanelFrame) - } - bottomInset = actionPanelSize.height - listBottomInset += actionPanelSize.height - } else if let actionPanel = self.actionPanel { - self.actionPanel = nil - if let actionPanelView = actionPanel.view { - actionPanelGeneralTransition.setFrame(view: actionPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height), size: actionPanelView.bounds.size), completion: { [weak actionPanelView] _ in - actionPanelView?.removeFromSuperview() - }) + transition.updateFrame(view: selectionPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height), size: selectionPanelView.bounds.size)) } } transition.updateFrame(node: self.contextGestureContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) - if case let .peer(peerId, _, isArchived) = self.scope, let items = self.items, items.items.isEmpty, items.count == 0 { - if peerId == self.context.account.peerId, self.isProfileEmbedded, self.currentStoryFolder != nil { - let emptyStateView: ComponentView - var emptyStateTransition = ComponentTransition(transition) - if let current = self.emptyStateView { - emptyStateView = current - } else { - emptyStateTransition = .immediate - emptyStateView = ComponentView() - self.emptyStateView = emptyStateView - } - - //TODO:localize - let emptyStateSize = emptyStateView.update( - transition: emptyStateTransition, - component: AnyComponent(EmptyStateIndicatorComponent( - context: self.context, - theme: presentationData.theme, - fitToHeight: self.isProfileEmbedded, - animationName: nil, - title: "Organize Your Stories", - text: "Add some stories to this album.", - actionTitle: "Add to Album", - action: { [weak self] in - guard let self else { - return - } - self.presentAddStoriesToFolder() - }, - additionalActionTitle: nil, - additionalAction: {}, - additionalActionSeparator: nil - )), - environment: {}, - containerSize: CGSize(width: size.width, height: size.height - gridTopInset - bottomInset) - ) - - let emptyStateFrame: CGRect - if self.isProfileEmbedded { - emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: max(gridTopInset + 22.0, floor((visibleHeight - bottomInset - emptyStateSize.height) * 0.5))), size: emptyStateSize) - } else { - emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: gridTopInset), size: emptyStateSize) - } - - if let emptyStateComponentView = emptyStateView.view { - if emptyStateComponentView.superview == nil { - self.view.addSubview(emptyStateComponentView) - if self.didUpdateItemsOnce { - emptyStateComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - emptyStateTransition.setFrame(view: emptyStateComponentView, frame: emptyStateFrame) - } - - let backgroundColor: UIColor - if self.isProfileEmbedded, case .botPreview = self.scope { - backgroundColor = presentationData.theme.list.blocksBackgroundColor - } else if self.isProfileEmbedded { - backgroundColor = presentationData.theme.list.blocksBackgroundColor - } else { - backgroundColor = presentationData.theme.list.blocksBackgroundColor - } - - if self.didUpdateItemsOnce { - ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut)).setBackgroundColor(view: self.view, color: backgroundColor) - } else { - self.view.backgroundColor = backgroundColor - } - } else { - let emptyStateView: ComponentView - var emptyStateTransition = ComponentTransition(transition) - if let current = self.emptyStateView { - emptyStateView = current - } else { - emptyStateTransition = .immediate - emptyStateView = ComponentView() - self.emptyStateView = emptyStateView - } - let emptyStateSize = emptyStateView.update( - transition: emptyStateTransition, - component: AnyComponent(EmptyStateIndicatorComponent( - context: self.context, - theme: presentationData.theme, - fitToHeight: self.isProfileEmbedded, - animationName: "StoryListEmpty", - title: isArchived ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyPosts_Title, - text: isArchived ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyPosts_Text, - actionTitle: isArchived ? nil : presentationData.strings.StoryList_SavedAddAction, - action: { [weak self] in - guard let self else { - return - } - self.emptyAction?() - }, - additionalActionTitle: (isArchived || self.isProfileEmbedded) ? nil : presentationData.strings.StoryList_SavedEmptyAction, - additionalAction: { [weak self] in - guard let self else { - return - } - self.additionalEmptyAction?() - } - )), - environment: {}, - containerSize: CGSize(width: size.width, height: size.height - gridTopInset - bottomInset) - ) - - let emptyStateFrame: CGRect - if self.isProfileEmbedded { - emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: max(gridTopInset, floor((visibleHeight - gridTopInset - bottomInset - emptyStateSize.height) * 0.5))), size: emptyStateSize) - } else { - emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: gridTopInset), size: emptyStateSize) - } - - if let emptyStateComponentView = emptyStateView.view { - if emptyStateComponentView.superview == nil { - self.view.addSubview(emptyStateComponentView) - if self.didUpdateItemsOnce { - emptyStateComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - emptyStateTransition.setFrame(view: emptyStateComponentView, frame: emptyStateFrame) - } - - let backgroundColor: UIColor - if self.isProfileEmbedded { - backgroundColor = presentationData.theme.list.plainBackgroundColor - } else { - backgroundColor = presentationData.theme.list.blocksBackgroundColor - } - - if self.didUpdateItemsOnce { - ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut)).setBackgroundColor(view: self.view, color: backgroundColor) - } else { - self.view.backgroundColor = backgroundColor - } - } - } else if case .botPreview = self.scope, let items = self.items, items.items.isEmpty, items.count == 0 { + if case let .peer(_, _, isArchived) = self.scope, let items = self.items, items.items.isEmpty, items.count == 0 { let emptyStateView: ComponentView var emptyStateTransition = ComponentTransition(transition) if let current = self.emptyStateView { @@ -4544,44 +3949,29 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr emptyStateView = ComponentView() self.emptyStateView = emptyStateView } - - var isMainLanguage = true - if let listSource = self.listSource as? BotPreviewStoryListContext, let _ = listSource.language { - isMainLanguage = false - } - let emptyStateSize = emptyStateView.update( transition: emptyStateTransition, component: AnyComponent(EmptyStateIndicatorComponent( context: self.context, theme: presentationData.theme, fitToHeight: self.isProfileEmbedded, - animationName: nil, - title: presentationData.strings.BotPreviews_Empty_Title, - text: presentationData.strings.BotPreviews_Empty_Text(Int32(self.maxBotPreviewCount)), - actionTitle: self.canManageStories ? presentationData.strings.BotPreviews_Empty_Add : nil, + animationName: "StoryListEmpty", + title: isArchived ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyPosts_Title, + text: isArchived ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyPosts_Text, + actionTitle: isArchived ? nil : presentationData.strings.StoryList_SavedAddAction, action: { [weak self] in guard let self else { return } - if self.canAddMoreBotPreviews() { - self.emptyAction?() - } else { - self.presentUnableToAddMorePreviewsAlert() - } + self.emptyAction?() }, - additionalActionTitle: self.canManageStories ? (isMainLanguage ? presentationData.strings.BotPreviews_Empty_AddTranslation : presentationData.strings.BotPreviews_Empty_DeleteTranslation) : nil, + additionalActionTitle: (isArchived || self.isProfileEmbedded) ? nil : presentationData.strings.StoryList_SavedEmptyAction, additionalAction: { [weak self] in guard let self else { return } - if isMainLanguage { - self.presentAddBotPreviewLanguage() - } else { - self.presentDeleteBotPreviewLanguage() - } - }, - additionalActionSeparator: self.canManageStories ? presentationData.strings.BotPreviews_Empty_Separator : nil + self.additionalEmptyAction?() + } )), environment: {}, containerSize: CGSize(width: size.width, height: size.height - gridTopInset - bottomInset) @@ -4589,7 +3979,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr let emptyStateFrame: CGRect if self.isProfileEmbedded { - emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: max(gridTopInset + 22.0, floor((visibleHeight - gridTopInset - bottomInset - emptyStateSize.height) * 0.5))), size: emptyStateSize) + emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: max(gridTopInset, floor((visibleHeight - gridTopInset - bottomInset - emptyStateSize.height) * 0.5))), size: emptyStateSize) } else { emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: gridTopInset), size: emptyStateSize) } @@ -4605,10 +3995,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } let backgroundColor: UIColor - if self.isProfileEmbedded, case .botPreview = self.scope { - backgroundColor = presentationData.theme.list.blocksBackgroundColor - } else if self.isProfileEmbedded { - backgroundColor = presentationData.theme.list.blocksBackgroundColor + if self.isProfileEmbedded { + backgroundColor = presentationData.theme.list.plainBackgroundColor } else { backgroundColor = presentationData.theme.list.blocksBackgroundColor } @@ -4618,7 +4006,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } else { self.view.backgroundColor = backgroundColor } - } else if case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded, let items = self.items, items.items.isEmpty, items.count == 0 { + } else if case .botPreview = self.scope, let items = self.items, items.items.isEmpty, items.count == 0 { let emptyStateView: ComponentView var emptyStateTransition = ComponentTransition(transition) if let current = self.emptyStateView { @@ -4716,8 +4104,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr if self.isProfileEmbedded, case .botPreview = self.scope { subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor) - } else if self.isProfileEmbedded, case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, !isArchived, self.isProfileEmbedded { - subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor) } else if self.isProfileEmbedded { subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.plainBackgroundColor) } else { @@ -4726,8 +4112,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } else { if self.isProfileEmbedded, case .botPreview = self.scope { self.view.backgroundColor = presentationData.theme.list.blocksBackgroundColor - } else if self.isProfileEmbedded, case let .peer(peerId, _, isArchived) = self.scope, peerId == self.context.account.peerId, self.isProfileEmbedded, !isArchived { - self.view.backgroundColor = presentationData.theme.list.blocksBackgroundColor } else { if case let .search(peerId, _) = self.scope, peerId != nil { @@ -4751,8 +4135,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr var adjustForSmallCount = true if case .botPreview = self.scope { adjustForSmallCount = false - } else if self.currentStoryFolder != nil { - adjustForSmallCount = false } self.itemGrid.pinchEnabled = items.count > 2 && !self.isReordering @@ -4855,21 +4237,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } public func canReorder() -> Bool { - if case .botPreview = self.scope { - guard let items = self.items else { - return false - } - return items.count > 1 - } else { - if self.currentStoryFolder == nil { - return false - } - - guard let items = self.items else { - return false - } - return items.count > 1 + guard let items = self.items else { + return false } + return items.count > 1 } private func presentAddBotPreviewLanguage() { @@ -4882,90 +4253,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr })) } - private func presentAddStoryFolder(addItems: [EngineStoryItem] = []) { - //TODO:localize - let promptController = promptController( - sharedContext: self.context.sharedContext, - updatedPresentationData: nil, - text: "Create a New Album", - titleFont: .bold, - subtitle: "Choose a name for your album and start adding your stories there.", - value: "", - placeholder: "Title", - characterLimit: 20, - displayCharacterLimit: true, - apply: { [weak self] value in - guard let self else { - return - } - if let value { - if let listSource = self.listSource as? PeerStoryListContext { - listSource.addFolder(title: value, completion: { [weak self] id in - Queue.mainQueue().async { - guard let self, let listSource = self.listSource as? PeerStoryListContext else { - return - } - if !addItems.isEmpty { - listSource.addToFolder(id: id, items: addItems) - } - self.setStoryFolder(id: id, assumeEmpty: addItems.isEmpty) - } - }) - } - } - } - ) - self.parentController?.present(promptController, in: .window(.root)) - } - - private func presentAddStoriesToFolder() { - guard case let .peer(peerId, _, _) = self.scope else { - return - } - guard let folder = self.currentStoryFolder else { - return - } - - let controller = self.context.sharedContext.makeStorySelectionController(context: self.context, peerId: peerId, completion: { [weak self] items in - guard let self else { - return - } - if let listSource = self.listSource as? PeerStoryListContext { - listSource.addToFolder(id: folder.id, items: items) - } - }) - controller.navigationPresentation = .modal - - self.parentController?.push(controller) - } - - public func presentDeleteCurrentStoryFolder() { - if let folder = self.currentStoryFolder { - self.presentDeleteStoryFolder(id: folder.id) - } - } - - private func presentDeleteStoryFolder(id: Int64) { - guard let folder = self.currentStoryFolders.first(where: { $0.id == id }) else { - return - } - let _ = folder - - if self.currentStoryFolder?.id == id { - self.setStoryFolder(id: nil, assumeEmpty: false) - } - self.currentStoryFolders.removeAll(where: { $0.id == id }) - self.removedStoryFolders.insert(id) - - if let listContext = self.listSource as? PeerStoryListContext { - listContext.removeFolder(id: id) - } - - if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams { - self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: false, transition: .immediate) - } - } - public func presentUnableToAddMorePreviewsAlert() { self.parentController?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.BotPreviews_AlertTooManyPreviews(Int32(self.maxBotPreviewCount)), actions: [ TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { @@ -5051,32 +4338,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } if let id { - if let cachedListSource = self.cachedListSources[AnyHashable(id)] { + if let cachedListSource = self.cachedListSources[id] { self.listSource = cachedListSource } else { let listSource = BotPreviewStoryListContext(account: self.context.account, engine: self.context.engine, peerId: peerId, language: id, assumeEmpty: assumeEmpty) self.listSource = listSource - self.cachedListSources[AnyHashable(id)] = listSource - } - } else { - self.listSource = self.defaultListSource - } - - self.requestHistoryAroundVisiblePosition(synchronous: false, reloadAtTop: true) - } - - private func setStoryFolder(id: Int64?, assumeEmpty: Bool) { - if let listSource = self.listSource as? PeerStoryListContext, listSource.folderId == id { - return - } - - if let id { - if let cachedListSource = self.cachedListSources[AnyHashable(id)] { - self.listSource = cachedListSource - } else { - let listSource = PeerStoryListContext(account: self.context.account, peerId: self.context.account.peerId, isArchived: false, folderId: id) - self.listSource = listSource - //self.cachedListSources[AnyHashable(id)] = listSource + self.cachedListSources[id] = listSource } } else { self.listSource = self.defaultListSource @@ -5109,12 +4376,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr self.itemGrid.setReordering(isReordering: isReordering) - if !isReordering && !self.currentStoryFolders.isEmpty && self.canManageStories { - if let listSource = self.listSource as? PeerStoryListContext { - listSource.reorderFolders(ids: self.currentStoryFolders.map(\.id)) - } - } - if !isReordering, let reorderedIds = self.reorderedIds { self.reorderedIds = nil if case .botPreview = self.scope, let listSource = self.listSource as? BotPreviewStoryListContext { @@ -5130,26 +4391,20 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr listSource.reorderItems(media: reorderedMedia) } } else if case let .peer(id, _, _) = self.scope, id == self.context.account.peerId, let items = self.items { - if let _ = self.currentStoryFolder { - if let listSource = self.listSource as? PeerStoryListContext { - listSource.reorderItemsInFolder(itemIds: reorderedIds.map { $0.id }) - } - } else { - var updatedPinnedIds: [Int32] = [] - for id in reorderedIds { - inner: for item in items.items { - if let item = item as? VisualMediaItem { - if item.storyId == id { - if item.isPinned { - updatedPinnedIds.append(id.id) - break inner - } + var updatedPinnedIds: [Int32] = [] + for id in reorderedIds { + inner: for item in items.items { + if let item = item as? VisualMediaItem { + if item.storyId == id { + if item.isPinned { + updatedPinnedIds.append(id.id) + break inner } } } } - let _ = self.context.engine.messages.updatePinnedToTopStories(peerId: id, ids: updatedPinnedIds).startStandalone() } + let _ = self.context.engine.messages.updatePinnedToTopStories(peerId: id, ids: updatedPinnedIds).startStandalone() } } @@ -5507,34 +4762,3 @@ private final class BottomActionsPanelComponent: Component { } } -private final class ItemExtractedContentSource: ContextExtractedContentSource { - let keepInPlace: Bool - let ignoreContentTouches: Bool = true - let blurBackground: Bool = true - let adjustContentForSideInset: Bool = true - - private let sourceNode: ContextExtractedContentContainingNode - private weak var containerView: UIView? - - init(sourceNode: ContextExtractedContentContainingNode, containerView: UIView, keepInPlace: Bool) { - self.sourceNode = sourceNode - self.containerView = containerView - self.keepInPlace = keepInPlace - } - - func takeView() -> ContextControllerTakeViewInfo? { - var contentArea: CGRect? - if let containerView = self.containerView { - contentArea = containerView.convert(containerView.bounds, to: nil) - } - - return ContextControllerTakeViewInfo( - containingItem: .node(self.sourceNode), - contentAreaInScreenSpace: contentArea ?? UIScreen.main.bounds - ) - } - - func putBack() -> ContextControllerPutBackViewInfo? { - return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) - } -} From c825438b72d40f9ea8d4282bcd7e361c647ece3e Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 20 Jul 2025 19:49:14 +0200 Subject: [PATCH 2/9] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 4 ++-- .../Sources/BotCheckoutController.swift | 3 +++ .../Payments/BotPaymentForm.swift | 4 ++++ .../Sources/AgeVerificationScreen.swift | 7 ++++++- .../Sources/FaceScanScreen.swift | 20 ++++++++++--------- .../Sources/GiftSetupScreen.swift | 14 +++++++++++++ .../Sources/GiftViewScreen.swift | 5 ++++- .../Sources/AddGiftsScreen.swift | 8 ++++++-- .../Sources/GiftsListView.swift | 10 +++++++--- .../Sources/PeerInfoGiftsPaneNode.swift | 10 +++++++++- .../Sources/TelegramRootController.swift | 17 ---------------- 11 files changed, 66 insertions(+), 36 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 7051fae35d..c369488679 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14736,8 +14736,8 @@ Sorry for the inconvenience."; "Gift.Options.Gift.BuyLimitReached_any" = "You've already sent %@ of these gifts, and it's the limit."; "AgeVerification.Title" = "Age Verification"; -"AgeVerification.Text" = "To access this content, you must confirm you are at least **18** years old as required by UK law.\n\nThis is a one-time process using your phone's camera. Your selfie will not be stored by Telegram."; -"AgeVerification.Text.gb" = "To access this content, you must confirm you are at least **18** years old as required by UK law.\n\nThis is a one-time process using your phone's camera. Your selfie will not be stored by Telegram."; +"AgeVerification.Text" = "To access this content, you must confirm you are at least **18** years old.\n\nThis is a one-time process using your phone's camera. Your selfie will not be stored by Telegram."; +"AgeVerification.Text.GB" = "To access this content, you must confirm you are at least **18** years old as required by UK law.\n\nThis is a one-time process using your phone's camera. Your selfie will not be stored by Telegram."; "AgeVerification.Verify" = "Verify My Age"; "AgeVerification.Success.Title" = "Age check passed!"; diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift index 95baaa332f..035fa49f99 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift @@ -12,6 +12,7 @@ public final class BotCheckoutController: ViewController { public enum FetchError { case generic case disallowedStarGifts + case starGiftsUserLimit } public let form: BotPaymentForm @@ -58,6 +59,8 @@ public final class BotCheckoutController: ViewController { switch error { case .disallowedStarGift: return .disallowedStarGifts + case .starGiftUserLimit: + return .starGiftsUserLimit default: return .generic } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift index 9330c8db39..173c4b50c5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift @@ -180,6 +180,7 @@ public enum BotPaymentFormRequestError { case noPaymentNeeded case disallowedStarGift case starGiftResellTooEarly(Int32) + case starGiftUserLimit } extension BotPaymentInvoice { @@ -488,6 +489,8 @@ func _internal_fetchBotPaymentForm(accountPeerId: PeerId, postbox: Postbox, netw if let value = Int32(timeout) { return .fail(.starGiftResellTooEarly(value)) } + } else if error.errorDescription == "STARGIFT_USER_USAGE_LIMITED" { + return .fail(.starGiftUserLimit) } return .fail(.generic) } @@ -651,6 +654,7 @@ public enum SendBotPaymentFormError { case alreadyPaid case starGiftOutOfStock case disallowedStarGift + case starGiftUserLimit } public enum SendBotPaymentResult { diff --git a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift index efb8d18c65..bcf91273b2 100644 --- a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift +++ b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift @@ -407,7 +407,12 @@ public func presentAgeVerification(context: AccountContext, parentController: Vi } else { let infoScreen = AgeVerificationScreen(context: context, completion: { [weak parentController] check, availability in if check { - let scanScreen = FaceScanScreen(context: context, availability: availability, completion: { [weak parentController] passed in + var requiredAge = 18 + if let value = context.currentAppConfiguration.with({ $0 }).data?["verify_age_min"] as? Double { + requiredAge = Int(value) + } + + let scanScreen = FaceScanScreen(context: context, availability: availability, requiredAge: requiredAge, completion: { [weak parentController] passed in if passed { let _ = updateAgeVerificationState(engine: context.engine, { _ in return AgeVerificationState(verificationPassed: passed) diff --git a/submodules/TelegramUI/Components/FaceScanScreen/Sources/FaceScanScreen.swift b/submodules/TelegramUI/Components/FaceScanScreen/Sources/FaceScanScreen.swift index 334469152d..2be0e9db59 100644 --- a/submodules/TelegramUI/Components/FaceScanScreen/Sources/FaceScanScreen.swift +++ b/submodules/TelegramUI/Components/FaceScanScreen/Sources/FaceScanScreen.swift @@ -20,20 +20,21 @@ import ZipArchive import PlainButtonComponent import MultilineTextComponent -private let requiredAge = 18 - final class FaceScanScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext let availability: Signal + let requiredAge: Int init( context: AccountContext, - availability: Signal + availability: Signal, + requiredAge: Int ) { self.context = context self.availability = availability + self.requiredAge = requiredAge } static func ==(lhs: FaceScanScreenComponent, rhs: FaceScanScreenComponent) -> Bool { @@ -109,7 +110,7 @@ final class FaceScanScreenComponent: Component { self.backgroundColor = .black - self.previewLayer.backgroundColor = UIColor.red.cgColor + //self.previewLayer.backgroundColor = UIColor.red.cgColor self.previewLayer.videoGravity = .resizeAspectFill self.layer.addSublayer(previewLayer) @@ -240,7 +241,7 @@ final class FaceScanScreenComponent: Component { let targetCenter = CGPoint(x: 0.5, y: 0.5) let distance = sqrt(pow(faceCenter.x - targetCenter.x, 2) + pow(faceCenter.y - targetCenter.y, 2)) - if distance < 0.35 { + if distance < 0.24 { switch processState { case .waitingForFace: self.processState = .positioning @@ -306,7 +307,7 @@ final class FaceScanScreenComponent: Component { } private func fillSegment(_ segmentIndex: Int) { - guard !self.completedAngles.contains(segmentIndex) else { + guard let component = self.component, !self.completedAngles.contains(segmentIndex) else { return } self.completedAngles.insert(segmentIndex) @@ -320,7 +321,7 @@ final class FaceScanScreenComponent: Component { if !self.ages.isEmpty { let averageAge = self.ages.reduce(0, +) / Double(self.ages.count) if let environment = self.environment, let controller = environment.controller() as? FaceScanScreen { - controller.completion(averageAge >= Double(requiredAge)) + controller.completion(averageAge >= Double(component.requiredAge)) controller.dismiss(animated: true) } } else { @@ -457,7 +458,6 @@ final class FaceScanScreenComponent: Component { self.frameView.frame = frameViewFrame self.frameView.update(size: frameViewFrame.size) - //TODO:localize var instructionString = environment.strings.FaceScan_Instruction_Position switch self.processState { case .waitingForFace, .positioning: @@ -550,6 +550,7 @@ public final class FaceScanScreen: ViewControllerComponentContainer { public init( context: AccountContext, availability: Signal, + requiredAge: Int, completion: @escaping (Bool) -> Void ) { self.context = context @@ -557,7 +558,8 @@ public final class FaceScanScreen: ViewControllerComponentContainer { super.init(context: context, component: FaceScanScreenComponent( context: context, - availability: availability + availability: availability, + requiredAge: requiredAge ), navigationBarAppearance: .none, theme: .default, updatedPresentationData: nil) self.title = "" diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index eec3b4716c..fd33e2dc21 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -363,6 +363,8 @@ final class GiftSetupScreenComponent: Component { let entities = generateChatInputTextEntities(self.textInputState.text) var finalPrice: Int64 + var perUserLimit: Int32? + var giftFile: TelegramMediaFile? let source: BotPaymentInvoiceSource switch component.subject { case let .premium(product): @@ -377,6 +379,8 @@ final class GiftSetupScreenComponent: Component { if self.includeUpgrade, let upgradeStars = starGift.upgradeStars { finalPrice += upgradeStars } + perUserLimit = starGift.perUserLimit?.total + giftFile = starGift.file source = .starGift(hideName: self.hideName, includeUpgrade: self.includeUpgrade, peerId: peerId, giftId: starGift.id, text: self.textInputState.text.string, entities: entities) } @@ -395,6 +399,8 @@ final class GiftSetupScreenComponent: Component { switch error { case .disallowedStarGifts: return .fail(.disallowedStarGift) + case .starGiftsUserLimit: + return .fail(.starGiftUserLimit) default: return .fail(.generic) } @@ -468,6 +474,14 @@ final class GiftSetupScreenComponent: Component { var errorText: String? switch error { + case .starGiftUserLimit: + if let perUserLimit, let giftFile { + let text = presentationData.strings.Gift_Options_Gift_BuyLimitReached(perUserLimit) + let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: component.context, file: giftFile, loop: true, title: nil, text: text, undoText: nil, customAction: nil), action: { _ in return false }) + controller.present(undoController, in: .current) + return + } + return case .starGiftOutOfStock: errorText = presentationData.strings.Gift_Send_ErrorOutOfStock case .disallowedStarGift: diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 3f2bb8baec..9ff2dc3ff3 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -288,11 +288,14 @@ private final class GiftViewSheetContent: CombinedComponent { } var minRequiredAmount = StarsAmount(value: 100, nanos: 0) + var canUpgrade = false if let resellStars = self.subject.arguments?.resellStars { minRequiredAmount = StarsAmount(value: resellStars, nanos: 0) + } else if let arguments = self.subject.arguments, arguments.canUpgrade && arguments.upgradeStars == nil { + canUpgrade = true } - if let starsContext = context.starsContext, let state = starsContext.currentState, state.balance < minRequiredAmount { + if let starsContext = context.starsContext, let state = starsContext.currentState, state.balance < minRequiredAmount || canUpgrade { self.optionsDisposable = (context.engine.payments.starsTopUpOptions() |> deliverOnMainQueue).start(next: { [weak self] options in guard let self else { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift index b1ec546109..23cdd4f560 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift @@ -23,17 +23,20 @@ final class AddGiftsScreenComponent: Component { let context: AccountContext let peerId: EnginePeer.Id let collectionId: Int32 + let remainingCount: Int32 let profileGifts: ProfileGiftsContext init( context: AccountContext, peerId: EnginePeer.Id, collectionId: Int32, + remainingCount: Int32, profileGifts: ProfileGiftsContext ) { self.context = context self.peerId = peerId self.collectionId = collectionId + self.remainingCount = remainingCount self.profileGifts = profileGifts } @@ -128,7 +131,7 @@ final class AddGiftsScreenComponent: Component { if let current = self.giftsListView { giftsListView = current } else { - giftsListView = GiftsListView(context: component.context, peerId: component.peerId, profileGifts: component.profileGifts, giftsCollections: nil, canSelect: true, ignoreCollection: component.collectionId) + giftsListView = GiftsListView(context: component.context, peerId: component.peerId, profileGifts: component.profileGifts, giftsCollections: nil, canSelect: true, ignoreCollection: component.collectionId, remainingSelectionCount: component.remainingCount) giftsListView.selectionUpdated = { [weak self] in guard let self else { return @@ -248,6 +251,7 @@ public final class AddGiftsScreen: ViewControllerComponentContainer { context: AccountContext, peerId: EnginePeer.Id, collectionId: Int32, + remainingCount: Int32, completion: @escaping ([ProfileGiftsContext.State.StarGift]) -> Void ) { self.context = context @@ -264,10 +268,10 @@ public final class AddGiftsScreen: ViewControllerComponentContainer { context: context, peerId: peerId, collectionId: collectionId, + remainingCount: remainingCount, profileGifts: self.profileGifts ), navigationBarAppearance: .default, theme: .default, updatedPresentationData: nil) - self.title = presentationData.strings.AddGifts_Title self.navigationPresentation = .modal diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift index ef195fb43d..b42e5eae26 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift @@ -36,6 +36,7 @@ final class GiftsListView: UIView { private let canSelect: Bool private let ignoreCollection: Int32? + private let remainingSelectionCount: Int32 private var dataDisposable: Disposable? @@ -124,13 +125,14 @@ final class GiftsListView: UIView { var contextAction: ((ProfileGiftsContext.State.StarGift, UIView, ContextGesture) -> Void)? var addToCollection: (() -> Void)? - init(context: AccountContext, peerId: PeerId, profileGifts: ProfileGiftsContext, giftsCollections: ProfileGiftsCollectionsContext?, canSelect: Bool, ignoreCollection: Int32? = nil) { + init(context: AccountContext, peerId: PeerId, profileGifts: ProfileGiftsContext, giftsCollections: ProfileGiftsCollectionsContext?, canSelect: Bool, ignoreCollection: Int32? = nil, remainingSelectionCount: Int32 = 0) { self.context = context self.peerId = peerId self.profileGifts = profileGifts self.giftsCollections = giftsCollections self.canSelect = canSelect self.ignoreCollection = ignoreCollection + self.remainingSelectionCount = remainingSelectionCount if let value = context.currentAppConfiguration.with({ $0 }).data?["stargifts_pinned_to_top_limit"] as? Double { self.maxPinnedCount = Int(value) @@ -548,8 +550,10 @@ final class GiftsListView: UIView { if self.selectedItemIds.contains(itemReferenceId) { self.selectedItemIds.remove(itemReferenceId) } else { - self.selectedItemIds.insert(itemReferenceId) - self.selectedItemsMap[itemReferenceId] = product + if self.selectedItemIds.count < self.remainingSelectionCount { + self.selectedItemIds.insert(itemReferenceId) + self.selectedItemsMap[itemReferenceId] = product + } } self.selectionUpdated() self.updateScrolling(transition: .easeInOut(duration: 0.25)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index ab1595fa59..a736019aad 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -242,7 +242,15 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } public func addGiftsToCollection(id: Int32) { - let screen = AddGiftsScreen(context: self.context, peerId: self.peerId, collectionId: id, completion: { [weak self] gifts in + var collectionGiftsMaxCount: Int32 = 1000 + if let value = self.context.currentAppConfiguration.with({ $0 }).data?["stargifts_collection_gifts_limit"] as? Double { + collectionGiftsMaxCount = Int32(value) + } + var remainingCount = collectionGiftsMaxCount + if let currentCount = self.giftsListView.profileGifts.currentState?.count { + remainingCount = max(0, collectionGiftsMaxCount - currentCount) + } + let screen = AddGiftsScreen(context: self.context, peerId: self.peerId, collectionId: id, remainingCount: remainingCount, completion: { [weak self] gifts in guard let self else { return } diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 683e3bb911..35348db5b1 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -31,7 +31,6 @@ import PeerInfoScreen import PeerInfoStoryGridScreen import ShareWithPeersScreen import ChatEmptyNode -//import FaceScanScreen import UndoUI private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceholderNode { @@ -237,22 +236,6 @@ public final class TelegramRootController: NavigationController, TelegramRootCon self.accountSettingsController = accountSettingsController self.rootTabController = tabBarController self.pushViewController(tabBarController, animated: false) - -// Queue.mainQueue().after(1.0) { -// let context = self.context -// let infoScreen = AgeVerificationScreen(context: context, completion: { [weak chatListController] proceed in -// if proceed { -// let scanScreen = FaceScanScreen(context: context, completion: { success in -// let controller = UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: "Age check passed!", text: "You can now view this content.", cancel: nil, destructive: false), elevatedLayout: true, action: { _ in return true }) -// Queue.mainQueue().after(0.1) { -// chatListController?.present(controller, in: .window(.root)) -// } -// }) -// chatListController?.push(scanScreen) -// } -// }) -// chatListController.push(infoScreen) -// } } public func updateRootControllers(showCallsTab: Bool) { From 9d703f5b60460b62256f7c50d0aad9e7b2554ece Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 21 Jul 2025 00:38:47 +0200 Subject: [PATCH 3/9] Various fixes --- .../Sources/AccountContext.swift | 2 +- .../Sources/ChatListController.swift | 3 +- .../Sources/ChatListSearchListPaneNode.swift | 9 ++- .../Sources/AgeVerificationScreen.swift | 80 +++++++++++++++---- .../Sources/FaceScanScreen.swift | 19 ++--- .../Sources/AffiliateProgramSetupScreen.swift | 3 +- .../Sources/PeerInfoScreen.swift | 3 +- .../Sources/PeerInfoGiftsPaneNode.swift | 4 +- .../Sources/ApplicationContext.swift | 16 +++- .../Chat/ChatControllerOpenWebApp.swift | 37 ++++++++- .../TelegramUI/Sources/ChatController.swift | 16 +++- .../Sources/SharedAccountContext.swift | 4 +- .../WebUI/Sources/WebAppController.swift | 45 ++++++++--- versions.json | 2 +- 14 files changed, 188 insertions(+), 55 deletions(-) diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 7b0b0d692f..bdbab5249c 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1296,7 +1296,7 @@ public protocol SharedAccountContext: AnyObject { func makeIncomingMessagePrivacyScreen(context: AccountContext, value: GlobalPrivacySettings.NonContactChatsPrivacy, exceptions: SelectivePrivacySettings, update: @escaping (GlobalPrivacySettings.NonContactChatsPrivacy) -> Void) -> ViewController - func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?) + func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?, verifyAgeCompletion: ((Int) -> Void)?) func makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, mode: AffiliateProgramSetupScreenMode) -> Signal func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 858ccf4ab0..5b8e949137 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1250,7 +1250,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController simple: true, source: .generic, skipTermsOfService: true, - payload: nil + payload: nil, + verifyAgeCompletion: nil ) } diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 6e4a1e43e0..cb4f254746 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -3282,7 +3282,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { simple: true, source: .generic, skipTermsOfService: true, - payload: nil + payload: nil, + verifyAgeCompletion: nil ) interaction.dismissSearch() } @@ -4268,7 +4269,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { simple: true, source: .generic, skipTermsOfService: true, - payload: nil + payload: nil, + verifyAgeCompletion: nil ) } } @@ -4287,7 +4289,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { simple: true, source: .generic, skipTermsOfService: true, - payload: nil + payload: nil, + verifyAgeCompletion: nil ) interaction.dismissSearch() } else { diff --git a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift index bcf91273b2..03dbf571ac 100644 --- a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift +++ b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift @@ -405,6 +405,20 @@ public func presentAgeVerification(context: AccountContext, parentController: Vi if state.verificationPassed { completion() } else { + let miniappPromise = Promise(nil) + var useVerifyAgeBot = false + if let value = context.currentAppConfiguration.with({ $0 }).data?["force_verify_age_bot"] as? Bool, value { + useVerifyAgeBot = value + } + if useVerifyAgeBot, let verifyAgeBotUsername = context.currentAppConfiguration.with({ $0 }).data?["verify_age_bot_username"] as? String { + miniappPromise.set(context.engine.peers.resolvePeerByName(name: verifyAgeBotUsername, referrer: nil) + |> mapToSignal { result in + if case let .result(peer) = result { + return .single(peer) + } + return .complete() + }) + } let infoScreen = AgeVerificationScreen(context: context, completion: { [weak parentController] check, availability in if check { var requiredAge = 18 @@ -412,24 +426,60 @@ public func presentAgeVerification(context: AccountContext, parentController: Vi requiredAge = Int(value) } - let scanScreen = FaceScanScreen(context: context, availability: availability, requiredAge: requiredAge, completion: { [weak parentController] passed in - if passed { - let _ = updateAgeVerificationState(engine: context.engine, { _ in - return AgeVerificationState(verificationPassed: passed) - }).start() - completion() - - let navigationController = parentController?.navigationController - Queue.mainQueue().after(2.0) { - let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AgeVerification_Success_Title, text: presentationData.strings.AgeVerification_Success_Text, cancel: nil, destructive: false), action: { _ in return true }) - (navigationController?.viewControllers.last as? ViewController)?.present(controller, in: .window(.root)) - } + let success = { [weak parentController] in + let _ = updateAgeVerificationState(engine: context.engine, { _ in + return AgeVerificationState(verificationPassed: true) + }).start() + completion() + + let navigationController = parentController?.navigationController + Queue.mainQueue().after(2.0) { + let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AgeVerification_Success_Title, text: presentationData.strings.AgeVerification_Success_Text, cancel: nil, destructive: false), action: { _ in return true }) + (navigationController?.viewControllers.last as? ViewController)?.present(controller, in: .current) + } + } + + let failure = { [weak parentController] in + let controller = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_banned", scale: 0.066, colors: [:], title: presentationData.strings.AgeVerification_Fail_Title, text: presentationData.strings.AgeVerification_Fail_Text, customUndoText: nil, timeout: nil), action: { _ in return true }) + parentController?.present(controller, in: .current) + } + + let _ = (miniappPromise.get() + |> take(1) + |> deliverOnMainQueue).start(next: { peer in + if let peer, let 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, + verifyAgeCompletion: { age in + if age >= requiredAge { + success() + } else { + failure() + } + } + ) } else { - let controller = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_banned", scale: 0.066, colors: [:], title: presentationData.strings.AgeVerification_Fail_Title, text: presentationData.strings.AgeVerification_Fail_Text, customUndoText: nil, timeout: nil), action: { _ in return true }) - parentController?.present(controller, in: .window(.root)) + let scanScreen = FaceScanScreen(context: context, availability: availability, completion: { age in + if age >= requiredAge { + success() + } else { + failure() + } + }) + parentController?.push(scanScreen) } }) - parentController?.push(scanScreen) } }) parentController?.push(infoScreen) diff --git a/submodules/TelegramUI/Components/FaceScanScreen/Sources/FaceScanScreen.swift b/submodules/TelegramUI/Components/FaceScanScreen/Sources/FaceScanScreen.swift index 2be0e9db59..66d5ad7bc3 100644 --- a/submodules/TelegramUI/Components/FaceScanScreen/Sources/FaceScanScreen.swift +++ b/submodules/TelegramUI/Components/FaceScanScreen/Sources/FaceScanScreen.swift @@ -25,16 +25,13 @@ final class FaceScanScreenComponent: Component { let context: AccountContext let availability: Signal - let requiredAge: Int init( context: AccountContext, - availability: Signal, - requiredAge: Int + availability: Signal ) { self.context = context self.availability = availability - self.requiredAge = requiredAge } static func ==(lhs: FaceScanScreenComponent, rhs: FaceScanScreenComponent) -> Bool { @@ -307,7 +304,7 @@ final class FaceScanScreenComponent: Component { } private func fillSegment(_ segmentIndex: Int) { - guard let component = self.component, !self.completedAngles.contains(segmentIndex) else { + guard !self.completedAngles.contains(segmentIndex) else { return } self.completedAngles.insert(segmentIndex) @@ -321,7 +318,7 @@ final class FaceScanScreenComponent: Component { if !self.ages.isEmpty { let averageAge = self.ages.reduce(0, +) / Double(self.ages.count) if let environment = self.environment, let controller = environment.controller() as? FaceScanScreen { - controller.completion(averageAge >= Double(component.requiredAge)) + controller.completion(Int(averageAge)) controller.dismiss(animated: true) } } else { @@ -437,7 +434,7 @@ final class FaceScanScreenComponent: Component { let center = CGPoint(x: availableSize.width / 2, y: environment.statusBarHeight + 10.0 + widthRadius * 1.3) - var previewScale = 1.0 + var previewScale = 0.85 if self.processState == .tracking || self.processState == .readyToStart || self.processState == .completed || self.transitioningToViewFinder { let circlePath = CGPath(roundedRect: CGRect(x: center.x - radius, y: center.y - radius, width: radius * 2, height: radius * 2), cornerWidth: radius, cornerHeight: radius, transform: nil) path.addPath(circlePath) @@ -545,21 +542,19 @@ final class FaceScanScreenComponent: Component { public final class FaceScanScreen: ViewControllerComponentContainer { private let context: AccountContext - fileprivate let completion: (Bool) -> Void + fileprivate let completion: (Int) -> Void public init( context: AccountContext, availability: Signal, - requiredAge: Int, - completion: @escaping (Bool) -> Void + completion: @escaping (Int) -> Void ) { self.context = context self.completion = completion super.init(context: context, component: FaceScanScreenComponent( context: context, - availability: availability, - requiredAge: requiredAge + availability: availability ), navigationBarAppearance: .none, theme: .default, updatedPresentationData: nil) self.title = "" diff --git a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift index f784de7564..6acf5283b7 100644 --- a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift @@ -1257,7 +1257,8 @@ final class AffiliateProgramSetupScreenComponent: Component { simple: true, source: .generic, skipTermsOfService: true, - payload: nil + payload: nil, + verifyAgeCompletion: nil ) } else if let navigationController = controller.navigationController as? NavigationController { component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(item.peer), subject: nil, keepStack: .always, animated: true, pushController: { [weak navigationController] chatController, animated, completion in diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 8987ea9da8..557f3ff470 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -1451,7 +1451,8 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese simple: true, source: .generic, skipTermsOfService: true, - payload: nil + payload: nil, + verifyAgeCompletion: nil ) }) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index a736019aad..3011f23546 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -201,7 +201,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr return } - let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Title, titleFont: .bold, subtitle: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Text, value: "", placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 20, displayCharacterLimit: true, apply: { [weak self] value in + let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Title, titleFont: .bold, subtitle: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Text, value: "", placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 12, displayCharacterLimit: true, apply: { [weak self] value in guard let self, let value else { return } @@ -264,7 +264,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr return } - let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_RenameCollection_Title, titleFont: .bold, value: collection.title, placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 20, displayCharacterLimit: true, apply: { [weak self] value in + let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_RenameCollection_Title, titleFont: .bold, value: collection.title, placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 12, displayCharacterLimit: true, apply: { [weak self] value in guard let self, let value else { return } diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index 51b4902c1e..52bfd8774d 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -900,7 +900,21 @@ final class AuthorizedApplicationContext { } if openAppIfAny, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp), let parentController = self.rootController.viewControllers.last as? ViewController { - self.context.sharedContext.openWebApp(context: self.context, parentController: parentController, updatedPresentationData: nil, botPeer: peer, chatPeer: nil, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: true, payload: nil) + self.context.sharedContext.openWebApp( + context: self.context, + parentController: parentController, + updatedPresentationData: nil, + botPeer: peer, + chatPeer: nil, + threadId: nil, + buttonText: "", + url: "", + simple: true, + source: .generic, + skipTermsOfService: true, + payload: nil, + verifyAgeCompletion: nil + ) } else { self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: chatLocation, subject: alwaysKeepMessageId || isOutgoingMessage ? messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false) } : nil, activateInput: activateInput ? .text : nil)) } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift index 312ce0b606..083f47e8d5 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift @@ -27,7 +27,8 @@ func openWebAppImpl( simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, - payload: String? + payload: String?, + verifyAgeCompletion: ((Int) -> Void)? ) { if context.isFrozen { parentController.push(context.sharedContext.makeAccountFreezeInfoScreen(context: context)) @@ -246,7 +247,7 @@ func openWebAppImpl( navigationController = parentController.effectiveNavigationController } return navigationController ?? (context.sharedContext.mainWindow?.viewController as? NavigationController) - }) + }, verifyAgeCompletion: verifyAgeCompletion) controller.navigationPresentation = .flatModal parentController.push(controller) @@ -351,7 +352,21 @@ public extension ChatControllerImpl { } self.chatDisplayNode.dismissInput() - self.context.sharedContext.openWebApp(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, botPeer: EnginePeer(peer), chatPeer: EnginePeer(peer), threadId: self.chatLocation.threadId, buttonText: buttonText, url: url, simple: simple, source: source, skipTermsOfService: false, payload: nil) + self.context.sharedContext.openWebApp( + context: self.context, + parentController: self, + updatedPresentationData: self.updatedPresentationData, + botPeer: EnginePeer(peer), + chatPeer: EnginePeer(peer), + threadId: self.chatLocation.threadId, + buttonText: buttonText, + url: url, + simple: simple, + source: source, + skipTermsOfService: false, + payload: nil, + verifyAgeCompletion: nil + ) } fileprivate static func botRequestSwitchInline(context: AccountContext, controller: ChatControllerImpl?, peerId: EnginePeer.Id, botAddress: String, query: String, chatTypes: [ReplyMarkupButtonRequestPeerType]?, completion: @escaping () -> Void) -> Void { @@ -616,7 +631,21 @@ public extension ChatControllerImpl { } }) } else { - context.sharedContext.openWebApp(context: context, parentController: parentController, updatedPresentationData: updatedPresentationData, botPeer: botPeer, chatPeer: nil, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false, payload: payload) + context.sharedContext.openWebApp( + context: context, + parentController: parentController, + updatedPresentationData: updatedPresentationData, + botPeer: botPeer, + chatPeer: nil, + threadId: nil, + buttonText: "", + url: "", + simple: true, + source: .generic, + skipTermsOfService: false, + payload: payload, + verifyAgeCompletion: nil + ) } } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 82a1c85707..d1d891b2c3 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -8907,7 +8907,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G commit() }) } else { - self.context.sharedContext.openWebApp(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, botPeer: peer, chatPeer: nil, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false, payload: botAppStart.payload) + self.context.sharedContext.openWebApp( + context: self.context, + parentController: self, + updatedPresentationData: self.updatedPresentationData, + botPeer: peer, + chatPeer: nil, + threadId: nil, + buttonText: "", + url: "", + simple: true, + source: .generic, + skipTermsOfService: false, + payload: botAppStart.payload, + verifyAgeCompletion: nil + ) commit() } }) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 48ad0ae251..dcb2d5e440 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3882,8 +3882,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { return incomingMessagePrivacyScreen(context: context, value: value, exceptions: exceptions, update: update) } - public func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?) { - openWebAppImpl(context: context, parentController: parentController, updatedPresentationData: updatedPresentationData, botPeer: botPeer, chatPeer: chatPeer, threadId: threadId, buttonText: buttonText, url: url, simple: simple, source: source, skipTermsOfService: skipTermsOfService, payload: payload) + public func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?, verifyAgeCompletion: ((Int) -> Void)?) { + openWebAppImpl(context: context, parentController: parentController, updatedPresentationData: updatedPresentationData, botPeer: botPeer, chatPeer: chatPeer, threadId: threadId, buttonText: buttonText, url: url, simple: simple, source: source, skipTermsOfService: skipTermsOfService, payload: payload, verifyAgeCompletion: verifyAgeCompletion) } public func makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, mode: AffiliateProgramSetupScreenMode) -> Signal { diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 3445a10613..2d92f8bbe6 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -653,10 +653,14 @@ public final class WebAppController: ViewController, AttachmentContainable { self.animateTransitionIn() }) } - + @available(iOSApplicationExtension 15.0, iOS 15.0, *) func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) { - decisionHandler(.prompt) + if self.controller?.isVerifyAgeBot == true && type == .camera { + decisionHandler(.grant) + } else { + decisionHandler(.prompt) + } } func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { @@ -1756,6 +1760,12 @@ public final class WebAppController: ViewController, AttachmentContainable { } case "web_app_hide_keyboard": self.view.window?.endEditing(true) + case "web_app_verify_age": + if let json, self.controller?.isVerifyAgeBot == true { + if let ageValue = json["age"] as? Double { + self.controller?.verifyAgeCompletion?(Int(ageValue)) + } + } default: break } @@ -3281,6 +3291,8 @@ public final class WebAppController: ViewController, AttachmentContainable { public var completion: () -> Void = {} public var requestSwitchInline: (String, [ReplyMarkupButtonRequestPeerType]?, @escaping () -> Void) -> Void = { _, _, _ in } + public var verifyAgeCompletion: ((Int) -> Void)? + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, params: WebAppParameters, replyToMessageId: MessageId?, threadId: Int64?) { self.context = context self.source = params.source @@ -3323,16 +3335,20 @@ public final class WebAppController: ViewController, AttachmentContainable { self.navigationItem.leftBarButtonItem?.action = #selector(self.cancelPressed) self.navigationItem.leftBarButtonItem?.target = self - self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) - self.navigationItem.rightBarButtonItem?.action = #selector(self.moreButtonPressed) - self.navigationItem.rightBarButtonItem?.target = self + if !self.isVerifyAgeBot { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) + self.navigationItem.rightBarButtonItem?.action = #selector(self.moreButtonPressed) + self.navigationItem.rightBarButtonItem?.target = self + } self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - let titleView = WebAppTitleView(context: self.context, theme: self.presentationData.theme) - titleView.title = WebAppTitle(title: params.botName, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: params.botVerified) - self.navigationItem.titleView = titleView - self.titleView = titleView + if !self.isVerifyAgeBot { + let titleView = WebAppTitleView(context: self.context, theme: self.presentationData.theme) + titleView.title = WebAppTitle(title: params.botName, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: params.botVerified) + self.navigationItem.titleView = titleView + self.titleView = titleView + } self.moreButtonNode.action = { [weak self] _, gesture in if let strongSelf = self { @@ -3387,6 +3403,13 @@ public final class WebAppController: ViewController, AttachmentContainable { self.presentationDataDisposable?.dispose() } + private var isVerifyAgeBot: Bool { + if let ageBotUsername = self.context.currentAppConfiguration.with({ $0 }).data?["verify_age_bot_username"] as? String { + return self.botAddress == ageBotUsername + } + return false + } + public func beforeMaximize(navigationController: NavigationController, completion: @escaping () -> Void) { switch self.source { case .generic, .settings: @@ -3857,7 +3880,8 @@ public func standaloneWebAppController( willDismiss: @escaping () -> Void = {}, didDismiss: @escaping () -> Void = {}, getNavigationController: @escaping () -> NavigationController? = { return nil }, - getSourceRect: (() -> CGRect?)? = nil + getSourceRect: (() -> CGRect?)? = nil, + verifyAgeCompletion: ((Int) -> Void)? = nil ) -> ViewController { let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: params.peerId), buttons: [.standalone], initialButton: .standalone, fromMenu: params.source == .menu, hasTextInput: false, isFullSize: params.fullSize, makeEntityInputView: { return nil @@ -3868,6 +3892,7 @@ public func standaloneWebAppController( webAppController.completion = completion webAppController.getNavigationController = getNavigationController webAppController.requestSwitchInline = requestSwitchInline + webAppController.verifyAgeCompletion = verifyAgeCompletion present(webAppController, webAppController.mediaPickerContext) } controller.willDismiss = willDismiss diff --git a/versions.json b/versions.json index 059b1bfd9a..03d9138921 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "11.14", + "app": "11.13.3", "xcode": "16.2", "bazel": "8.2.1:22ff65b05869f6160e5157b1b425a14a62085d71d8baef571f462b8fe5a703a3", "macos": "15" From c9e8fe831a456fb324ec901b7103af3cb19e5964 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 21 Jul 2025 01:08:46 +0200 Subject: [PATCH 4/9] Various fixes --- .../Telegram-iOS/en.lproj/Localizable.strings | 3 ++ .../Sources/AddGiftsScreen.swift | 1 + .../Sources/PeerInfoGiftsPaneNode.swift | 43 ++++++++++++------- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index c369488679..389d6884de 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14700,6 +14700,9 @@ Sorry for the inconvenience."; "AccessDenied.AgeVerificationCamera" = "Telegram needs access to your camera for age verification.\n\nOpen your device's Settings > Privacy > Camera and set Telegram to ON."; +"PeerInfo.Gifts.Collections.All" = "All Gifts"; +"PeerInfo.Gifts.Collections.Add" = "Add Collection"; + "PeerInfo.Gifts.AddGiftsButton" = "Add Gifts"; "PeerInfo.Gifts.AddCollection" = "Add Collection"; diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift index 23cdd4f560..a1793154c8 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift @@ -114,6 +114,7 @@ final class AddGiftsScreenComponent: Component { var contentSize = CGSize(width: self.scrollView.bounds.width, height: contentHeight) contentSize.height += environment.safeInsets.bottom contentSize.height = max(contentSize.height, self.scrollView.bounds.size.height) + contentSize.height += 50.0 + 24.0 transition.setFrame(view: giftsListView, frame: CGRect(origin: CGPoint(), size: contentSize)) if self.scrollView.contentSize != contentSize { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index 3011f23546..8d334f5a33 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -523,13 +523,19 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr var topInset: CGFloat = 60.0 - if let collections = self.collections, !collections.isEmpty { + var canEditCollections = false + if self.peerId == self.context.account.peerId || self.canManage { + canEditCollections = true + } + + let hasNonEmptyCollections = self.collections?.contains(where: { $0.count > 0 }) ?? false + if let collections = self.collections, !collections.isEmpty && (hasNonEmptyCollections || canEditCollections) { var tabSelectorItems: [TabSelectorComponent.Item] = [] tabSelectorItems.append(TabSelectorComponent.Item( id: AnyHashable(GiftCollection.all.rawValue), - title: "All Gifts" + title: params.presentationData.strings.PeerInfo_Gifts_Collections_All )) - + var effectiveCollections: [StarGiftCollection] = collections if let reorderedCollectionIds = self.reorderedCollectionIds { var collectionMap: [Int32: StarGiftCollection] = [:] @@ -546,6 +552,9 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } for collection in effectiveCollections { + if !canEditCollections && collection.count == 0 { + continue + } tabSelectorItems.append(TabSelectorComponent.Item( id: AnyHashable(GiftCollection.collection(collection.id).rawValue), content: .component(AnyComponent( @@ -565,19 +574,21 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } )) } - - tabSelectorItems.append(TabSelectorComponent.Item( - id: AnyHashable(GiftCollection.create.rawValue), - content: .component(AnyComponent( - CollectionTabItemComponent( - context: self.context, - icon: .add, - title: "Add Collection", - theme: params.presentationData.theme - ) - )), - isReorderable: false - )) + + if canEditCollections { + tabSelectorItems.append(TabSelectorComponent.Item( + id: AnyHashable(GiftCollection.create.rawValue), + content: .component(AnyComponent( + CollectionTabItemComponent( + context: self.context, + icon: .add, + title: params.presentationData.strings.PeerInfo_Gifts_Collections_Add, + theme: params.presentationData.theme + ) + )), + isReorderable: false + )) + } let tabSelectorSize = self.tabSelector.update( transition: transition, From 28b03349526955571060be8be8bf2c2e8debba50 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 21 Jul 2025 13:39:37 +0200 Subject: [PATCH 5/9] Various fixes --- .../Telegram-iOS/en.lproj/Localizable.strings | 6 ++ .../Sources/PremiumIntroScreen.swift | 8 +- .../Sources/PeerInfoScreen.swift | 15 ++- .../Sources/GiftsListView.swift | 7 ++ .../Sources/PeerInfoGiftsPaneNode.swift | 101 +++++++++++++----- .../Sources/TabSelectorComponent.swift | 8 ++ .../ChatControllerOpenTodoContextMenu.swift | 3 +- 7 files changed, 107 insertions(+), 41 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 389d6884de..ff8bcd7404 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14731,6 +14731,12 @@ Sorry for the inconvenience."; "PeerInfo.Gifts.EmptyCollection.Text" = "Add some gifts to this collection."; "PeerInfo.Gifts.EmptyCollection.Action" = "Add to Collection"; +"PeerInfo.Gifts.AddedToCollection" = "The gift has been added to **%@**."; +"PeerInfo.Gifts.RemovedFromCollection" = "The gift has been removed from **%@**."; + +"PeerInfo.Gifts.AddedToCollectionUnique" = "**%1$@** has been added to **%2$@**."; +"PeerInfo.Gifts.RemovedFromCollectionUnique" = "**%1$@** has been removed from **%2$@**."; + "AddGifts.Title" = "Add Gifts"; "AddGifts.AddGifts_1" = "Add %@ Gift"; "AddGifts.AddGifts_any" = "Add %@ Gifts"; diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 76fba73534..e0961e864b 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -3096,13 +3096,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { products.append(PremiumProduct(option: option, storeProduct: product)) } } - - //TODO:release - if let product = availableProducts.first(where: { $0.id.hasSuffix(".annual") }) { - let (currency, price) = product.priceCurrencyAndAmount - products.insert(PremiumProduct(option: PremiumPromoConfiguration.PremiumProductOption(isCurrent: false, months: 24, currency: currency, amount: price * 2, botUrl: "", transactionId: nil, availableForUpgrade: true, storeProductId: "org.telegram.telegramPremium.biannual"), storeProduct: product), at: 0) - } - + strongSelf.products = products strongSelf.isPremium = forceHasPremium || isPremium strongSelf.otherPeerName = otherPeerName diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 557f3ff470..43cd0843f6 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -11364,7 +11364,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let canReorderEquals = lhs.2 == rhs.2 return filterEquals && sortingEquals && canReorderEquals }) - |> map { [weak self, weak pane, weak giftsContext] filter, sorting, canReorder -> ContextController.Items in + |> map { [weak pane, weak giftsContext] filter, sorting, canReorder -> ContextController.Items in var items: [ContextMenuItem] = [] if hasVisibility { @@ -11389,13 +11389,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } }))) - items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_ShareCollection, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.default) - //TODO:release - self?.openShareLink(url: "https://t.me/") - }))) +// items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_ShareCollection, icon: { theme in +// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) +// }, action: { [weak self] _, f in +// f(.default) +// self?.openShareLink(url: "https://t.me/") +// }))) } if canReorder { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift index b42e5eae26..2a52b933bc 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift @@ -408,6 +408,13 @@ final class GiftsListView: UIView { } return self.updateScrolling(interactive: interactive, topInset: topInset, visibleBounds: visibleBounds, transition: transition) } + + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + if let topInset = self.topInset, point.y < topInset { + return false + } + return super.point(inside: point, with: event) + } func updateScrolling(interactive: Bool = false, topInset: CGFloat, visibleBounds: CGRect, transition: ComponentTransition) -> CGFloat { self.topInset = topInset diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index 8d334f5a33..0c732eb347 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -184,7 +184,11 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr self.scrollNode.view.contentInsetAdjustmentBehavior = .never self.scrollNode.view.delegate = self - self.scrollNode.view.insertSubview(self.giftsListView, at: 0) + if let tabSelectorView = self.tabSelector.view { + self.scrollNode.view.insertSubview(self.giftsListView, aboveSubview: tabSelectorView) + } else { + self.scrollNode.view.insertSubview(self.giftsListView, at: 0) + } } private func item(at point: CGPoint) -> (AnyHashable, ComponentView)? { @@ -211,6 +215,10 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } if let collection { self.setCurrentCollection(collection: .collection(collection.id)) + + if let tabSelectorView = self.tabSelector.view as? TabSelectorComponent.View { + tabSelectorView.scrollToEnd() + } } }) }) @@ -230,6 +238,10 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr self?.setCurrentCollection(collection: .all) let _ = self?.profileGiftsCollections.deleteCollection(id: id).start() + + if let tabSelectorView = self?.tabSelector.view as? TabSelectorComponent.View { + tabSelectorView.scrollToStart() + } }) ]), ActionSheetItemGroup(items: [ @@ -468,20 +480,21 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } f(.default) - self.renameCollection(id: id) + Queue.mainQueue().after(0.15) { + self.renameCollection(id: id) + } }))) - items.append(.action(ContextMenuActionItem(text: params.presentationData.strings.PeerInfo_Gifts_ShareCollection, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.actionSheet.primaryTextColor) - }, action: { [weak self] _, f in - guard let self else { - return - } - f(.default) - - //TODO:release - let _ = self - }))) +// items.append(.action(ContextMenuActionItem(text: params.presentationData.strings.PeerInfo_Gifts_ShareCollection, icon: { theme in +// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.actionSheet.primaryTextColor) +// }, action: { [weak self] _, f in +// guard let self else { +// return +// } +// f(.default) +// +// let _ = self +// }))) items.append(.action(ContextMenuActionItem(text: params.presentationData.strings.PeerInfo_Gifts_Reorder, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.actionSheet.primaryTextColor) @@ -502,7 +515,9 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } f(.default) - self.deleteCollection(id: id) + Queue.mainQueue().after(0.15) { + self.deleteCollection(id: id) + } }))) let contextController = ContextController( @@ -640,7 +655,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr if let tabSelectorView = self.tabSelector.view { if tabSelectorView.superview == nil { tabSelectorView.alpha = 1.0 - self.scrollNode.view.addSubview(tabSelectorView) + self.scrollNode.view.insertSubview(tabSelectorView, at: 0) if !transition.animation.isImmediate { tabSelectorView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) @@ -957,10 +972,52 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr }, iconPosition: collection.icon == nil ? .left : .right, action: { [weak self] _, f in f(.default) + guard let self else { + return + } + if isAdded, let giftReference = gift.reference { - let _ = self?.profileGiftsCollections.removeGifts(id: collection.id, gifts: [giftReference]).start() + let _ = self.profileGiftsCollections.removeGifts(id: collection.id, gifts: [giftReference]).start() } else { - let _ = self?.profileGiftsCollections.addGifts(id: collection.id, gifts: [gift]).start() + let _ = self.profileGiftsCollections.addGifts(id: collection.id, gifts: [gift]).start() + } + + var giftFile: TelegramMediaFile? + var giftTitle: String? + switch gift.gift { + case let .generic(gift): + giftFile = gift.file + case let .unique(uniqueGift): + giftTitle = uniqueGift.title + " #\(presentationStringsFormattedNumber(uniqueGift.number, currentParams.presentationData.dateTimeFormat.groupingSeparator))" + for attribute in uniqueGift.attributes { + if case let .model(_, file, _) = attribute { + giftFile = file + } + } + } + + if let giftFile { + let text: String + if let giftTitle { + if isAdded { + text = currentParams.presentationData.strings.PeerInfo_Gifts_RemovedFromCollectionUnique(giftTitle, collection.title).string + } else { + text = currentParams.presentationData.strings.PeerInfo_Gifts_AddedToCollectionUnique(giftTitle, collection.title).string + } + } else { + if isAdded { + text = currentParams.presentationData.strings.PeerInfo_Gifts_RemovedFromCollection(collection.title).string + } else { + text = currentParams.presentationData.strings.PeerInfo_Gifts_AddedToCollection(collection.title).string + } + } + + let undoController = UndoOverlayController( + presentationData: currentParams.presentationData, + content: .sticker(context: self.context, file: giftFile, loop: false, title: nil, text: text, undoText: nil, customAction: nil), + action: { _ in return true } + ) + self.parentController?.present(undoController, in: .current) } }))) } @@ -1370,14 +1427,12 @@ private final class CollectionTabItemComponent: Component { let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.title, font: Font.semibold(14.0), textColor: .white)) + text: .plain(NSAttributedString(string: component.title, font: Font.semibold(14.0), textColor: component.theme.list.itemSecondaryTextColor)) )), environment: {}, containerSize: CGSize(width: availableSize.width, height: 100.0) ) - - let tintColor = component.theme.list.itemSecondaryTextColor - + var iconOffset: CGFloat = 0.0 var iconSize = CGSize() if let icon = component.icon { @@ -1411,7 +1466,7 @@ private final class CollectionTabItemComponent: Component { transition: .immediate, component: AnyComponent(BundleIconComponent( name: "Chat/Input/Media/PanelBadgeAdd", - tintColor: tintColor + tintColor: component.theme.list.itemSecondaryTextColor )), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) @@ -1447,8 +1502,6 @@ private final class CollectionTabItemComponent: Component { self.addSubview(titleView) } titleView.frame = titleFrame - - transition.setTintColor(layer: titleView.layer, color: tintColor) } let size: CGSize diff --git a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift index 53502dd20c..ef0827e734 100644 --- a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift +++ b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift @@ -483,6 +483,14 @@ public final class TabSelectorComponent: Component { } } + public func scrollToStart() { + self.setContentOffset(.zero, animated: true) + } + + public func scrollToEnd() { + self.setContentOffset(CGPoint(x: self.contentSize.width - self.bounds.width, y: 0.0), animated: true) + } + func update(component: TabSelectorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { let selectionColorUpdated = component.colors.selection != self.component?.colors.selection diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift index 36860380ad..e2dcfb2857 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift @@ -128,8 +128,7 @@ extension ChatControllerImpl { } } - if "".isEmpty { - //TODO:release + if canReplyInChat(self.presentationInterfaceState, accountPeerId: self.context.account.peerId) { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Todo_ReplyToItem, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] c, _ in From 6ced6dacd687e2950026dcfc7b3dba790f876e38 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 21 Jul 2025 16:04:37 +0200 Subject: [PATCH 6/9] Various fixes --- .../Sources/AddGiftsScreen.swift | 6 ++++++ .../Sources/PeerInfoGiftsPaneNode.swift | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift index a1793154c8..63383fd9ea 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift @@ -133,6 +133,12 @@ final class AddGiftsScreenComponent: Component { giftsListView = current } else { giftsListView = GiftsListView(context: component.context, peerId: component.peerId, profileGifts: component.profileGifts, giftsCollections: nil, canSelect: true, ignoreCollection: component.collectionId, remainingSelectionCount: component.remainingCount) + giftsListView.onContentUpdated = { [weak self] in + guard let self else { + return + } + self.state?.updated(transition: .immediate) + } giftsListView.selectionUpdated = { [weak self] in guard let self else { return diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index 0c732eb347..219bcc9a2d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -151,6 +151,15 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr super.init() + self.giftsListView.onContentUpdated = { [weak self] in + guard let self else { + return + } + if let params = self.currentParams { + self.update(size: params.size, topInset: params.topInset, sideInset: params.sideInset, bottomInset: params.bottomInset, deviceMetrics: params.deviceMetrics, visibleHeight: params.visibleHeight, isScrollingLockedAtTop: params.isScrollingLockedAtTop, expandProgress: params.expandProgress, navigationHeight: params.navigationHeight, presentationData: params.presentationData, synchronous: true, transition: .immediate) + } + } + self.addSubnode(self.backgroundNode) self.addSubnode(self.scrollNode) From 9dcbc8192342a3df8339c0199137324a012e059c Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 21 Jul 2025 16:46:14 +0200 Subject: [PATCH 7/9] Various fixes --- .../LegacyComponents/Sources/TGMediaAssetsController.m | 5 ++++- submodules/MediaPickerUI/Sources/MediaPickerScreen.swift | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 8c9f3a2d7b..dcdf83da92 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -871,7 +871,7 @@ if (_intent == TGMediaAssetsControllerSendMediaIntent && _selectionContext.allowGrouping) [[NSUserDefaults standardUserDefaults] setObject:@(!_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"]; - + return [TGMediaAssetsController resultSignalsForSelectionContext:_selectionContext editingContext:_editingContext intent:_intent currentItem:currentItem storeAssets:storeAssets convertToJpeg:false descriptionGenerator:descriptionGenerator saveEditedPhotos:_saveEditedPhotos]; } @@ -889,6 +889,9 @@ if (selectedItems.count == 0 && currentItem != nil) [selectedItems addObject:currentItem]; + if (intent == TGMediaAssetsControllerSendMediaIntent) + [[NSUserDefaults standardUserDefaults] setObject:@(editingContext.isHighQualityPhoto) forKey:@"TG_photoHighQuality_v0"]; + if (saveEditedPhotos && storeAssets && editingContext != nil) { NSMutableArray *fullSizeSignals = [[NSMutableArray alloc] init]; diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 3eeeb0943b..70114ae942 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -2159,6 +2159,12 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att strongSelf.controllerNode.dismissInput() } }, selectionState: selectionContext, editingState: editingContext ?? TGMediaEditingContext()) + + let highQualityPhoto = UserDefaults.standard.bool(forKey: "TG_photoHighQuality_v0") + if highQualityPhoto { + self.interaction?.editingState.setHighQualityPhoto(highQualityPhoto) + } + self.interaction?.selectionState?.grouping = true self.interaction?.editingState.sendPaidMessageStars = sendPaidMessageStars ?? 0 From 89c03bc96a36bde79f4b21cdca3ba16ee3b5ba89 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 21 Jul 2025 17:00:15 +0200 Subject: [PATCH 8/9] Hide gifts sortings in collections --- .../Sources/PeerInfoScreen.swift | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 43cd0843f6..a15d813cbf 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -11426,15 +11426,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro items.append(.separator) } - items.append(.action(ContextMenuActionItem(text: sorting == .date ? strings.PeerInfo_Gifts_SortByValue : strings.PeerInfo_Gifts_SortByDate, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: sorting == .date ? "Peer Info/SortValue" : "Peer Info/SortDate"), color: theme.contextMenu.primaryColor) - }, action: { [weak giftsContext] _, f in - f(.default) - - giftsContext?.updateSorting(sorting == .date ? .value : .date) - }))) + if case .all = pane.currentCollection { + items.append(.action(ContextMenuActionItem(text: sorting == .date ? strings.PeerInfo_Gifts_SortByValue : strings.PeerInfo_Gifts_SortByDate, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: sorting == .date ? "Peer Info/SortValue" : "Peer Info/SortDate"), color: theme.contextMenu.primaryColor) + }, action: { [weak giftsContext] _, f in + f(.default) + + giftsContext?.updateSorting(sorting == .date ? .value : .date) + }))) - items.append(.separator) + items.append(.separator) + } let toggleFilter: (ProfileGiftsContext.Filters) -> Void = { [weak giftsContext] value in var updatedFilter = filter From fd714756d2dca86a9c0f387f2d650a08b9ba7195 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 21 Jul 2025 17:42:40 +0200 Subject: [PATCH 9/9] Fix build --- .../PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index a15d813cbf..a1223f9d14 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -11426,7 +11426,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro items.append(.separator) } - if case .all = pane.currentCollection { + if let pane, case .all = pane.currentCollection { items.append(.action(ContextMenuActionItem(text: sorting == .date ? strings.PeerInfo_Gifts_SortByValue : strings.PeerInfo_Gifts_SortByDate, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: sorting == .date ? "Peer Info/SortValue" : "Peer Info/SortDate"), color: theme.contextMenu.primaryColor) }, action: { [weak giftsContext] _, f in