From 3486393f99efd0154f0869675efb3efdae22195b Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Sat, 20 Dec 2025 03:02:08 +0800 Subject: [PATCH] Glass --- .../Sources/CallListController.swift | 9 +- submodules/ChatListUI/BUILD | 2 +- .../Sources/ChatListContainerItemNode.swift | 2 +- .../Sources/ChatListController.swift | 748 ++++++----- .../Sources/ChatListControllerNode.swift | 168 ++- .../Sources/ChatListSearchContainerNode.swift | 2 +- .../ChatTitleActivityContentNode.swift | 4 +- .../Source/Components/Button.swift | 15 + .../Sources/ViewControllerComponent.swift | 2 + submodules/ComposePollUI/BUILD | 1 - .../Sources/ComposePollScreen.swift | 37 +- ...onSequenceCountrySelectionController.swift | 4 +- submodules/Display/Source/NavigationBar.swift | 4 +- .../Source/NavigationBarContentNode.swift | 3 +- .../Source/NavigationBarTitleView.swift | 6 +- .../Sources/FeaturedStickersScreen.swift | 4 +- .../GalleryUI/Sources/GalleryTitleView.swift | 8 +- submodules/HashtagSearchUI/BUILD | 2 + .../HashtagSearchNavigationContentNode.swift | 120 +- .../Sources/InviteRequestsSearchItem.swift | 4 +- submodules/ItemListUI/BUILD | 2 + .../Sources/ItemListController.swift | 19 +- ...ItemListControllerSegmentedTitleView.swift | 66 +- .../ItemListControllerTabsContentNode.swift | 6 +- .../LocationPickerControllerNode.swift | 2 +- .../LocationSearchNavigationContentNode.swift | 4 +- .../Sources/MediaPickerScreen.swift | 10 +- ...hannelDiscussionGroupSetupSearchItem.swift | 8 +- ...GroupInfoSearchNavigationContentNode.swift | 8 +- .../Sources/PremiumIntroScreen.swift | 2 +- .../SearchBarNode/Sources/SearchBarNode.swift | 7 + .../NavigationBarSearchContentNode.swift | 6 +- .../Sources/TabBarContollerNode.swift | 2 +- .../Sources/ComponentsThemes.swift | 2 +- .../DefaultDarkPresentationTheme.swift | 2 +- .../Sources/AnimatedTextComponent.swift | 26 +- ...ntActionsSearchNavigationContentNode.swift | 4 +- .../ChatSearchNavigationContentNode.swift | 8 +- .../Sources/ChatListTabsComponent.swift | 490 ------- .../Sources/ChatListHeaderComponent.swift | 2 +- .../Sources/ChatListNavigationBar.swift | 22 +- .../Sources/ChatListTitleView.swift | 22 +- .../Sources/ChatTitleComponent.swift | 106 +- .../ChatTitleView/Sources/ChatTitleView.swift | 46 +- .../Components/ComposeTodoScreen/BUILD | 1 - .../Sources/ComposeTodoScreen.swift | 37 +- .../EdgeEffect/Sources/EdgeEffect.swift | 2 +- .../Sources/ForumCreateTopicScreen.swift | 2 +- .../Sources/GiftOptionsScreen.swift | 18 +- .../Components/Gifts/GiftStoreScreen/BUILD | 1 + .../Sources/GiftStoreScreen.swift | 82 +- .../Sources/GlassBackgroundComponent.swift | 6 +- .../Sources/GlassBarButtonComponent.swift | 66 +- ...upStickerSearchNavigationContentNode.swift | 6 +- .../HeaderPanelContainerComponent.swift | 8 + .../BUILD | 7 +- .../Sources/HorizontalTabsComponent.swift | 1194 +++++++++++++++++ .../LiquidLens/Sources/LiquidLensView.swift | 93 +- .../Sources/NavigationBarImpl.swift | 146 +- .../Sources/AffiliateProgramSetupScreen.swift | 23 +- .../Sources/CollectionTabItemComponent.swift | 6 +- .../Sources/PeerInfoChatListPaneNode.swift | 2 +- .../Sources/PeerInfoChatPaneNode.swift | 2 +- .../Components/PeerInfo/PeerInfoScreen/BUILD | 1 + .../Sources/PeerInfoHeaderNode.swift | 14 +- .../Sources/PeerInfoPaneContainerNode.swift | 625 +++------ .../Sources/PeerInfoScreen.swift | 9 + .../PeerInfoVisualMediaPaneNode/BUILD | 3 +- .../Sources/PeerInfoGiftsPaneNode.swift | 66 +- .../Sources/PeerInfoStoryPaneNode.swift | 22 +- .../Sources/QuickReplySetupScreen.swift | 2 +- .../Sources/PasskeysScreen.swift | 2 +- .../Sources/UserApperanceScreen.swift | 2 +- .../Sources/StarsPurchaseScreen.swift | 75 +- .../Sources/StarsStatisticsScreen.swift | 55 +- .../Sources/StarsTransactionsScreen.swift | 2 +- .../Components/StorageUsageScreen/BUILD | 1 + .../Sources/DataUsageScreen.swift | 97 +- .../Sources/StorageUsageScreen.swift | 129 +- .../Sources/StoryPeerListComponent.swift | 10 +- .../Sources/TabBarComponent.swift | 5 +- .../Sources/TabSelectorComponent.swift | 2 +- .../Sources/ContactSelectionController.swift | 30 +- .../WebSearchNavigationContentNode.swift | 4 +- 84 files changed, 2842 insertions(+), 2033 deletions(-) delete mode 100644 submodules/TelegramUI/Components/ChatList/ChatListTabsComponent/Sources/ChatListTabsComponent.swift rename submodules/TelegramUI/Components/{ChatList/ChatListTabsComponent => HorizontalTabsComponent}/BUILD (76%) create mode 100644 submodules/TelegramUI/Components/HorizontalTabsComponent/Sources/HorizontalTabsComponent.swift diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index fc8b3fac5e..a59541a57f 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -43,7 +43,7 @@ private final class DeleteAllButtonNode: ASDisplayNode { self.buttonNode.addSubnode(self.titleNode) self.contentNode.contentNode.addSubnode(self.buttonNode) - self.titleNode.attributedText = NSAttributedString(string: presentationData.strings.CallList_DeleteAll, font: Font.regular(17.0), textColor: presentationData.theme.rootController.navigationBar.accentTextColor) + self.titleNode.attributedText = NSAttributedString(string: presentationData.strings.CallList_DeleteAll, font: Font.medium(17.0), textColor: presentationData.theme.chat.inputPanel.panelControlColor) //self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) } @@ -54,9 +54,10 @@ private final class DeleteAllButtonNode: ASDisplayNode { override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { let titleSize = self.titleNode.updateLayout(constrainedSize) - self.titleNode.frame = CGRect(origin: CGPoint(), size: titleSize) - self.buttonNode.frame = CGRect(origin: CGPoint(), size: titleSize) - return titleSize + let size = CGSize(width: 10.0 * 2.0 + titleSize.width, height: 44.0) + self.titleNode.frame = CGRect(origin: CGPoint(x: 10.0, y: floorToScreenPixels((size.height - titleSize.height) * 0.5)), size: titleSize) + self.buttonNode.frame = CGRect(origin: CGPoint(), size: size) + return size } override public func layout() { diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index 5b3b470792..87652ea35f 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -119,7 +119,7 @@ swift_library( "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode", "//submodules/TelegramUI/Components/HeaderPanelContainerComponent", - "//submodules/TelegramUI/Components/ChatList/ChatListTabsComponent", + "//submodules/TelegramUI/Components/HorizontalTabsComponent", "//submodules/TelegramUI/Components/GlobalControlPanelsContext", "//submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent", "//submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent", diff --git a/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift b/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift index 0a7e58172a..55f028e0f8 100644 --- a/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift +++ b/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift @@ -450,7 +450,7 @@ final class ChatListContainerItemNode: ASDisplayNode { self.layoutAdditionalPanels(transition: transition) - let edgeEffectHeight: CGFloat = insets.bottom + let edgeEffectHeight: CGFloat = insets.bottom + 8.0 let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - edgeEffectHeight), size: CGSize(width: size.width, height: edgeEffectHeight)) transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame) self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectFrame.height, 40.0), transition: ComponentTransition(transition)) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 0dc6b2d891..60884e2102 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -58,7 +58,7 @@ import AdsReportScreen import SearchBarNode import ChatListFilterTabContainerNode import HeaderPanelContainerComponent -import ChatListTabsComponent +import HorizontalTabsComponent import GlobalControlPanelsContext private final class ContextControllerContentSourceImpl: ContextControllerContentSource { @@ -749,8 +749,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.currentState.editing, canReorderAllChats: strongSelf.isPremium, filtersLimit: tabContainerData.2, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition) - if let navigationBarView = strongSelf.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View, let headerPanelsView = navigationBarView.headerPanels as? HeaderPanelContainerComponent.View, let tabsView = headerPanelsView.tabs as? ChatListTabsComponent.View { - tabsView.updateTabSwitchFraction(fraction: fraction, transition: ComponentTransition(transition)) + if let navigationBarView = strongSelf.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View, let headerPanelsView = navigationBarView.headerPanels as? HeaderPanelContainerComponent.View, let tabsView = headerPanelsView.tabs as? HorizontalTabsComponent.View { + tabsView.updateTabSwitchFraction(fraction: fraction, isDragging: strongSelf.chatListDisplayNode.mainContainerNode.isSwitchingCurrentItemFilterByDragging, transition: ComponentTransition(transition)) } } self.reloadFilters() @@ -965,6 +965,356 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.requestLayout(transition: .immediate) } + func tabContextGesture(id: Int32?, sourceNode: ContextExtractedContentContainingNode?, sourceView: ContextExtractedContentContainingView?, gesture: ContextGesture?, keepInPlace: Bool, isDisabled: Bool) { + let context = self.context + let filterPeersAreMuted: Signal<(areMuted: Bool, peerIds: [EnginePeer.Id])?, NoError> = self.context.engine.peers.currentChatListFilters() + |> take(1) + |> mapToSignal { filters -> Signal<(areMuted: Bool, peerIds: [EnginePeer.Id])?, NoError> in + guard let filter = filters.first(where: { $0.id == id }) else { + return .single(nil) + } + guard case let .filter(_, _, _, data) = filter else { + return .single(nil) + } + + let filterPredicate: ChatListFilterPredicate = chatListFilterPredicate(filter: data, accountPeerId: context.account.peerId) + return context.engine.peers.getChatListPeers(filterPredicate: filterPredicate) + |> mapToSignal { peers -> Signal<(areMuted: Bool, peerIds: [EnginePeer.Id])?, NoError> in + let peerIds = peers.map(\.id) + return context.engine.data.get( + EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.NotificationSettings.init(id:))), + TelegramEngine.EngineData.Item.NotificationSettings.Global() + ) + |> map { list, globalSettings -> (areMuted: Bool, peerIds: [EnginePeer.Id])? in + for peer in peers { + switch list[peer.id]?.muteState { + case .unmuted: + return (false, peerIds) + case .default: + let globalValue: EngineGlobalNotificationSettings.CategorySettings + switch peer { + case .user, .secretChat: + globalValue = globalSettings.privateChats + case .legacyGroup: + globalValue = globalSettings.groupChats + case let .channel(channel): + if case .broadcast = channel.info { + globalValue = globalSettings.channels + } else { + globalValue = globalSettings.groupChats + } + } + if globalValue.enabled { + return (false, peerIds) + } + default: + break + } + } + return (true, peerIds) + } + } + } + + let _ = combineLatest( + queue: Queue.mainQueue(), + self.context.engine.peers.currentChatListFilters(), + self.context.engine.data.get( + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) + ), + filterPeersAreMuted + ).startStandalone(next: { [weak self] filters, premiumLimits, filterPeersAreMuted in + guard let self else { + return + } + var items: [ContextMenuItem] = [] + if let id = id { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_EditFolder, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) + }, action: { c, f in + c?.dismiss(completion: { [weak self] in + guard let self else { + return + } + if isDisabled { + let context = self.context + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .folders, count: self.tabContainerNode.filtersCount, action: { + let controller = PremiumIntroScreen(context: context, source: .folders) + replaceImpl?(controller) + return true + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + self.push(controller) + } else { + let _ = (self.context.engine.peers.currentChatListFilters() + |> deliverOnMainQueue).startStandalone(next: { [weak self] presetList in + guard let self else { + return + } + var found = false + for filter in presetList { + if filter.id == id { + self.push(chatListFilterPresetController(context: self.context, currentPreset: filter, updated: { _ in })) + f(.dismissWithoutContent) + found = true + break + } + } + if !found { + f(.default) + } + }) + } + }) + }))) + + if let _ = filters.first(where: { $0.id == id }) { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_AddChatsToFolder, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, f in + c?.dismiss(completion: { + guard let self else { + return + } + + if isDisabled { + let context = self.context + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .folders, count: self.tabContainerNode.filtersCount, action: { + let controller = PremiumIntroScreen(context: context, source: .folders) + replaceImpl?(controller) + return true + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + self.push(controller) + } else { + let _ = combineLatest( + queue: Queue.mainQueue(), + self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId), + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) + ), + self.context.engine.peers.currentChatListFilters() + ).startStandalone(next: { [weak self] result, presetList in + guard let self else { + return + } + var found = false + for filter in presetList { + if filter.id == id, case let .filter(_, _, _, data) = filter { + let (accountPeer, limits, premiumLimits) = result + let isPremium = accountPeer?.isPremium ?? false + + let limit = limits.maxFolderChatsCount + let premiumLimit = premiumLimits.maxFolderChatsCount + + if data.includePeers.peers.count >= premiumLimit { + let controller = PremiumLimitScreen(context: self.context, subject: .chatsPerFolder, count: Int32(data.includePeers.peers.count), action: { + return true + }) + self.push(controller) + f(.dismissWithoutContent) + return + } else if data.includePeers.peers.count >= limit && !isPremium { + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: self.context, subject: .chatsPerFolder, count: Int32(data.includePeers.peers.count), action: { + let controller = PremiumIntroScreen(context: self.context, source: .chatsPerFolder) + replaceImpl?(controller) + return true + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + self.push(controller) + f(.dismissWithoutContent) + return + } + + let _ = (self.context.engine.peers.currentChatListFilters() + |> deliverOnMainQueue).startStandalone(next: { [weak self] filters in + guard let self else { + return + } + self.push(chatListFilterAddChatsController(context: self.context, filter: filter, allFilters: filters, limit: limits.maxFolderChatsCount, premiumLimit: premiumLimits.maxFolderChatsCount, isPremium: isPremium, presentUndo: { [weak self] content in + guard let self else { + return + } + self.present(UndoOverlayController(presentationData: self.presentationData, content: content, elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + })) + f(.dismissWithoutContent) + }) + found = true + break + } + } + if !found { + f(.default) + } + }) + } + }) + }))) + + if let filterEntries = self.tabContainerData?.0 { + for filter in filterEntries { + if case let .filter(filterId, _, unread) = filter, filterId == id { + if unread.value > 0 { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_ReadAll, textColor: .primary, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReadAll"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, f in + c?.dismiss(completion: { + guard let self else { + return + } + self.readAllInFilter(id: id) + }) + }))) + } + + for filter in filters { + if filter.id == filterId, case let .filter(_, title, _, data) = filter { + if let filterPeersAreMuted, filterPeersAreMuted.peerIds.count <= 200 { + items.append(.action(ContextMenuActionItem(text: filterPeersAreMuted.areMuted ? self.presentationData.strings.ChatList_ContextUnmuteAll : self.presentationData.strings.ChatList_ContextMuteAll, textColor: .primary, badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: filterPeersAreMuted.areMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, f in + c?.dismiss(completion: { + }) + + guard let self else { + return + } + + let _ = (self.context.engine.peers.updateMultiplePeerMuteSettings(peerIds: filterPeersAreMuted.peerIds, muted: !filterPeersAreMuted.areMuted) + |> deliverOnMainQueue).startStandalone(completed: { [weak self] in + guard let self else { + return + } + + let iconColor: UIColor = .white + let overlayController: UndoOverlayController + if !filterPeersAreMuted.areMuted { + let text = NSMutableAttributedString(string: self.presentationData.strings.ChatList_ToastFolderMutedV2) + let folderNameRange = (text.string as NSString).range(of: "{folder}") + if folderNameRange.location != NSNotFound { + text.replaceCharacters(in: folderNameRange, with: "") + text.insert(title.attributedString(attributes: [ + ChatTextInputAttributes.bold: true + ]), at: folderNameRange.location) + } + + overlayController = UndoOverlayController(presentationData: self.presentationData, content: .universalWithEntities(context: self.context, animation: "anim_profilemute", scale: 0.075, colors: [ + "Middle.Group 1.Fill 1": iconColor, + "Top.Group 1.Fill 1": iconColor, + "Bottom.Group 1.Fill 1": iconColor, + "EXAMPLE.Group 1.Fill 1": iconColor, + "Line.Group 1.Stroke 1": iconColor + ], title: nil, text: text, animateEntities: title.enableAnimations, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }) + } else { + let text = NSMutableAttributedString(string: self.presentationData.strings.ChatList_ToastFolderUnmutedV2) + let folderNameRange = (text.string as NSString).range(of: "{folder}") + if folderNameRange.location != NSNotFound { + text.replaceCharacters(in: folderNameRange, with: "") + text.insert(title.attributedString(attributes: [ + ChatTextInputAttributes.bold: true + ]), at: folderNameRange.location) + } + + overlayController = UndoOverlayController(presentationData: self.presentationData, content: .universalWithEntities(context: self.context, animation: "anim_profileunmute", scale: 0.075, colors: [ + "Middle.Group 1.Fill 1": iconColor, + "Top.Group 1.Fill 1": iconColor, + "Bottom.Group 1.Fill 1": iconColor, + "EXAMPLE.Group 1.Fill 1": iconColor, + "Line.Group 1.Stroke 1": iconColor + ], title: nil, text: text, animateEntities: title.enableAnimations, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }) + } + self.present(overlayController, in: .current) + }) + }))) + } + + if !data.includePeers.peers.isEmpty && data.categories.isEmpty && !data.excludeRead && !data.excludeMuted && !data.excludeArchived && data.excludePeers.isEmpty { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_ContextMenuShare, textColor: .primary, badge: data.hasSharedLinks ? nil : ContextMenuActionBadge(value: self.presentationData.strings.ChatList_ContextMenuBadgeNew, color: .accent, style: .label), icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, f in + c?.dismiss(completion: { + guard let self else { + return + } + self.shareFolder(filterId: filterId, data: data, title: title) + }) + }))) + } + + break + } + } + + break + } + } + } + + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_RemoveFolder, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] c, f in + c?.dismiss(completion: { + guard let self else { + return + } + self.askForFilterRemoval(id: id) + }) + }))) + } + } else { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_EditFolders, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, f in + c?.dismiss(completion: { + guard let self else { + return + } + self.openFilterSettings() + }) + }))) + } + + if filters.count > 1 { + items.append(.separator) + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_ReorderTabs, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, f in + c?.dismiss(completion: { + guard let self else { + return + } + + self.chatListDisplayNode.isReorderingFilters = true + self.isReorderingTabsValue.set(true) + + (self.parent as? TabBarController)?.updateIsTabBarEnabled(false, transition: .animated(duration: 0.2, curve: .easeInOut)) + if let layout = self.validLayout { + self.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + }) + }))) + } + + if let sourceNode { + let controller = ContextController(presentationData: self.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: self, sourceNode: sourceNode, sourceView: sourceView, keepInPlace: keepInPlace)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) + self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) + } else if let sourceView { + let controller = ContextController(presentationData: self.presentationData, source: .reference(ChatListHeaderBarContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) + self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) + } + }) + } + override public func loadDisplayNode() { self.displayNode = ChatListControllerNode(context: self.context, location: self.location, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, controller: self) @@ -1671,7 +2021,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } else { let contextContentSource: ContextContentSource if peer.id.namespace == Namespaces.Peer.SecretChat, let node = node.subnodes?.first as? ContextExtractedContentContainingNode { - contextContentSource = .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: node, keepInPlace: false)) + contextContentSource = .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: node, sourceView: nil, keepInPlace: false)) } else { var subject: ChatControllerSubject? if case let .search(messageId) = source, let id = messageId { @@ -1732,359 +2082,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - let tabContextGesture: (Int32?, ContextExtractedContentContainingNode, ContextGesture, Bool, Bool) -> Void = { [weak self] id, sourceNode, gesture, keepInPlace, isDisabled in - guard let strongSelf = self else { - return - } - - let context = strongSelf.context - let filterPeersAreMuted: Signal<(areMuted: Bool, peerIds: [EnginePeer.Id])?, NoError> = strongSelf.context.engine.peers.currentChatListFilters() - |> take(1) - |> mapToSignal { filters -> Signal<(areMuted: Bool, peerIds: [EnginePeer.Id])?, NoError> in - guard let filter = filters.first(where: { $0.id == id }) else { - return .single(nil) - } - guard case let .filter(_, _, _, data) = filter else { - return .single(nil) - } - - let filterPredicate: ChatListFilterPredicate = chatListFilterPredicate(filter: data, accountPeerId: context.account.peerId) - return context.engine.peers.getChatListPeers(filterPredicate: filterPredicate) - |> mapToSignal { peers -> Signal<(areMuted: Bool, peerIds: [EnginePeer.Id])?, NoError> in - let peerIds = peers.map(\.id) - return context.engine.data.get( - EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.NotificationSettings.init(id:))), - TelegramEngine.EngineData.Item.NotificationSettings.Global() - ) - |> map { list, globalSettings -> (areMuted: Bool, peerIds: [EnginePeer.Id])? in - for peer in peers { - switch list[peer.id]?.muteState { - case .unmuted: - return (false, peerIds) - case .default: - let globalValue: EngineGlobalNotificationSettings.CategorySettings - switch peer { - case .user, .secretChat: - globalValue = globalSettings.privateChats - case .legacyGroup: - globalValue = globalSettings.groupChats - case let .channel(channel): - if case .broadcast = channel.info { - globalValue = globalSettings.channels - } else { - globalValue = globalSettings.groupChats - } - } - if globalValue.enabled { - return (false, peerIds) - } - default: - break - } - } - return (true, peerIds) - } - } - } - - let _ = combineLatest( - queue: Queue.mainQueue(), - strongSelf.context.engine.peers.currentChatListFilters(), - strongSelf.context.engine.data.get( - TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) - ), - filterPeersAreMuted - ).startStandalone(next: { [weak self] filters, premiumLimits, filterPeersAreMuted in - guard let strongSelf = self else { - return - } - var items: [ContextMenuItem] = [] - if let id = id { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_EditFolder, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) - }, action: { c, f in - c?.dismiss(completion: { - guard let strongSelf = self else { - return - } - if isDisabled { - let context = strongSelf.context - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .folders, count: strongSelf.tabContainerNode.filtersCount, action: { - let controller = PremiumIntroScreen(context: context, source: .folders) - replaceImpl?(controller) - return true - }) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - strongSelf.push(controller) - } else { - let _ = (strongSelf.context.engine.peers.currentChatListFilters() - |> deliverOnMainQueue).startStandalone(next: { presetList in - guard let strongSelf = self else { - return - } - var found = false - for filter in presetList { - if filter.id == id { - strongSelf.push(chatListFilterPresetController(context: strongSelf.context, currentPreset: filter, updated: { _ in })) - f(.dismissWithoutContent) - found = true - break - } - } - if !found { - f(.default) - } - }) - } - }) - }))) - - if let _ = filters.first(where: { $0.id == id }) { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_AddChatsToFolder, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) - }, action: { c, f in - c?.dismiss(completion: { - guard let strongSelf = self else { - return - } - - if isDisabled { - let context = strongSelf.context - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .folders, count: strongSelf.tabContainerNode.filtersCount, action: { - let controller = PremiumIntroScreen(context: context, source: .folders) - replaceImpl?(controller) - return true - }) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - strongSelf.push(controller) - } else { - let _ = combineLatest( - queue: Queue.mainQueue(), - strongSelf.context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId), - TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), - TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) - ), - strongSelf.context.engine.peers.currentChatListFilters() - ).startStandalone(next: { result, presetList in - guard let strongSelf = self else { - return - } - var found = false - for filter in presetList { - if filter.id == id, case let .filter(_, _, _, data) = filter { - let (accountPeer, limits, premiumLimits) = result - let isPremium = accountPeer?.isPremium ?? false - - let limit = limits.maxFolderChatsCount - let premiumLimit = premiumLimits.maxFolderChatsCount - - if data.includePeers.peers.count >= premiumLimit { - let controller = PremiumLimitScreen(context: strongSelf.context, subject: .chatsPerFolder, count: Int32(data.includePeers.peers.count), action: { - return true - }) - strongSelf.push(controller) - f(.dismissWithoutContent) - return - } else if data.includePeers.peers.count >= limit && !isPremium { - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: strongSelf.context, subject: .chatsPerFolder, count: Int32(data.includePeers.peers.count), action: { - let controller = PremiumIntroScreen(context: strongSelf.context, source: .chatsPerFolder) - replaceImpl?(controller) - return true - }) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - strongSelf.push(controller) - f(.dismissWithoutContent) - return - } - - let _ = (strongSelf.context.engine.peers.currentChatListFilters() - |> deliverOnMainQueue).startStandalone(next: { filters in - guard let strongSelf = self else { - return - } - strongSelf.push(chatListFilterAddChatsController(context: strongSelf.context, filter: filter, allFilters: filters, limit: limits.maxFolderChatsCount, premiumLimit: premiumLimits.maxFolderChatsCount, isPremium: isPremium, presentUndo: { content in - guard let strongSelf = self else { - return - } - strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: content, elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) - })) - f(.dismissWithoutContent) - }) - found = true - break - } - } - if !found { - f(.default) - } - }) - } - }) - }))) - - if let filterEntries = strongSelf.tabContainerData?.0 { - for filter in filterEntries { - if case let .filter(filterId, _, unread) = filter, filterId == id { - if unread.value > 0 { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_ReadAll, textColor: .primary, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReadAll"), color: theme.contextMenu.primaryColor) - }, action: { c, f in - c?.dismiss(completion: { - guard let strongSelf = self else { - return - } - strongSelf.readAllInFilter(id: id) - }) - }))) - } - - for filter in filters { - if filter.id == filterId, case let .filter(_, title, _, data) = filter { - if let filterPeersAreMuted, filterPeersAreMuted.peerIds.count <= 200 { - items.append(.action(ContextMenuActionItem(text: filterPeersAreMuted.areMuted ? strongSelf.presentationData.strings.ChatList_ContextUnmuteAll : strongSelf.presentationData.strings.ChatList_ContextMuteAll, textColor: .primary, badge: nil, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: filterPeersAreMuted.areMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) - }, action: { c, f in - c?.dismiss(completion: { - }) - - guard let strongSelf = self else { - return - } - - let _ = (strongSelf.context.engine.peers.updateMultiplePeerMuteSettings(peerIds: filterPeersAreMuted.peerIds, muted: !filterPeersAreMuted.areMuted) - |> deliverOnMainQueue).startStandalone(completed: { - guard let strongSelf = self else { - return - } - - let iconColor: UIColor = .white - let overlayController: UndoOverlayController - if !filterPeersAreMuted.areMuted { - let text = NSMutableAttributedString(string: strongSelf.presentationData.strings.ChatList_ToastFolderMutedV2) - let folderNameRange = (text.string as NSString).range(of: "{folder}") - if folderNameRange.location != NSNotFound { - text.replaceCharacters(in: folderNameRange, with: "") - text.insert(title.attributedString(attributes: [ - ChatTextInputAttributes.bold: true - ]), at: folderNameRange.location) - } - - overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universalWithEntities(context: strongSelf.context, animation: "anim_profilemute", scale: 0.075, colors: [ - "Middle.Group 1.Fill 1": iconColor, - "Top.Group 1.Fill 1": iconColor, - "Bottom.Group 1.Fill 1": iconColor, - "EXAMPLE.Group 1.Fill 1": iconColor, - "Line.Group 1.Stroke 1": iconColor - ], title: nil, text: text, animateEntities: title.enableAnimations, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }) - } else { - let text = NSMutableAttributedString(string: strongSelf.presentationData.strings.ChatList_ToastFolderUnmutedV2) - let folderNameRange = (text.string as NSString).range(of: "{folder}") - if folderNameRange.location != NSNotFound { - text.replaceCharacters(in: folderNameRange, with: "") - text.insert(title.attributedString(attributes: [ - ChatTextInputAttributes.bold: true - ]), at: folderNameRange.location) - } - - overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universalWithEntities(context: strongSelf.context, animation: "anim_profileunmute", scale: 0.075, colors: [ - "Middle.Group 1.Fill 1": iconColor, - "Top.Group 1.Fill 1": iconColor, - "Bottom.Group 1.Fill 1": iconColor, - "EXAMPLE.Group 1.Fill 1": iconColor, - "Line.Group 1.Stroke 1": iconColor - ], title: nil, text: text, animateEntities: title.enableAnimations, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }) - } - strongSelf.present(overlayController, in: .current) - }) - }))) - } - - if !data.includePeers.peers.isEmpty && data.categories.isEmpty && !data.excludeRead && !data.excludeMuted && !data.excludeArchived && data.excludePeers.isEmpty { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_ContextMenuShare, textColor: .primary, badge: data.hasSharedLinks ? nil : ContextMenuActionBadge(value: strongSelf.presentationData.strings.ChatList_ContextMenuBadgeNew, color: .accent, style: .label), icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) - }, action: { c, f in - c?.dismiss(completion: { - guard let strongSelf = self else { - return - } - strongSelf.shareFolder(filterId: filterId, data: data, title: title) - }) - }))) - } - - break - } - } - - break - } - } - } - - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_RemoveFolder, textColor: .destructive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) - }, action: { c, f in - c?.dismiss(completion: { - guard let strongSelf = self else { - return - } - strongSelf.askForFilterRemoval(id: id) - }) - }))) - } - } else { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_EditFolders, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) - }, action: { c, f in - c?.dismiss(completion: { - guard let strongSelf = self else { - return - } - strongSelf.openFilterSettings() - }) - }))) - } - - if filters.count > 1 { - items.append(.separator) - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_ReorderTabs, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) - }, action: { c, f in - c?.dismiss(completion: { - guard let strongSelf = self else { - return - } - - strongSelf.chatListDisplayNode.isReorderingFilters = true - strongSelf.isReorderingTabsValue.set(true) - - //TODO:update search enabled - //strongSelf.searchContentNode?.setIsEnabled(false, animated: true) - - (strongSelf.parent as? TabBarController)?.updateIsTabBarEnabled(false, transition: .animated(duration: 0.2, curve: .easeInOut)) - if let layout = strongSelf.validLayout { - strongSelf.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut)) - } - }) - }))) - } - - let controller = ContextController(presentationData: strongSelf.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode, keepInPlace: keepInPlace)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) - strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) - }) - } self.tabContainerNode.contextGesture = { id, sourceNode, gesture, isDisabled in - tabContextGesture(id, sourceNode, gesture, false, isDisabled) + self.tabContextGesture(id: id, sourceNode: sourceNode, sourceView: nil, gesture: gesture, keepInPlace: false, isDisabled: isDisabled) } if case .chatList(.root) = self.location { @@ -3492,7 +3491,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }))) } - let controller = ContextController(presentationData: self.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: self, sourceNode: sourceNode, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) + let controller = ContextController(presentationData: self.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: self, sourceNode: sourceNode, sourceView: nil, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) }) } @@ -4528,7 +4527,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }) } - private func askForFilterRemoval(id: Int32) { + func askForFilterRemoval(id: Int32) { let apply: () -> Void = { [weak self] in guard let strongSelf = self else { return @@ -6552,22 +6551,49 @@ private final class ChatListTabBarContextReferenceContentSource: ContextReferenc } } +private final class ChatListHeaderBarContextReferenceContentSource: ContextReferenceContentSource { + let keepInPlace: Bool = true + let actionsHorizontalAlignment: ContextActionsHorizontalAlignment = .center + + private let controller: ChatListController + private let sourceView: ContextExtractedContentContainingView + + init(controller: ChatListController, sourceView: ContextExtractedContentContainingView) { + self.controller = controller + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo( + referenceView: self.sourceView.contentView, + contentAreaInScreenSpace: UIScreen.main.bounds, + actionsPosition: .bottom + ) + } +} + private final class ChatListHeaderBarContextExtractedContentSource: ContextExtractedContentSource { let keepInPlace: Bool let ignoreContentTouches: Bool = true let blurBackground: Bool = true private let controller: ChatListController - private let sourceNode: ContextExtractedContentContainingNode + private let sourceNode: ContextExtractedContentContainingNode? + private let sourceView: ContextExtractedContentContainingView? - init(controller: ChatListController, sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool) { + init(controller: ChatListController, sourceNode: ContextExtractedContentContainingNode?, sourceView: ContextExtractedContentContainingView?, keepInPlace: Bool) { self.controller = controller self.sourceNode = sourceNode + self.sourceView = sourceView self.keepInPlace = keepInPlace } func takeView() -> ContextControllerTakeViewInfo? { - return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) + if let sourceNode = self.sourceNode { + return ContextControllerTakeViewInfo(containingItem: .node(sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) + } else { + return ContextControllerTakeViewInfo(containingItem: .view(self.sourceView!), contentAreaInScreenSpace: UIScreen.main.bounds) + } } func putBack() -> ContextControllerPutBackViewInfo? { diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index e3c11ddfe5..68d02c1178 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -21,10 +21,9 @@ import ChatFolderLinkPreviewScreen import ChatListHeaderComponent import StoryPeerListComponent import TelegramNotices -import EdgeEffect import ChatListFilterTabContainerNode import HeaderPanelContainerComponent -import ChatListTabsComponent +import HorizontalTabsComponent import PremiumUI import MediaPlaybackHeaderPanelComponent import LiveLocationHeaderPanelComponent @@ -139,6 +138,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele } public var currentItemFilterUpdated: ((ChatListFilterTabEntryId, CGFloat, ContainedViewLayoutTransition, Bool) -> Void)? + public private(set) var isSwitchingCurrentItemFilterByDragging: Bool = false public var currentItemFilter: ChatListFilterTabEntryId { return self.currentItemNode.chatListFilter.flatMap { .filter($0.id) } ?? .all } @@ -582,6 +582,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele itemNode.layer.removeAllAnimations() } self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: .immediate) + self.isSwitchingCurrentItemFilterByDragging = true self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate, true) } } @@ -658,6 +659,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele } } self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: .immediate) + self.isSwitchingCurrentItemFilterByDragging = true self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) } case .cancelled, .ended: @@ -719,6 +721,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele if let switchToId = applyNodeAsCurrent, let itemNode = self.itemNodes[switchToId] { self.applyItemNodeAsCurrent(id: switchToId, itemNode: itemNode) } + self.isSwitchingCurrentItemFilterByDragging = false self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) } default: @@ -1457,76 +1460,121 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { if self.controller?.tabContainerData != nil || !panels.isEmpty { var tabs: AnyComponent? if let tabContainerData = self.controller?.tabContainerData, tabContainerData.0.count > 1 { - let selectedTab: ChatListTabsComponent.Tab.Id + let selectedTab: HorizontalTabsComponent.Tab.Id switch self.effectiveContainerNode.currentItemFilter { case .all: - selectedTab = .all + selectedTab = AnyHashable(Int32.min) case let .filter(id): - selectedTab = .filter(id: id) + selectedTab = AnyHashable(id) } - tabs = AnyComponent(ChatListTabsComponent( + let isEditing = self.isReorderingFilters || (self.mainContainerNode.currentItemNode.currentState.editing && !self.didBeginSelectingChatsWhileEditing) + + tabs = AnyComponent(HorizontalTabsComponent( context: self.context, theme: self.presentationData.theme, - strings: self.presentationData.strings, - tabs: tabContainerData.0.map { entry -> ChatListTabsComponent.Tab in + tabs: tabContainerData.0.map { entry -> HorizontalTabsComponent.Tab in + let id: HorizontalTabsComponent.Tab.Id + let title: HorizontalTabsComponent.Tab.Title + var badge: HorizontalTabsComponent.Tab.Badge? + var isMainTab = false switch entry { case .all: - return .all - case let .filter(id, text, unread): - return .filter(id: id, text: text, unread: ChatListTabsComponent.Tab.UnreadCount( - value: unread.value, - hasUnmuted: unread.hasUnmuted - )) + id = Int32.min + title = HorizontalTabsComponent.Tab.Title(text: self.presentationData.strings.ChatList_Tabs_All, entities: [], enableAnimations: false) + isMainTab = true + case let .filter(idValue, text, unread): + id = AnyHashable(idValue) + title = HorizontalTabsComponent.Tab.Title(text: text.text, entities: text.entities, enableAnimations: text.enableAnimations) + if unread.value != 0 { + badge = HorizontalTabsComponent.Tab.Badge( + title: "\(unread.value)", + isAccent: unread.hasUnmuted + ) + } } + + return HorizontalTabsComponent.Tab( + id: id, + content: .title(title), + badge: badge, + action: { [weak self] in + guard let self, let tabContainerData = self.controller?.tabContainerData else { + return + } + + let isPremium = self.context.isPremium + + let mappedId: ChatListFilterTabEntryId = entry.id + + var isDisabled = false + if let filtersLimit = tabContainerData.2 { + guard let folderIndex = tabContainerData.0.firstIndex(where: { $0.id == mappedId }) else { + return + } + isDisabled = !isPremium && folderIndex >= filtersLimit + } + + if isDisabled { + let filtersCount = tabContainerData.0.count(where: { item in + if case .all = item { + return false + } else { + return true + } + }) + let context = self.context + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .folders, count: Int32(filtersCount), action: { + let controller = PremiumIntroScreen(context: context, source: .folders) + replaceImpl?(controller) + return true + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + self.controller?.push(controller) + } else { + self.controller?.selectTab(id: mappedId) + } + }, + contextAction: { [weak self] sourceView, gesture in + guard let self, let tabContainerData = self.controller?.tabContainerData else { + return + } + + let isPremium = self.context.isPremium + + let mappedId: Int32? + switch entry { + case .all: + mappedId = nil + case let .filter(idValue, _, _): + mappedId = idValue + } + + var isDisabled = false + if let filtersLimit = tabContainerData.2 { + guard let folderIndex = tabContainerData.0.firstIndex(where: { $0.id == entry.id }) else { + return + } + isDisabled = !isPremium && folderIndex >= filtersLimit + } + + self.controller?.tabContextGesture(id: mappedId, sourceNode: nil, sourceView: sourceView, gesture: gesture, keepInPlace: false, isDisabled: isDisabled) + }, + deleteAction: (!isEditing || isMainTab) ? nil : { [weak self] in + guard let self else { + return + } + if case let .filter(id) = entry.id { + self.controller?.askForFilterRemoval(id: id) + } + } + ) }, selectedTab: selectedTab, - selectTab: { [weak self] id in - guard let self, let tabContainerData = self.controller?.tabContainerData else { - return - } - - let isPremium = self.context.isPremium - - let mappedId: ChatListFilterTabEntryId - switch id { - case .all: - mappedId = .all - case let .filter(id): - mappedId = .filter(id) - } - - var isDisabled = false - if let filtersLimit = tabContainerData.2 { - guard let folderIndex = tabContainerData.0.firstIndex(where: { $0.id == mappedId }) else { - return - } - isDisabled = !isPremium && folderIndex >= filtersLimit - } - - if isDisabled { - let filtersCount = tabContainerData.0.count(where: { item in - if case .all = item { - return false - } else { - return true - } - }) - let context = self.context - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .folders, count: Int32(filtersCount), action: { - let controller = PremiumIntroScreen(context: context, source: .folders) - replaceImpl?(controller) - return true - }) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - self.controller?.push(controller) - } else { - self.controller?.selectTab(id: mappedId) - } - } + isEditing: isEditing )) } diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 29100646e0..7cd8cfb0b3 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -987,7 +987,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo bottomInset += 44.0 - let edgeEffectHeight: CGFloat = bottomInset + let edgeEffectHeight: CGFloat = bottomInset + 8.0 let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight), size: CGSize(width: layout.size.width, height: edgeEffectHeight)) transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame) self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectHeight, 50.0), transition: ComponentTransition(transition)) diff --git a/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityContentNode.swift b/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityContentNode.swift index 1944d18b29..11a1b756b8 100644 --- a/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityContentNode.swift +++ b/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityContentNode.swift @@ -110,7 +110,7 @@ public class ChatTitleActivityContentNode: ASDisplayNode { }) if case .slide = style { - self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 14.0), duration: transitionDuration, additive: true) + self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 4.0), duration: transitionDuration, additive: true) } } @@ -118,7 +118,7 @@ public class ChatTitleActivityContentNode: ASDisplayNode { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: transitionDuration) if case .slide = style { - self.layer.animatePosition(from: CGPoint(x: 0.0, y: -14.0), to: CGPoint(), duration: transitionDuration, additive: true) + self.layer.animatePosition(from: CGPoint(x: 0.0, y: -4.0), to: CGPoint(), duration: transitionDuration, additive: true) } } diff --git a/submodules/ComponentFlow/Source/Components/Button.swift b/submodules/ComponentFlow/Source/Components/Button.swift index c93ef055dc..a40aeb0d5b 100644 --- a/submodules/ComponentFlow/Source/Components/Button.swift +++ b/submodules/ComponentFlow/Source/Components/Button.swift @@ -3,6 +3,7 @@ import UIKit public final class Button: Component { public let content: AnyComponent + public let contentInsets: UIEdgeInsets public let minSize: CGSize? public let hitTestEdgeInsets: UIEdgeInsets? public let tag: AnyObject? @@ -15,6 +16,7 @@ public final class Button: Component { convenience public init( content: AnyComponent, + contentInsets: UIEdgeInsets = UIEdgeInsets(), isEnabled: Bool = true, automaticHighlight: Bool = true, action: @escaping () -> Void, @@ -22,6 +24,7 @@ public final class Button: Component { ) { self.init( content: content, + contentInsets: contentInsets, minSize: nil, hitTestEdgeInsets: nil, tag: nil, @@ -35,6 +38,7 @@ public final class Button: Component { private init( content: AnyComponent, + contentInsets: UIEdgeInsets = UIEdgeInsets(), minSize: CGSize? = nil, hitTestEdgeInsets: UIEdgeInsets? = nil, tag: AnyObject? = nil, @@ -46,6 +50,7 @@ public final class Button: Component { highlightedAction: ActionSlot? ) { self.content = content + self.contentInsets = contentInsets self.minSize = minSize self.hitTestEdgeInsets = hitTestEdgeInsets self.tag = tag @@ -60,6 +65,7 @@ public final class Button: Component { public func minSize(_ minSize: CGSize?) -> Button { return Button( content: self.content, + contentInsets: self.contentInsets, minSize: minSize, hitTestEdgeInsets: self.hitTestEdgeInsets, tag: self.tag, @@ -75,6 +81,7 @@ public final class Button: Component { public func withHitTestEdgeInsets(_ hitTestEdgeInsets: UIEdgeInsets?) -> Button { return Button( content: self.content, + contentInsets: self.contentInsets, minSize: self.minSize, hitTestEdgeInsets: hitTestEdgeInsets, tag: self.tag, @@ -90,6 +97,7 @@ public final class Button: Component { public func withIsExclusive(_ isExclusive: Bool) -> Button { return Button( content: self.content, + contentInsets: self.contentInsets, minSize: self.minSize, hitTestEdgeInsets: self.hitTestEdgeInsets, tag: self.tag, @@ -106,6 +114,7 @@ public final class Button: Component { public func withHoldAction(_ holdAction: ((UIView) -> Void)?) -> Button { return Button( content: self.content, + contentInsets: self.contentInsets, minSize: self.minSize, hitTestEdgeInsets: self.hitTestEdgeInsets, tag: self.tag, @@ -121,6 +130,7 @@ public final class Button: Component { public func tagged(_ tag: AnyObject) -> Button { return Button( content: self.content, + contentInsets: self.contentInsets, minSize: self.minSize, hitTestEdgeInsets: self.hitTestEdgeInsets, tag: tag, @@ -137,6 +147,9 @@ public final class Button: Component { if lhs.content != rhs.content { return false } + if lhs.contentInsets != rhs.contentInsets { + return false + } if lhs.minSize != rhs.minSize { return false } @@ -318,6 +331,8 @@ public final class Button: Component { size.width = max(size.width, minSize.width) size.height = max(size.height, minSize.height) } + size.width += component.contentInsets.left + component.contentInsets.right + size.height += component.contentInsets.top + component.contentInsets.bottom self.component = component diff --git a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift index b7e025bfcf..e017ad6411 100644 --- a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift +++ b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift @@ -286,6 +286,8 @@ open class ViewControllerComponentContainer: ViewController { } super.init(navigationBarPresentationData: navigationBarPresentationData) + self._hasGlassStyle = true + self.setupPresentationData(effectiveUpdatedPresentationData, navigationBarAppearance: navigationBarAppearance, statusBarStyle: statusBarStyle, presentationMode: presentationMode) } diff --git a/submodules/ComposePollUI/BUILD b/submodules/ComposePollUI/BUILD index 04f0e88a47..db6a0ef318 100644 --- a/submodules/ComposePollUI/BUILD +++ b/submodules/ComposePollUI/BUILD @@ -42,7 +42,6 @@ swift_library( "//submodules/ChatPresentationInterfaceState", "//submodules/TelegramUI/Components/EmojiSuggestionsComponent", "//submodules/TelegramUI/Components/ListComposePollOptionComponent", - "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ diff --git a/submodules/ComposePollUI/Sources/ComposePollScreen.swift b/submodules/ComposePollUI/Sources/ComposePollScreen.swift index 6518004aeb..32b9cddef3 100644 --- a/submodules/ComposePollUI/Sources/ComposePollScreen.swift +++ b/submodules/ComposePollUI/Sources/ComposePollScreen.swift @@ -27,7 +27,6 @@ import EmojiSuggestionsComponent import TextFormat import TextFieldComponent import ListComposePollOptionComponent -import EdgeEffect import GlassBarButtonComponent public final class ComposedPoll { @@ -76,6 +75,7 @@ final class ComposePollScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let peer: EnginePeer let isQuiz: Bool? let initialData: ComposePollScreen.InitialData @@ -83,12 +83,14 @@ final class ComposePollScreenComponent: Component { init( context: AccountContext, + overNavigationContainer: UIView, peer: EnginePeer, isQuiz: Bool?, initialData: ComposePollScreen.InitialData, completion: @escaping (ComposedPoll) -> Void ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.peer = peer self.isQuiz = isQuiz self.initialData = initialData @@ -112,7 +114,6 @@ final class ComposePollScreenComponent: Component { final class View: UIView, UIScrollViewDelegate { private let scrollView: UIScrollView - private let edgeEffectView: EdgeEffectView private var reactionInput: ComponentView? private let pollTextSection = ComponentView() @@ -184,8 +185,6 @@ final class ComposePollScreenComponent: Component { self.scrollView.contentInsetAdjustmentBehavior = .never self.scrollView.alwaysBounceVertical = true - self.edgeEffectView = EdgeEffectView() - self.pollOptionsSectionContainer = ListSectionContentView(frame: CGRect()) super.init(frame: frame) @@ -193,8 +192,6 @@ final class ComposePollScreenComponent: Component { self.scrollView.delegate = self self.addSubview(self.scrollView) - self.addSubview(self.edgeEffectView) - let reorderRecognizer = ReorderGestureRecognizer( shouldBegin: { [weak self] point in guard let self, let (id, item) = self.item(at: point) else { @@ -1623,11 +1620,6 @@ final class ComposePollScreenComponent: Component { self.scrollView.verticalScrollIndicatorInsets = scrollInsets } - let edgeEffectHeight: CGFloat = 80.0 - let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) - transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) - self.edgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) - let title = self.isQuiz ? environment.strings.CreatePoll_QuizTitle : environment.strings.CreatePoll_Title let titleSize = self.title.update( transition: .immediate, @@ -1648,19 +1640,19 @@ final class ComposePollScreenComponent: Component { let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: floorToScreenPixels((environment.navigationHeight - titleSize.height) / 2.0) + 3.0), size: titleSize) if let titleView = self.title.view { if titleView.superview == nil { - self.addSubview(titleView) + component.overNavigationContainer.addSubview(titleView) } transition.setFrame(view: titleView, frame: titleFrame) } - let barButtonSize = CGSize(width: 40.0, height: 40.0) + let barButtonSize = CGSize(width: 44.0, height: 44.0) let cancelButtonSize = self.cancelButton.update( transition: transition, component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: environment.theme.overallDarkAppearance, - state: .generic, + state: .glass, component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", @@ -1680,7 +1672,7 @@ final class ComposePollScreenComponent: Component { let cancelButtonFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: 16.0), size: cancelButtonSize) if let cancelButtonView = self.cancelButton.view { if cancelButtonView.superview == nil { - self.addSubview(cancelButtonView) + component.overNavigationContainer.addSubview(cancelButtonView) } transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame) } @@ -1716,7 +1708,7 @@ final class ComposePollScreenComponent: Component { let doneButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - doneButtonSize.width, y: 16.0), size: doneButtonSize) if let doneButtonView = self.doneButton.view { if doneButtonView.superview == nil { - self.addSubview(doneButtonView) + component.overNavigationContainer.addSubview(doneButtonView) } transition.setFrame(view: doneButtonView, frame: doneButtonFrame) } @@ -1797,6 +1789,8 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont fileprivate let completion: (ComposedPoll) -> Void private var isDismissed: Bool = false + private let overNavigationContainer: UIView + fileprivate private(set) var sendButtonItem: UIBarButtonItem? public var isMinimized: Bool = false @@ -1841,13 +1835,16 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont self.context = context self.completion = completion + self.overNavigationContainer = SparseContainerView() + super.init(context: context, component: ComposePollScreenComponent( context: context, + overNavigationContainer: self.overNavigationContainer, peer: peer, isQuiz: isQuiz, initialData: initialData, completion: completion - ), navigationBarAppearance: .transparent, theme: .default) + ), navigationBarAppearance: .default, theme: .default) self._hasGlassStyle = true @@ -1882,6 +1879,10 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont return componentView.attemptNavigation(complete: complete) } + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift index f25be2945b..72af6873c4 100644 --- a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift +++ b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift @@ -169,10 +169,12 @@ private final class AuthorizationSequenceCountrySelectionNavigationContentNode: return 54.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } func activate() { diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 8f82571cc0..7d1058a123 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -145,7 +145,7 @@ public protocol NavigationBar: ASDisplayNode { var clippingNode: SparseNode { get } var backgroundView: UIView { get } - var customOverBackgroundContentSubview: UIView? { get set } + var customOverBackgroundContentView: UIView { get } var contentNode: NavigationBarContentNode? { get } var secondaryContentNode: ASDisplayNode? { get } var secondaryContentNodeDisplayFraction: CGFloat { get set } @@ -187,7 +187,7 @@ public protocol NavigationBar: ASDisplayNode { func executeBack() -> Bool func setHidden(_ hidden: Bool, animated: Bool) - var requestContainerLayout: (ContainedViewLayoutTransition) -> Void { get set } + var requestContainerLayout: ((ContainedViewLayoutTransition) -> Void)? { get set } func updateLayout(size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, additionalCutout: CGSize?, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool, transition: ContainedViewLayoutTransition) } diff --git a/submodules/Display/Source/NavigationBarContentNode.swift b/submodules/Display/Source/NavigationBarContentNode.swift index 768628733b..6a165a64e7 100644 --- a/submodules/Display/Source/NavigationBarContentNode.swift +++ b/submodules/Display/Source/NavigationBarContentNode.swift @@ -26,6 +26,7 @@ open class NavigationBarContentNode: ASDisplayNode { return .replacement } - open func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + open func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { + return size } } diff --git a/submodules/Display/Source/NavigationBarTitleView.swift b/submodules/Display/Source/NavigationBarTitleView.swift index ee8fa822e5..c843151d38 100644 --- a/submodules/Display/Source/NavigationBarTitleView.swift +++ b/submodules/Display/Source/NavigationBarTitleView.swift @@ -1,8 +1,10 @@ import Foundation import UIKit -public protocol NavigationBarTitleView { +public protocol NavigationBarTitleView: UIView { + var requestUpdate: ((ContainedViewLayoutTransition) -> Void)? { get set } + func animateLayoutTransition() - func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) + func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize } diff --git a/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift b/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift index 4ec29aad8f..9ed63103e5 100644 --- a/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift +++ b/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift @@ -1019,10 +1019,12 @@ private final class SearchNavigationContentNode: NavigationBarContentNode { return 54.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: 1.0 + size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } func activate() { diff --git a/submodules/GalleryUI/Sources/GalleryTitleView.swift b/submodules/GalleryUI/Sources/GalleryTitleView.swift index d30da98d38..bfae28100f 100644 --- a/submodules/GalleryUI/Sources/GalleryTitleView.swift +++ b/submodules/GalleryUI/Sources/GalleryTitleView.swift @@ -14,6 +14,8 @@ final class GalleryTitleView: UIView, NavigationBarTitleView { private let authorNameNode: ASTextNode private let dateNode: ASTextNode + var requestUpdate: ((ContainedViewLayoutTransition) -> Void)? + override init(frame: CGRect) { self.authorNameNode = ASTextNode() self.authorNameNode.displaysAsynchronously = false @@ -41,7 +43,9 @@ final class GalleryTitleView: UIView, NavigationBarTitleView { self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: .white) } - func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) { + func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + let size = availableSize + let leftInset: CGFloat = 0.0 let rightInset: CGFloat = 0.0 @@ -55,6 +59,8 @@ final class GalleryTitleView: UIView, NavigationBarTitleView { self.authorNameNode.frame = CGRect(origin: CGPoint(x: floor((size.width - authorNameSize.width) / 2.0), y: floor((size.height - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0)), size: authorNameSize) self.dateNode.frame = CGRect(origin: CGPoint(x: floor((size.width - dateSize.width) / 2.0), y: floor((size.height - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0) + authorNameSize.height + labelsSpacing), size: dateSize) } + + return availableSize } func animateLayoutTransition() { diff --git a/submodules/HashtagSearchUI/BUILD b/submodules/HashtagSearchUI/BUILD index eecdc4a889..a70101927c 100644 --- a/submodules/HashtagSearchUI/BUILD +++ b/submodules/HashtagSearchUI/BUILD @@ -32,6 +32,8 @@ swift_library( "//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/Components/BlurredBackgroundComponent", "//submodules/UIKitRuntimeUtils", + "//submodules/TelegramUI/Components/HorizontalTabsComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift index 1ecbfff335..8390c82469 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift @@ -8,7 +8,8 @@ import TelegramPresentationData import SearchBarNode import ComponentFlow import ComponentDisplayAdapters -import TabSelectorComponent +import HorizontalTabsComponent +import GlassBackgroundComponent private let searchBarFont = Font.regular(17.0) @@ -23,6 +24,9 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode { var onReturn: (String) -> Void = { _ in } private let searchBar: SearchBarNode + + private let tabsBackgroundContainer: GlassBackgroundContainerView + private let tabsBackgroundView: GlassBackgroundView private let tabSelector = ComponentView() private var queryUpdated: ((String) -> Void)? @@ -31,7 +35,7 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode { var selectedIndex: Int = 0 { didSet { if let (size, leftInset, rightInset) = self.validLayout { - self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .animated(duration: 0.35, curve: .spring)) + let _ = self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .animated(duration: 0.35, curve: .spring)) } } } @@ -40,7 +44,7 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode { didSet { if self.transitionFraction != oldValue { if let (size, leftInset, rightInset) = self.validLayout { - self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: self.transitionFraction == nil ? .animated(duration: 0.35, curve: .spring) : .immediate) + let _ = self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: self.transitionFraction == nil ? .animated(duration: 0.35, curve: .spring) : .immediate) } } } @@ -79,12 +83,18 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode { var initialQuery = initialQuery initialQuery.removeFirst() - self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), presentationTheme: theme, strings: strings, fieldStyle: .modern, icon: icon, displayBackground: false) + self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), presentationTheme: theme, strings: strings, fieldStyle: .glass, icon: icon, displayBackground: false) self.searchBar.text = initialQuery self.searchBar.placeholderString = NSAttributedString(string: strings.HashtagSearch_SearchPlaceholder, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) + self.tabsBackgroundContainer = GlassBackgroundContainerView() + self.tabsBackgroundView = GlassBackgroundView() + super.init() + self.tabsBackgroundContainer.contentView.addSubview(self.tabsBackgroundView) + self.view.addSubview(self.tabsBackgroundContainer) + self.searchBar.autocapitalization = .none if hasCurrentChat { @@ -120,7 +130,7 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode { override var nominalHeight: CGFloat { if self.hasCurrentChat { - return 54.0 + 44.0 + return 64.0 + 44.0 } else { return 45.0 } @@ -128,58 +138,98 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode { private var validLayout: (CGSize, CGFloat, CGFloat)? - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { self.validLayout = (size, leftInset, rightInset) let sideInset: CGFloat = 6.0 - let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight + 5.0), size: CGSize(width: size.width, height: 54.0)) + let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: 6.0), size: CGSize(width: size.width, height: 44.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset + sideInset, rightInset: rightInset + sideInset, transition: transition) if self.hasTabs { - var items: [TabSelectorComponent.Item] = [] + var items: [HorizontalTabsComponent.Tab] = [] if self.hasCurrentChat { - items.append(TabSelectorComponent.Item(id: AnyHashable(0), title: self.strings.HashtagSearch_ThisChat)) + items.append(HorizontalTabsComponent.Tab( + id: AnyHashable(0), + content: .title(HorizontalTabsComponent.Tab.Title(text: self.strings.HashtagSearch_ThisChat, entities: [], enableAnimations: false)), + badge: nil, + action: { [weak self] in + guard let self else { + return + } + self.indexUpdated?(0) + }, + contextAction: nil, + deleteAction: nil + )) } - items.append(TabSelectorComponent.Item(id: AnyHashable(1), title: self.strings.HashtagSearch_MyMessages)) - items.append(TabSelectorComponent.Item(id: AnyHashable(2), title: self.strings.HashtagSearch_PublicPosts)) + + items.append(HorizontalTabsComponent.Tab( + id: AnyHashable(1), + content: .title(HorizontalTabsComponent.Tab.Title(text: self.strings.HashtagSearch_MyMessages, entities: [], enableAnimations: false)), + badge: nil, + action: { [weak self] in + guard let self else { + return + } + self.indexUpdated?(1) + }, + contextAction: nil, + deleteAction: nil + )) + + items.append(HorizontalTabsComponent.Tab( + id: AnyHashable(2), + content: .title(HorizontalTabsComponent.Tab.Title(text: self.strings.HashtagSearch_PublicPosts, entities: [], enableAnimations: false)), + badge: nil, + action: { [weak self] in + guard let self else { + return + } + self.indexUpdated?(2) + }, + contextAction: nil, + deleteAction: nil + )) let tabSelectorSize = self.tabSelector.update( transition: ComponentTransition(transition), - component: AnyComponent(TabSelectorComponent( - colors: TabSelectorComponent.Colors( - foreground: self.theme.list.itemSecondaryTextColor, - selection: self.theme.list.itemAccentColor - ), + component: AnyComponent(HorizontalTabsComponent( + context: nil, theme: self.theme, - customLayout: TabSelectorComponent.CustomLayout( - font: Font.medium(14.0), - spacing: self.hasCurrentChat ? 24.0 : 8.0, - lineSelection: true - ), - items: items, - selectedId: AnyHashable(self.selectedIndex), - setSelectedId: { [weak self] id in - guard let self, let index = id.base as? Int else { - return - } - self.indexUpdated?(index) - }, - transitionFraction: self.transitionFraction + tabs: items, + selectedTab: AnyHashable(self.selectedIndex), + isEditing: false )), environment: {}, - containerSize: CGSize(width: size.width, height: 44.0) + containerSize: CGSize(width: size.width - (leftInset + 16.0) * 2.0, height: 44.0) ) let tabSelectorFrameOriginX = floorToScreenPixels((size.width - tabSelectorSize.width) / 2.0) - let tabSelectorFrame = CGRect(origin: CGPoint(x: tabSelectorFrameOriginX, y: size.height - tabSelectorSize.height - 10.0), size: tabSelectorSize) - if let tabSelectorView = self.tabSelector.view { + let tabSelectorFrame = CGRect(origin: CGPoint(x: tabSelectorFrameOriginX, y: searchBarFrame.maxY + 10.0), size: tabSelectorSize) + + transition.updateFrame(view: self.tabsBackgroundContainer, frame: tabSelectorFrame) + self.tabsBackgroundContainer.update(size: tabSelectorFrame.size, isDark: self.theme.overallDarkAppearance, transition: ComponentTransition(transition)) + + transition.updateFrame(view: self.tabsBackgroundView, frame: CGRect(origin: CGPoint(), size: tabSelectorFrame.size)) + self.tabsBackgroundView.update(size: tabSelectorFrame.size, cornerRadius: tabSelectorFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), transition: ComponentTransition(transition)) + + if let tabSelectorView = self.tabSelector.view as? HorizontalTabsComponent.View { if tabSelectorView.superview == nil { - self.view.addSubview(tabSelectorView) + self.tabsBackgroundView.contentView.addSubview(tabSelectorView) + tabSelectorView.setOverlayContainerView(overlayContainerView: self.view) } - transition.updateFrame(view: tabSelectorView, frame: tabSelectorFrame) + transition.updateFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(), size: tabSelectorFrame.size)) + + var transitionFraction: CGFloat = 0.0 + if let transitionFractionValue = self.transitionFraction { + transitionFraction = -transitionFractionValue + } + tabSelectorView.updateTabSwitchFraction(fraction: transitionFraction, isDragging: false, transition: ComponentTransition(transition)) } } + + return size } func activate() { diff --git a/submodules/InviteLinksUI/Sources/InviteRequestsSearchItem.swift b/submodules/InviteLinksUI/Sources/InviteRequestsSearchItem.swift index 7202bd643c..28f3932fb8 100644 --- a/submodules/InviteLinksUI/Sources/InviteRequestsSearchItem.swift +++ b/submodules/InviteLinksUI/Sources/InviteRequestsSearchItem.swift @@ -78,10 +78,12 @@ final class SearchNavigationContentNode: NavigationBarContentNode, ItemListContr return 56.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 56.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } func activate() { diff --git a/submodules/ItemListUI/BUILD b/submodules/ItemListUI/BUILD index 71afc19942..cbd9e07683 100644 --- a/submodules/ItemListUI/BUILD +++ b/submodules/ItemListUI/BUILD @@ -34,6 +34,8 @@ swift_library( "//submodules/Components/ComponentDisplayAdapters", "//submodules/TelegramUI/Components/TextNodeWithEntities", "//submodules/TelegramUI/Components/ListItemComponentAdaptor", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/HorizontalTabsComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/ItemListUI/Sources/ItemListController.swift b/submodules/ItemListUI/Sources/ItemListController.swift index f889d9aa30..8eda7718f1 100644 --- a/submodules/ItemListUI/Sources/ItemListController.swift +++ b/submodules/ItemListUI/Sources/ItemListController.swift @@ -690,7 +690,8 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl private let titleNode: ImmediateTextNode private let subtitleNode: ImmediateTextNode - private var validLayout: (CGSize, CGRect)? + private var validLayout: CGSize? + var requestUpdate: ((ContainedViewLayoutTransition) -> Void)? init(theme: PresentationTheme, title: String, subtitle: String) { self.titleNode = ImmediateTextNode() @@ -720,21 +721,23 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl func updateTheme(theme: PresentationTheme) { self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.medium(17.0), textColor: theme.rootController.navigationBar.primaryTextColor) self.subtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor) - if let (size, clearBounds) = self.validLayout { - let _ = self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) + if let size = self.validLayout { + let _ = self.updateLayout(availableSize: size, transition: .immediate) } } override func layoutSubviews() { super.layoutSubviews() - if let (size, clearBounds) = self.validLayout { - let _ = self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) + if let size = self.validLayout { + let _ = self.updateLayout(availableSize: size, transition: .immediate) } } - func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) { - self.validLayout = (size, clearBounds) + func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + let size = availableSize + + self.validLayout = size let titleSize = self.titleNode.updateLayout(size) let subtitleSize = self.subtitleNode.updateLayout(size) @@ -745,6 +748,8 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl self.titleNode.frame = titleFrame self.subtitleNode.frame = subtitleFrame + + return availableSize } func animateLayoutTransition() { diff --git a/submodules/ItemListUI/Sources/ItemListControllerSegmentedTitleView.swift b/submodules/ItemListUI/Sources/ItemListControllerSegmentedTitleView.swift index f2d0b36fbe..d70f2e3a47 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerSegmentedTitleView.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerSegmentedTitleView.swift @@ -3,9 +3,12 @@ import UIKit import Display import TelegramPresentationData import ComponentFlow -import TabSelectorComponent +import GlassBackgroundComponent +import HorizontalTabsComponent public final class ItemListControllerSegmentedTitleView: UIView { + private let backgroundContainer: GlassBackgroundContainerView + private let backgroundView: GlassBackgroundView private let tabSelector = ComponentView() public var theme: PresentationTheme { @@ -42,7 +45,13 @@ public final class ItemListControllerSegmentedTitleView: UIView { self.segments = segments self.index = selectedIndex + self.backgroundContainer = GlassBackgroundContainerView() + self.backgroundView = GlassBackgroundView() + super.init(frame: CGRect()) + + self.addSubview(self.backgroundContainer) + self.backgroundContainer.contentView.addSubview(self.backgroundView) } required public init?(coder aDecoder: NSCoder) { @@ -62,10 +71,19 @@ public final class ItemListControllerSegmentedTitleView: UIView { return } - let mappedItems = zip(0 ..< self.segments.count, self.segments).map { index, segment in - return TabSelectorComponent.Item( + let mappedItems: [HorizontalTabsComponent.Tab] = zip(0 ..< self.segments.count, self.segments).map { index, segment in + return HorizontalTabsComponent.Tab( id: AnyHashable(index), - title: segment + content: .title(HorizontalTabsComponent.Tab.Title(text: segment, entities: [], enableAnimations: false)), + badge: nil, + action: { [weak self] in + guard let self else { + return + } + self.indexUpdated?(index) + }, + contextAction: nil, + deleteAction: nil ) } @@ -77,34 +95,32 @@ public final class ItemListControllerSegmentedTitleView: UIView { let tabSelectorSize = self.tabSelector.update( transition: transition, - component: AnyComponent(TabSelectorComponent( - colors: TabSelectorComponent.Colors( - foreground: self.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.8), - selection: self.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05) - ), + component: AnyComponent(HorizontalTabsComponent( + context: nil, theme: self.theme, - customLayout: TabSelectorComponent.CustomLayout( - font: Font.medium(15.0), - spacing: 8.0 - ), - items: mappedItems, - selectedId: AnyHashable(self.index), - setSelectedId: { [weak self] id in - guard let self, let index = id.base as? Int else { - return - } - self.indexUpdated?(index) - } + tabs: mappedItems, + selectedTab: AnyHashable(self.index), + isEditing: false, + layout: .fit )), environment: {}, - containerSize: CGSize(width: size.width, height: 44.0) + containerSize: CGSize(width: size.width, height: 40.0) ) + let tabSelectorFrame = CGRect(origin: CGPoint(x: floor((size.width - tabSelectorSize.width) / 2.0), y: floor((size.height - tabSelectorSize.height) / 2.0)), size: tabSelectorSize) - if let tabSelectorView = self.tabSelector.view { + + transition.setFrame(view: self.backgroundContainer, frame: tabSelectorFrame) + self.backgroundContainer.update(size: tabSelectorFrame.size, isDark: self.theme.overallDarkAppearance, transition: transition) + + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: tabSelectorFrame.size)) + self.backgroundView.update(size: tabSelectorFrame.size, cornerRadius: tabSelectorFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), transition: transition) + + if let tabSelectorView = self.tabSelector.view as? HorizontalTabsComponent.View { if tabSelectorView.superview == nil { - self.addSubview(tabSelectorView) + self.backgroundView.contentView.addSubview(tabSelectorView) + tabSelectorView.setOverlayContainerView(overlayContainerView: self) } - transition.setFrame(view: tabSelectorView, frame: tabSelectorFrame) + transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(), size: tabSelectorFrame.size)) } } } diff --git a/submodules/ItemListUI/Sources/ItemListControllerTabsContentNode.swift b/submodules/ItemListUI/Sources/ItemListControllerTabsContentNode.swift index 0e2526e212..71f3c5fa6c 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerTabsContentNode.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerTabsContentNode.swift @@ -63,10 +63,10 @@ final class ItemListControllerTabsContentNode: NavigationBarContentNode { guard let (size, leftInset, rightInset) = self.validLayout else { return } - self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: transition) + let _ = self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: transition) } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let isFirstTime = self.validLayout == nil self.validLayout = (size, leftInset, rightInset) @@ -116,6 +116,8 @@ final class ItemListControllerTabsContentNode: NavigationBarContentNode { if isFirstTime { self.requestContainerLayout(.immediate) } + + return size } override var height: CGFloat { diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index 6d87146144..1cab3b1644 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -1382,7 +1382,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM transition.updateFrame(view: titleView, frame: titleFrame) } - let barButtonSize = CGSize(width: 40.0, height: 40.0) + let barButtonSize = CGSize(width: 44.0, height: 44.0) let cancelButtonSize = self.cancelButton.update( transition: ComponentTransition(transition), component: AnyComponent(GlassBarButtonComponent( diff --git a/submodules/LocationUI/Sources/LocationSearchNavigationContentNode.swift b/submodules/LocationUI/Sources/LocationSearchNavigationContentNode.swift index dc2acb4fa0..2ece25f6b7 100644 --- a/submodules/LocationUI/Sources/LocationSearchNavigationContentNode.swift +++ b/submodules/LocationUI/Sources/LocationSearchNavigationContentNode.swift @@ -38,10 +38,12 @@ final class LocationSearchNavigationContentNode: NavigationBarContentNode { return 56.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 56.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } func activate() { diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index f2bf846b99..672868c2f2 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -2548,7 +2548,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att let useGlassButtons = (isBack || !self.controllerNode.scrolledToTop) && !self.controllerNode.isSwitchingAssetGroup let barButtonSideInset: CGFloat = 16.0 - let barButtonSize = CGSize(width: 40.0, height: 40.0) + let barButtonSize = CGSize(width: 44.0, height: 44.0) var buttonTransition = ComponentTransition.easeInOut(duration: 0.25) if case let .animated(duration, _) = transition, duration > 0.25 { @@ -2580,9 +2580,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att transition: buttonTransition, component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: self.presentationData.theme.overallDarkAppearance, - state: useGlassButtons ? .glass : .generic, + state: .glass, component: AnyComponentWithIdentity(id: isBack ? "back" : "close", component: AnyComponent( BundleIconComponent( name: isBack ? "Navigation/Back" : "Navigation/Close", @@ -2611,9 +2611,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att transition: buttonTransition, component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: self.presentationData.theme.overallDarkAppearance, - state: useGlassButtons ? .glass : .generic, + state: .glass, component: AnyComponentWithIdentity(id: "more", component: AnyComponent( LottieComponent( content: LottieComponent.AppBundleContent( diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift index bd45319a0c..b95ba04691 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift @@ -136,7 +136,7 @@ private final class ChannelDiscussionSearchNavigationContentNode: NavigationBarC didSet { if self.activity != oldValue { if let params = self.params { - self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) + let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) } } } @@ -213,7 +213,7 @@ private final class ChannelDiscussionSearchNavigationContentNode: NavigationBarC func updateTheme(_ theme: PresentationTheme) { self.theme = theme if let params = self.params { - self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) + let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) } self.updatePlaceholder() } @@ -228,7 +228,7 @@ private final class ChannelDiscussionSearchNavigationContentNode: NavigationBarC return 60.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { self.params = Params(size: size, leftInset: leftInset, rightInset: rightInset) let transition = ComponentTransition(transition) @@ -304,6 +304,8 @@ private final class ChannelDiscussionSearchNavigationContentNode: NavigationBarC transition.setFrame(view: self.close.background, frame: closeFrame) self.close.background.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + + return size } func activate() { diff --git a/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift b/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift index 824182dcc2..a298a6ab5c 100644 --- a/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift +++ b/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift @@ -49,7 +49,7 @@ final class GroupInfoSearchNavigationContentNode: NavigationBarContentNode, Item didSet { if self.activity != oldValue { if let params = self.params { - self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) + let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) } } } @@ -128,7 +128,7 @@ final class GroupInfoSearchNavigationContentNode: NavigationBarContentNode, Item func updateTheme(_ theme: PresentationTheme) { self.theme = theme if let params = self.params { - self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) + let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) } self.updatePlaceholder() } @@ -148,7 +148,7 @@ final class GroupInfoSearchNavigationContentNode: NavigationBarContentNode, Item return 60.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { self.params = Params(size: size, leftInset: leftInset, rightInset: rightInset) let transition = ComponentTransition(transition) @@ -224,6 +224,8 @@ final class GroupInfoSearchNavigationContentNode: NavigationBarContentNode, Item transition.setFrame(view: self.close.background, frame: closeFrame) self.close.background.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + + return size } func activate() { diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 3d9a74e164..2ccc8db373 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -4010,7 +4010,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { shareLink: { link in shareLinkImpl?(link) } - ), navigationBarAppearance: .transparent, presentationMode: modal ? .modal : .default, theme: forceDark ? .dark : .default, updatedPresentationData: screenContext.updatedPresentationData) + ), navigationBarAppearance: .default, presentationMode: modal ? .modal : .default, theme: forceDark ? .dark : .default, updatedPresentationData: screenContext.updatedPresentationData) if modal { let cancelItem = UIBarButtonItem(title: presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed)) diff --git a/submodules/SearchBarNode/Sources/SearchBarNode.swift b/submodules/SearchBarNode/Sources/SearchBarNode.swift index 5dbd9afb34..7174643fdc 100644 --- a/submodules/SearchBarNode/Sources/SearchBarNode.swift +++ b/submodules/SearchBarNode/Sources/SearchBarNode.swift @@ -1205,6 +1205,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { if case .glass = self.fieldStyle, self.takenSearchPlaceholderContentView == nil { transition.updateFrame(node: self.inlineSearchPlaceholder, frame: searchPlaceholderFrame) + var isFirstTime = false if let theme = self.theme { let _ = self.inlineSearchPlaceholder.updateLayout( placeholderString: self.placeholderString, @@ -1218,6 +1219,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { transition: transition ) if self.inlineSearchPlaceholderContentsView == nil { + isFirstTime = true let inlineSearchPlaceholderContentsView = self.inlineSearchPlaceholder.takeContents() inlineSearchPlaceholderContentsView.onCancel = { [weak self] in guard let self else { @@ -1232,6 +1234,11 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { if let inlineSearchPlaceholderContentsView = self.inlineSearchPlaceholderContentsView { inlineSearchPlaceholderContentsView.update(size: searchPlaceholderFrame.size, isActive: true, transition: transition) transition.updateFrame(view: inlineSearchPlaceholderContentsView, frame: searchPlaceholderFrame) + + if isFirstTime { + self.updateIsEmpty(animated: false) + inlineSearchPlaceholderContentsView.updateSearchIconVisibility(isVisible: !self.activity) + } } } diff --git a/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift b/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift index f42a71f2ef..e63cadc76d 100644 --- a/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift +++ b/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift @@ -139,7 +139,7 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode { fillColor = fillColor.withMultipliedBrightnessBy(0.8) } - let backgroundColor = self.theme?.rootController.navigationBar.opaqueBackgroundColor ?? .clear + let backgroundColor = self.theme?.chatList.regularSearchBarColor ?? .clear let controlColor = self.theme?.chat.inputPanel.panelControlColor ?? .black let placeholderString = NSAttributedString(string: self.placeholder, font: searchBarFont, textColor: textColor) @@ -158,10 +158,12 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode { } } - override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { self.validLayout = (size, leftInset, rightInset) self.updatePlaceholder(self.expansionProgress, size: size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } override public var height: CGFloat { diff --git a/submodules/TabBarUI/Sources/TabBarContollerNode.swift b/submodules/TabBarUI/Sources/TabBarContollerNode.swift index faf5f83fca..4bc946bc59 100644 --- a/submodules/TabBarUI/Sources/TabBarContollerNode.swift +++ b/submodules/TabBarUI/Sources/TabBarContollerNode.swift @@ -321,7 +321,7 @@ final class TabBarControllerNode: ASDisplayNode { }) } - return params.layout.size.height - tabBarFrame.minY + return params.layout.size.height - tabBarFrame.minY - 6.0 } func frameForControllerTab(at index: Int) -> CGRect? { diff --git a/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift b/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift index 15a5670fe2..3e60f0ff6b 100644 --- a/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift +++ b/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift @@ -85,7 +85,7 @@ public extension NavigationBarPresentationData { } convenience init(presentationData: PresentationData, hideBackground: Bool, hideBadge: Bool, hideSeparator: Bool = false, style: NavigationBar.Style = .legacy) { - self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, hideBackground: hideBackground, hideBadge: hideBadge, hideSeparator: hideSeparator, style: style), strings: NavigationBarStrings(presentationStrings: presentationData.strings)) + self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, hideBackground: hideBackground, hideBadge: hideBadge, hideSeparator: hideSeparator, edgeEffectColor: hideBackground ? .clear : nil, style: style), strings: NavigationBarStrings(presentationStrings: presentationData.strings)) } convenience init(presentationTheme: PresentationTheme, presentationStrings: PresentationStrings, style: NavigationBar.Style = .legacy) { diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift index fe645502e6..7ec9fbfb6e 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift @@ -371,7 +371,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati let navigationSearchBar = PresentationThemeNavigationSearchBar( backgroundColor: UIColor(rgb: 0x1c1c1d), accentColor: UIColor(rgb: 0xffffff), - inputFillColor: UIColor(rgb: 0x0f0f0f), + inputFillColor: UIColor(white: 1.0, alpha: 0.1), inputTextColor: UIColor(rgb: 0xffffff), inputPlaceholderTextColor: UIColor(rgb: 0x8f8f8f), inputIconColor: UIColor(rgb: 0x8f8f8f), diff --git a/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift b/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift index 4b15e0747b..42ab7770f9 100644 --- a/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift +++ b/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift @@ -108,6 +108,8 @@ public final class AnimatedTextComponent: Component { public final class View: UIView { private var characters: [CharacterKey: ComponentView] = [:] + private var spaceSize: CGSize? + private var component: AnimatedTextComponent? private weak var state: EmptyComponentState? @@ -120,6 +122,15 @@ public final class AnimatedTextComponent: Component { } func update(component: AnimatedTextComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let spaceSize: CGSize + if let current = self.spaceSize, self.component?.font == component.font { + spaceSize = current + } else { + let spaceSizeValue = NSAttributedString(string: " ", font: component.font, textColor: .black).boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil).size + spaceSize = CGSize(width: ceil(spaceSizeValue.width), height: ceil(spaceSizeValue.height)) + self.spaceSize = spaceSize + } + self.component = component self.state = state @@ -243,15 +254,23 @@ public final class AnimatedTextComponent: Component { let characterComponent: AnyComponent var characterOffset: CGPoint = .zero + var addTrailingSpace = false switch character { case let .text(text): if text == " " { - let spaceSize = NSAttributedString(string: " ", font: component.font, textColor: .black).boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil).size size.height = max(size.height, ceil(spaceSize.height)) size.width += max(0.0, ceil(spaceSize.width)) continue characterLoop } else { + if text.hasPrefix(" ") { + size.height = max(size.height, ceil(spaceSize.height)) + size.width += max(0.0, ceil(spaceSize.width)) + } + if text.hasSuffix(" ") { + addTrailingSpace = true + } + characterComponent = AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString(string: text, font: component.font, textColor: component.color)) )) @@ -327,6 +346,11 @@ public final class AnimatedTextComponent: Component { size.height = max(size.height, characterSize.height) size.width += max(0.0, characterSize.width - UIScreenPixel) + + if addTrailingSpace { + size.height = max(size.height, ceil(spaceSize.height)) + size.width += max(0.0, ceil(spaceSize.width)) + } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsSearchNavigationContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsSearchNavigationContentNode.swift index 63eb156744..6c6b603669 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsSearchNavigationContentNode.swift @@ -51,10 +51,12 @@ final class ChatRecentActionsSearchNavigationContentNode: NavigationBarContentNo return 54.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } func activate() { diff --git a/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/Sources/ChatSearchNavigationContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/Sources/ChatSearchNavigationContentNode.swift index b50857c895..eee01e3198 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/Sources/ChatSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/Sources/ChatSearchNavigationContentNode.swift @@ -126,7 +126,7 @@ public final class ChatSearchNavigationContentNode: NavigationBarContentNode { if self.hasActivity != value { self.hasActivity = value if let params = self.params { - self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) + let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) } } }) @@ -147,7 +147,7 @@ public final class ChatSearchNavigationContentNode: NavigationBarContentNode { } } - override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { self.params = (size, leftInset, rightInset) let transition = ComponentTransition(transition) @@ -223,6 +223,8 @@ public final class ChatSearchNavigationContentNode: NavigationBarContentNode { transition.setFrame(view: self.close.background, frame: closeFrame) self.close.background.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + + return size } public func activate() { @@ -290,7 +292,7 @@ public final class ChatSearchNavigationContentNode: NavigationBarContentNode { if presentationInterfaceState.theme != self.theme { self.theme = presentationInterfaceState.theme if let params = self.params { - self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) + let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) } } } diff --git a/submodules/TelegramUI/Components/ChatList/ChatListTabsComponent/Sources/ChatListTabsComponent.swift b/submodules/TelegramUI/Components/ChatList/ChatListTabsComponent/Sources/ChatListTabsComponent.swift deleted file mode 100644 index 47867c6ffd..0000000000 --- a/submodules/TelegramUI/Components/ChatList/ChatListTabsComponent/Sources/ChatListTabsComponent.swift +++ /dev/null @@ -1,490 +0,0 @@ -import Foundation -import UIKit -import Display -import ComponentFlow -import TelegramPresentationData -import AccountContext -import TelegramCore -import MultilineTextWithEntitiesComponent -import TextBadgeComponent -import LiquidLens - -public final class ChatListTabsComponent: Component { - public enum Tab: Equatable { - public struct UnreadCount: Equatable { - public let value: Int - public let hasUnmuted: Bool - - public init(value: Int, hasUnmuted: Bool) { - self.value = value - self.hasUnmuted = hasUnmuted - } - } - - public enum Id: Hashable { - case all - case filter(id: Int32) - } - - case all - case filter(id: Int32, text: ChatFolderTitle, unread: UnreadCount) - - public var id: Id { - switch self { - case .all: - return .all - case let .filter(id, _, _): - return .filter(id: id) - } - } - } - - public let context: AccountContext - public let theme: PresentationTheme - public let strings: PresentationStrings - public let tabs: [Tab] - public let selectedTab: Tab.Id? - public let selectTab: (Tab.Id) -> Void - - public init( - context: AccountContext, - theme: PresentationTheme, - strings: PresentationStrings, - tabs: [Tab], - selectedTab: Tab.Id?, - selectTab: @escaping (Tab.Id) -> Void - ) { - self.context = context - self.theme = theme - self.strings = strings - self.tabs = tabs - self.selectedTab = selectedTab - self.selectTab = selectTab - } - - public static func ==(lhs: ChatListTabsComponent, rhs: ChatListTabsComponent) -> Bool { - if lhs.theme !== rhs.theme { - return false - } - if lhs.strings !== rhs.strings { - return false - } - if lhs.tabs != rhs.tabs { - return false - } - if lhs.selectedTab != rhs.selectedTab { - return false - } - return true - } - - private final class ScrollView: UIScrollView { - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - return super.hitTest(point, with: event) - } - - override func touchesShouldCancel(in view: UIView) -> Bool { - return true - } - } - - private struct LayoutData { - var size: CGSize - var selectedItemFrame: CGRect - - init(size: CGSize, selectedItemFrame: CGRect) { - self.size = size - self.selectedItemFrame = selectedItemFrame - } - } - - public final class View: UIView, UIScrollViewDelegate { - private let lensView: LiquidLensView - private let scrollView: ScrollView - private let selectionView: UIImageView - private var itemViews: [Tab.Id: ComponentView] = [:] - - private var ignoreScrolling: Bool = false - private var tabSwitchFraction: CGFloat = 0.0 - private var temporaryLiftTimer: Foundation.Timer? - - private var layoutData: LayoutData? - - private var component: ChatListTabsComponent? - private weak var state: EmptyComponentState? - - override init(frame: CGRect) { - self.lensView = LiquidLensView(kind: .noContainer) - self.scrollView = ScrollView() - - self.selectionView = UIImageView() - //self.scrollView.addSubview(self.selectionView) - - super.init(frame: frame) - - self.scrollView.delaysContentTouches = false - self.scrollView.canCancelContentTouches = true - self.scrollView.contentInsetAdjustmentBehavior = .never - self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false - self.scrollView.showsVerticalScrollIndicator = false - self.scrollView.showsHorizontalScrollIndicator = false - self.scrollView.alwaysBounceHorizontal = false - self.scrollView.alwaysBounceVertical = false - self.scrollView.scrollsToTop = false - self.scrollView.clipsToBounds = false - self.scrollView.delegate = self - - self.addSubview(self.lensView) - - self.lensView.contentView.addSubview(self.scrollView) - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - return self.scrollView.hitTest(self.convert(point, to: self.scrollView), with: event) - } - - public func updateTabSwitchFraction(fraction: CGFloat, transition: ComponentTransition) { - self.tabSwitchFraction = -fraction - self.state?.updated(transition: transition, isLocal: true) - } - - public func scrollViewDidScroll(_ scrollView: UIScrollView) { - if self.ignoreScrolling { - return - } - self.updateScrolling(transition: .immediate) - } - - private func updateScrolling(transition: ComponentTransition) { - guard let component = self.component, let layoutData = self.layoutData else { - return - } - self.lensView.update(size: layoutData.size, selectionX: -self.scrollView.contentOffset.x + layoutData.selectedItemFrame.minX, selectionWidth: layoutData.selectedItemFrame.width, isDark: component.theme.overallDarkAppearance, isLifted: self.temporaryLiftTimer != nil, transition: transition) - } - - func update(component: ChatListTabsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - if self.component?.selectedTab != component.selectedTab { - self.tabSwitchFraction = 0.0 - - self.temporaryLiftTimer?.invalidate() - self.temporaryLiftTimer = nil - - if !transition.animation.isImmediate { - self.temporaryLiftTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false, block: { [weak self] timer in - guard let self else { - return - } - if self.temporaryLiftTimer === timer { - self.temporaryLiftTimer = nil - self.state?.updated(transition: .spring(duration: 0.5)) - } - }) - } - } - - self.component = component - self.state = state - - let size = CGSize(width: availableSize.width, height: 40.0) - let sideInset: CGFloat = 3.0 - - var contentWidth: CGFloat = sideInset - - var validIds: [Tab.Id] = [] - for tab in component.tabs { - let tabId = tab.id - validIds.append(tabId) - - var itemTransition = transition - let itemView: ComponentView - if let current = self.itemViews[tabId] { - itemView = current - } else { - itemTransition = itemTransition.withAnimation(.none) - itemView = ComponentView() - self.itemViews[tabId] = itemView - } - let itemSize = itemView.update( - transition: itemTransition, - component: AnyComponent(ItemComponent( - context: component.context, - theme: component.theme, - strings: component.strings, - tab: tab, - selectAction: { [weak self] in - guard let self, let component = self.component else { - return - } - component.selectTab(tabId) - } - )), - environment: {}, - containerSize: CGSize(width: 1000.0, height: size.height) - ) - let itemFrame = CGRect(origin: CGPoint(x: contentWidth, y: 0.0), size: itemSize) - if let itemComponentView = itemView.view { - if itemComponentView.superview == nil { - self.scrollView.addSubview(itemComponentView) - transition.animateAlpha(view: itemComponentView, from: 0.0, to: 1.0) - transition.animateScale(view: itemComponentView, from: 0.001, to: 1.0) - } - itemTransition.setFrame(view: itemComponentView, frame: itemFrame) - } - - contentWidth += itemSize.width - } - contentWidth += sideInset - - var removedIds: [Tab.Id] = [] - for (id, itemView) in self.itemViews { - if !validIds.contains(id) { - removedIds.append(id) - if let itemComponentView = itemView.view { - transition.setScale(view: itemComponentView, scale: 0.001) - transition.setAlpha(view: itemComponentView, alpha: 0.0, completion: { [weak itemComponentView] _ in - itemComponentView?.removeFromSuperview() - }) - } - } - } - for id in removedIds { - self.itemViews.removeValue(forKey: id) - } - - var selectedItemFrame: CGRect? - if let selectedTab = component.selectedTab { - for i in 0 ..< component.tabs.count { - if component.tabs[i].id == selectedTab { - if let itemView = self.itemViews[component.tabs[i].id]?.view { - var selectedItemFrameValue = itemView.frame - - var pendingItemFrame: CGRect? - if self.tabSwitchFraction != 0.0 { - if self.tabSwitchFraction > 0.0 && i != component.tabs.count - 1 { - if let nextItemView = self.itemViews[component.tabs[i + 1].id]?.view { - pendingItemFrame = nextItemView.frame - } - } else if self.tabSwitchFraction < 0.0 && i != 0 { - if let previousItemView = self.itemViews[component.tabs[i - 1].id]?.view { - pendingItemFrame = previousItemView.frame - } - } - } - if let pendingItemFrame { - let fraction = abs(self.tabSwitchFraction) - selectedItemFrameValue.origin.x = selectedItemFrameValue.minX * (1.0 - fraction) + pendingItemFrame.minX * fraction - selectedItemFrameValue.size.width = selectedItemFrameValue.width * (1.0 - fraction) + pendingItemFrame.width * fraction - } - - selectedItemFrame = selectedItemFrameValue - } - break - } - } - } - - if let selectedItemFrame { - var selectionTransition = transition - if self.selectionView.isHidden { - self.selectionView.isHidden = false - selectionTransition = selectionTransition.withAnimation(.none) - } - let selectionFrame = CGRect(origin: CGPoint(x: selectedItemFrame.minX, y: 3.0), size: CGSize(width: selectedItemFrame.width, height: size.height - 3.0 * 2.0)) - - if self.selectionView.image?.size.height != selectionFrame.height { - self.selectionView.image = generateStretchableFilledCircleImage(diameter: selectionFrame.height, color: .white)?.withRenderingMode(.alwaysTemplate) - } - self.selectionView.tintColor = component.theme.chat.inputPanel.panelControlColor.withAlphaComponent(0.1) - - selectionTransition.setFrame(view: self.selectionView, frame: selectionFrame) - } else { - self.selectionView.isHidden = true - } - - self.layoutData = LayoutData( - size: size, - selectedItemFrame: selectedItemFrame ?? CGRect() - ) - - self.ignoreScrolling = true - let contentSize = CGSize(width: contentWidth, height: size.height) - transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: size)) - if self.scrollView.contentSize != contentSize { - self.scrollView.contentSize = contentSize - } - - transition.setFrame(view: self.lensView, frame: CGRect(origin: CGPoint(), size: size)) - self.ignoreScrolling = false - - self.updateScrolling(transition: transition) - - return size - } - } - - public func makeView() -> View { - return View(frame: CGRect()) - } - - public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) - } -} - -private final class ItemComponent: Component { - let context: AccountContext - let theme: PresentationTheme - let strings: PresentationStrings - let tab: ChatListTabsComponent.Tab - let selectAction: () -> Void - - init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, tab: ChatListTabsComponent.Tab, selectAction: @escaping () -> Void) { - self.context = context - self.theme = theme - self.strings = strings - self.tab = tab - self.selectAction = selectAction - } - - static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool { - if lhs.theme !== rhs.theme { - return false - } - if lhs.strings !== rhs.strings { - return false - } - if lhs.tab != rhs.tab { - return false - } - return true - } - - final class View: UIView { - let title = ComponentView() - var badge: ComponentView? - - var component: ItemComponent? - - override init(frame: CGRect) { - super.init(frame: frame) - - self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:)))) - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.component?.selectAction() - } - } - - func update(component: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - self.component = component - - let sideInset: CGFloat = 16.0 - let badgeSpacing: CGFloat = 5.0 - - let font = Font.medium(15.0) - let titleString: NSAttributedString - var badgeData: (title: String, isActive: Bool)? - switch component.tab { - case .all: - titleString = NSAttributedString(string: component.strings.ChatList_Tabs_All, font: font, textColor: component.theme.chat.inputPanel.panelControlColor) - case let .filter(_, text, unread): - titleString = text.attributedString(font: font, textColor: component.theme.chat.inputPanel.panelControlColor) - if unread.value != 0 { - badgeData = ("\(unread.value)", unread.hasUnmuted) - } - } - - let titleSize = self.title.update( - transition: .immediate, - component: AnyComponent(MultilineTextWithEntitiesComponent( - context: component.context, - animationCache: component.context.animationCache, - animationRenderer: component.context.animationRenderer, - placeholderColor: component.theme.chat.inputPanel.panelControlColor.withMultipliedAlpha(0.1), - text: .plain(titleString), - displaysAsynchronously: false - )), - environment: {}, - containerSize: CGSize(width: 300.0, height: 100.0) - ) - - var size = CGSize(width: sideInset + titleSize.width, height: availableSize.height) - - if let badgeData { - let badge: ComponentView - var badgeTransition = transition - if let current = self.badge { - badge = current - } else { - badgeTransition = badgeTransition.withAnimation(.none) - badge = ComponentView() - self.badge = badge - } - let badgeSize = badge.update( - transition: badgeTransition, - component: AnyComponent(TextBadgeComponent( - text: badgeData.title, - font: Font.medium(12.0), - background: badgeData.isActive ? component.theme.list.itemCheckColors.fillColor : component.theme.chatList.unreadBadgeInactiveBackgroundColor, - foreground: component.theme.list.itemCheckColors.foregroundColor, - insets: UIEdgeInsets(top: 1.0, left: 5.0, bottom: 2.0, right: 5.0) - )), - environment: {}, - containerSize: CGSize(width: 100.0, height: 100.0) - ) - size.width += badgeSpacing - let badgeFrame = CGRect(origin: CGPoint(x: size.width, y: floorToScreenPixels((size.height - badgeSize.height) * 0.5)), size: badgeSize) - if let badgeView = badge.view { - if badgeView.superview == nil { - self.addSubview(badgeView) - transition.animateAlpha(view: badgeView, from: 0.0, to: 1.0) - transition.animateScale(view: badgeView, from: 0.001, to: 1.0) - } - badgeTransition.setFrame(view: badgeView, frame: badgeFrame) - } - size.width += badgeSize.width - } else if let badge = self.badge { - self.badge = nil - if let badgeView = badge.view { - transition.setFrame(view: badgeView, frame: badgeView.bounds.size.centered(around: CGPoint(x: size.width + sideInset - badgeView.bounds.width * 0.5, y: size.height * 0.5))) - transition.setScale(view: badgeView, scale: 0.001) - transition.setAlpha(view: badgeView, alpha: 0.0, completion: { [weak badgeView] _ in - badgeView?.removeFromSuperview() - }) - } - } - - size.width += sideInset - - let titleFrame = CGRect(origin: CGPoint(x: sideInset, y: floorToScreenPixels((size.height - titleSize.height) * 0.5)), size: titleSize) - if let titleView = self.title.view { - if titleView.superview == nil { - titleView.layer.anchorPoint = CGPoint() - self.addSubview(titleView) - } - transition.setPosition(view: titleView, position: titleFrame.origin) - titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) - } - - return size - } - } - - func makeView() -> View { - return View(frame: CGRect()) - } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) - } -} diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift index 89876f6a9b..6c12d87bd5 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift @@ -670,7 +670,7 @@ public final class ChatListHeaderComponent: Component { chatListTitleView.theme = theme chatListTitleView.strings = strings chatListTitleView.setTitle(chatListTitle, animated: false) - let titleContentRect = chatListTitleView.updateLayoutInternal(size: chatListTitleContentSize, clearBounds: CGRect(origin: CGPoint(), size: chatListTitleContentSize), transition: transition.containedViewLayoutTransition) + let titleContentRect = chatListTitleView.updateLayoutInternal(size: chatListTitleContentSize, transition: transition.containedViewLayoutTransition) centerContentWidth = floor((chatListTitleContentSize.width * 0.5 - titleContentRect.minX) * 2.0) let centerOffset = sideContentWidth * 0.5 diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift index b13ccb5930..3a54ef3fa0 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift @@ -11,6 +11,14 @@ import TelegramCore import StoryPeerListComponent import EdgeEffect +private func searchScrollHeightValue() -> CGFloat { + return 54.0 +} + +private func storiesHeightValue() -> CGFloat { + return 96.0 +} + public final class ChatListNavigationBar: Component { public final class AnimationHint { let disableStoriesAnimations: Bool @@ -172,10 +180,8 @@ public final class ChatListNavigationBar: Component { } } - public static let searchScrollHeight: CGFloat = 52.0 - public static let storiesScrollHeight: CGFloat = { - return 83.0 - }() + public static let searchScrollHeight: CGFloat = searchScrollHeightValue() + public static let storiesScrollHeight: CGFloat = storiesHeightValue() public final class View: UIView { private let edgeEffectView: EdgeEffectView @@ -340,7 +346,7 @@ public final class ChatListNavigationBar: Component { } let searchSize = CGSize(width: currentLayout.size.width, height: navigationBarSearchContentHeight) - var searchFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height - searchSize.height - self.bottomContentsContainer.bounds.height), size: searchSize) + var searchFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height - searchSize.height - self.bottomContentsContainer.bounds.height - 2.0), size: searchSize) if let activeSearch = component.activeSearch, !activeSearch.isExternal { searchFrame.origin.y = component.statusBarHeight + 8.0 } @@ -366,7 +372,7 @@ public final class ChatListNavigationBar: Component { searchFrameValue = searchFrame transition.setFrameWithAdditivePosition(view: searchContentNode.view, frame: searchFrame) - searchContentNode.updateLayout(size: searchSize, leftInset: component.sideInset, rightInset: component.sideInset, transition: transition.containedViewLayoutTransition) + let _ = searchContentNode.updateLayout(size: searchSize, leftInset: component.sideInset, rightInset: component.sideInset, transition: transition.containedViewLayoutTransition) var searchAlpha: CGFloat = search.isEnabled ? 1.0 : 0.5 if let activeSearch = component.activeSearch, activeSearch.isExternal { @@ -686,10 +692,10 @@ public final class ChatListNavigationBar: Component { } } else { contentHeight += 44.0 - contentHeight += 7.0 + contentHeight += 9.0 if component.search != nil { - contentHeight += navigationBarSearchContentHeight + contentHeight += navigationBarSearchContentHeight + 2.0 } } diff --git a/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleView.swift b/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleView.swift index 98c5db24d6..41cfe13d99 100644 --- a/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleView.swift +++ b/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleView.swift @@ -60,9 +60,10 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation private let animationCache: AnimationCache private let animationRenderer: MultiAnimationRenderer + public var requestUpdate: ((ContainedViewLayoutTransition) -> Void)? public var openStatusSetup: ((UIView) -> Void)? - private var validLayout: (CGSize, CGRect)? + private var validLayout: CGSize? public var manualLayout: Bool = false @@ -321,17 +322,18 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation override public func layoutSubviews() { super.layoutSubviews() - if !self.manualLayout, let (size, clearBounds) = self.validLayout { - let _ = self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) + if !self.manualLayout, let size = self.validLayout { + let _ = self.updateLayout(availableSize: size, transition: .immediate) } } - public func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) { - let _ = self.updateLayoutInternal(size: size, clearBounds: clearBounds, transition: transition) + public func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + let _ = self.updateLayoutInternal(size: availableSize, transition: transition) + return availableSize } - public func updateLayoutInternal(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) -> CGRect { - self.validLayout = (size, clearBounds) + public func updateLayoutInternal(size: CGSize, transition: ContainedViewLayoutTransition) -> CGRect { + self.validLayout = size var indicatorPadding: CGFloat = 0.0 let indicatorSize = self.activityIndicator.bounds.size @@ -339,7 +341,7 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation if !self.activityIndicator.isHidden { indicatorPadding = indicatorSize.width + 6.0 } - var maxTitleWidth = clearBounds.size.width - indicatorPadding + var maxTitleWidth = size.width - indicatorPadding var proxyPadding: CGFloat = 0.0 if !self.proxyNode.isHidden { maxTitleWidth -= 25.0 @@ -357,7 +359,7 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation var titleContentRect = CGRect(origin: CGPoint(x: indicatorPadding + floor((size.width - combinedWidth - indicatorPadding) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize) - titleContentRect.origin.x = min(titleContentRect.origin.x, clearBounds.maxX - proxyPadding - titleContentRect.width) + titleContentRect.origin.x = min(titleContentRect.origin.x, size.width - proxyPadding - titleContentRect.width) let titleFrame = titleContentRect var titleTransition = transition @@ -366,7 +368,7 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation } titleTransition.updateFrame(node: self.titleNode, frame: titleFrame) - let proxyFrame = CGRect(origin: CGPoint(x: clearBounds.maxX - 9.0 - self.proxyNode.bounds.width, y: floor((size.height - self.proxyNode.bounds.height) / 2.0)), size: self.proxyNode.bounds.size) + let proxyFrame = CGRect(origin: CGPoint(x: size.width - 9.0 - self.proxyNode.bounds.width, y: floor((size.height - self.proxyNode.bounds.height) / 2.0)), size: self.proxyNode.bounds.size) self.proxyNode.frame = proxyFrame self.proxyButton.frame = proxyFrame.insetBy(dx: -2.0, dy: -2.0) diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleComponent.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleComponent.swift index eda0606f24..75403455c5 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleComponent.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleComponent.swift @@ -16,16 +16,6 @@ import EmojiStatusComponent import GlassBackgroundComponent public final class ChatNavigationBarTitleView: UIView, NavigationBarTitleView { - private struct Params: Equatable { - let size: CGSize - let clearBounds: CGRect - - init(size: CGSize, clearBounds: CGRect) { - self.size = size - self.clearBounds = clearBounds - } - } - private final class ContentData { let context: AccountContext let theme: PresentationTheme @@ -44,13 +34,14 @@ public final class ChatNavigationBarTitleView: UIView, NavigationBarTitleView { } } + private let parentTitleState = ComponentState() private let title = ComponentView() private var contentData: ContentData? - private var params: Params? private var activities: ChatTitleComponent.Activities? private var networkState: AccountNetworkState? + public var requestUpdate: ((ContainedViewLayoutTransition) -> Void)? public var tapAction: (() -> Void)? public var longTapAction: (() -> Void)? @@ -113,56 +104,57 @@ public final class ChatNavigationBarTitleView: UIView, NavigationBarTitleView { } private func update(transition: ComponentTransition) { - guard let contentData, let params else { - return - } - self.update(params: params, contentData: contentData, transition: transition) + self.requestUpdate?(transition.containedViewLayoutTransition) } - private func update(params: Params, contentData: ContentData, transition: ComponentTransition) { - let _ = self.title.update( - transition: transition, - component: AnyComponent(ChatTitleComponent( - context: contentData.context, - theme: contentData.theme, - strings: contentData.strings, - dateTimeFormat: contentData.dateTimeFormat, - nameDisplayOrder: contentData.nameDisplayOrder, - displayBackground: true, - content: contentData.content, - activities: self.activities, - networkState: self.networkState, - tapped: { [weak self] in - guard let self else { - return + public func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + let transition = ComponentTransition(transition) + + if let contentData = self.contentData { + let titleSize = self.title.update( + transition: transition, + component: AnyComponent(ChatTitleComponent( + context: contentData.context, + theme: contentData.theme, + strings: contentData.strings, + dateTimeFormat: contentData.dateTimeFormat, + nameDisplayOrder: contentData.nameDisplayOrder, + displayBackground: true, + content: contentData.content, + activities: self.activities, + networkState: self.networkState, + tapped: { [weak self] in + guard let self else { + return + } + self.tapAction?() + }, + longTapped: { [weak self] in + guard let self else { + return + } + self.longTapAction?() } - self.tapAction?() - }, - longTapped: { [weak self] in - guard let self else { - return + )), + environment: {}, + containerSize: availableSize + ) + if let titleView = self.title.view { + if titleView.superview == nil { + self.title.parentState = self.parentTitleState + self.parentTitleState._updated = { [weak self] transition, _ in + guard let self else { + return + } + self.requestUpdate?(transition.containedViewLayoutTransition) } - self.longTapAction?() + self.addSubview(titleView) } - )), - environment: {}, - containerSize: params.size - ) - if let titleView = self.title.view { - if titleView.superview == nil { - self.addSubview(titleView) - } - transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(), size: params.size)) - } - } - - public func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) { - let params = Params(size: size, clearBounds: clearBounds) - if self.params != params { - self.params = params - if let contentData { - self.update(params: params, contentData: contentData, transition: ComponentTransition(transition)) + transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(), size: titleSize)) } + return titleSize + } else { + return availableSize } } } @@ -1008,7 +1000,7 @@ public final class ChatTitleComponent: Component { contentSize.height += subtitleSize.height let containerSize = CGSize(width: contentSize.width + containerSideInset * 2.0, height: 44.0) - let containerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - containerSize.width) * 0.5), y: floorToScreenPixels((availableSize.height - containerSize.height) * 0.5)), size: containerSize) + let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((availableSize.height - containerSize.height) * 0.5)), size: containerSize) let titleFrame = CGRect(origin: CGPoint(x: titleLeftIconsWidth + floor((containerFrame.width - titleSize.width - titleLeftIconsWidth - titleRightIconsWidth) * 0.5), y: floor((containerFrame.height - contentSize.height) * 0.5)), size: titleSize) if let titleView = self.title.view { @@ -1132,7 +1124,7 @@ public final class ChatTitleComponent: Component { self.contentContainer.layer.cornerRadius = 0.0 } - return availableSize + return CGSize(width: containerSize.width, height: availableSize.height) } } diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index 07fd3e3423..243d62dec7 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -193,7 +193,9 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { public var disableAnimations: Bool = false var manualLayout: Bool = false - private var validLayout: (CGSize, CGRect)? + private var validLayout: CGSize? + + public var requestUpdate: ((ContainedViewLayoutTransition) -> Void)? private var titleLeftIcon: ChatTitleIcon = .none private var titleRightIcon: ChatTitleIcon = .none @@ -462,8 +464,8 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { if !self.updateStatus(enableAnimation: enableAnimation) { if updated { - if !self.manualLayout, let (size, clearBounds) = self.validLayout { - let _ = self.updateLayout(size: size, clearBounds: clearBounds, transition: (self.disableAnimations || !enableAnimation) ? .immediate : .animated(duration: 0.2, curve: .easeInOut)) + if !self.manualLayout, let size = self.validLayout { + let _ = self.updateLayout(availableSize: size, transition: (self.disableAnimations || !enableAnimation) ? .immediate : .animated(duration: 0.2, curve: .easeInOut)) } } } @@ -720,8 +722,8 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } if self.activityNode.transitionToState(state, animation: enableAnimation ? .slide : .none) { - if !self.manualLayout, let (size, clearBounds) = self.validLayout { - let _ = self.updateLayout(size: size, clearBounds: clearBounds, transition: enableAnimation ? .animated(duration: 0.3, curve: .spring) : .immediate) + if !self.manualLayout, let size = self.validLayout { + let _ = self.updateLayout(availableSize: size, transition: enableAnimation ? .animated(duration: 0.3, curve: .spring) : .immediate) } return true } else { @@ -817,8 +819,8 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { override public func layoutSubviews() { super.layoutSubviews() - if !self.manualLayout, let (size, clearBounds) = self.validLayout { - let _ = self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) + if !self.manualLayout, let size = self.validLayout { + let _ = self.updateLayout(availableSize: size, transition: .immediate) } } @@ -833,17 +835,19 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { self.titleContent = titleContent let _ = self.updateStatus() - if !self.manualLayout, let (size, clearBounds) = self.validLayout { - let _ = self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) + if !self.manualLayout, let size = self.validLayout { + let _ = self.updateLayout(availableSize: size, transition: .immediate) } } } - public func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) { - self.validLayout = (size, clearBounds) + public func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + let size = availableSize - self.button.frame = clearBounds - self.contentContainer.frame = clearBounds + self.validLayout = size + + self.button.frame = CGRect(origin: CGPoint(), size: size) + self.contentContainer.frame = CGRect(origin: CGPoint(), size: size) var leftIconWidth: CGFloat = 0.0 var rightIconWidth: CGFloat = 0.0 @@ -997,7 +1001,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { titleInsets.left = verifiedIconWidth } - var titleSize = self.titleTextNode.updateLayout(size: CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - verifiedIconWidth - statusIconWidth - rightIconWidth - titleSideInset * 2.0, height: size.height), insets: titleInsets, animated: titleTransition.isAnimated) + var titleSize = self.titleTextNode.updateLayout(size: CGSize(width: size.width - leftIconWidth - credibilityIconWidth - verifiedIconWidth - statusIconWidth - rightIconWidth - titleSideInset * 2.0, height: size.height), insets: titleInsets, animated: titleTransition.isAnimated) titleSize.width += credibilityIconWidth titleSize.width += verifiedIconWidth if statusIconWidth > 0.0 { @@ -1007,15 +1011,15 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } } - let activitySize = self.activityNode.updateLayout(CGSize(width: clearBounds.size.width - titleSideInset * 2.0, height: clearBounds.size.height), alignment: .center) + let activitySize = self.activityNode.updateLayout(CGSize(width: size.width - titleSideInset * 2.0, height: size.height), alignment: .center) let titleInfoSpacing: CGFloat = 0.0 var activityFrame = CGRect() if activitySize.height.isZero { - titleFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - titleSize.width) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) + titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) if titleFrame.size.width < size.width { - titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0) + titleFrame.origin.x = floor((size.width - titleFrame.width) / 2.0) } titleTransition.updateFrameAdditive(view: self.titleContainerView, frame: titleFrame) titleTransition.updateFrameAdditive(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) @@ -1023,12 +1027,12 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { let combinedHeight = titleSize.height + activitySize.height + titleInfoSpacing let contentWidth = max(titleSize.width + rightIconWidth, activitySize.width) - var contentX = floor((clearBounds.width - contentWidth) / 2.0) - contentX = max(contentX, clearBounds.minX + 20.0) + var contentX = floor((size.width - contentWidth) / 2.0) + contentX = max(contentX, 20.0) titleFrame = CGRect(origin: CGPoint(x: contentX + floor((contentWidth - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize) - titleFrame.origin.x = max(titleFrame.origin.x, clearBounds.minX + leftIconWidth) + titleFrame.origin.x = max(titleFrame.origin.x, leftIconWidth) titleTransition.updateFrameAdditive(view: self.titleContainerView, frame: titleFrame) titleTransition.updateFrameAdditive(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) @@ -1068,6 +1072,8 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { let componentTransition = ComponentTransition(transition) componentTransition.setFrame(view: self.backgroundView, frame: backgroundFrame) self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: false, transition: componentTransition) + + return availableSize } @objc private func buttonPressed() { diff --git a/submodules/TelegramUI/Components/ComposeTodoScreen/BUILD b/submodules/TelegramUI/Components/ComposeTodoScreen/BUILD index 691a18915c..f10ddb8955 100644 --- a/submodules/TelegramUI/Components/ComposeTodoScreen/BUILD +++ b/submodules/TelegramUI/Components/ComposeTodoScreen/BUILD @@ -44,7 +44,6 @@ swift_library( "//submodules/TelegramUI/Components/ListComposePollOptionComponent", "//submodules/ComposePollUI", "//submodules/Markdown", - "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ diff --git a/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift b/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift index ec47e23130..0f9c474081 100644 --- a/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift +++ b/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift @@ -29,24 +29,26 @@ import TextFieldComponent import ListComposePollOptionComponent import Markdown import PresentationDataUtils -import EdgeEffect import GlassBarButtonComponent final class ComposeTodoScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let peer: EnginePeer let initialData: ComposeTodoScreen.InitialData let completion: (TelegramMediaTodo) -> Void init( context: AccountContext, + overNavigationContainer: UIView, peer: EnginePeer, initialData: ComposeTodoScreen.InitialData, completion: @escaping (TelegramMediaTodo) -> Void ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.peer = peer self.initialData = initialData self.completion = completion @@ -69,7 +71,6 @@ final class ComposeTodoScreenComponent: Component { final class View: UIView, UIScrollViewDelegate { private let scrollView: UIScrollView - private let edgeEffectView: EdgeEffectView private let todoTextSection = ComponentView() @@ -131,8 +132,6 @@ final class ComposeTodoScreenComponent: Component { self.scrollView.contentInsetAdjustmentBehavior = .never self.scrollView.alwaysBounceVertical = true - self.edgeEffectView = EdgeEffectView() - self.todoItemsSectionContainer = ListSectionContentView(frame: CGRect()) self.todoItemsSectionContainer.automaticallyLayoutExternalContentBackgroundView = false @@ -141,8 +140,6 @@ final class ComposeTodoScreenComponent: Component { self.scrollView.delegate = self self.addSubview(self.scrollView) - self.addSubview(self.edgeEffectView) - let reorderRecognizer = ReorderGestureRecognizer( shouldBegin: { [weak self] point in guard let self, let (id, item) = self.item(at: point) else { @@ -1538,11 +1535,6 @@ final class ComposeTodoScreenComponent: Component { } } } - - let edgeEffectHeight: CGFloat = 80.0 - let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) - transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) - self.edgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) let title: String if !component.initialData.canEdit && component.initialData.existingTodo != nil { @@ -1570,19 +1562,19 @@ final class ComposeTodoScreenComponent: Component { let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: floorToScreenPixels((environment.navigationHeight - titleSize.height) / 2.0) + 3.0), size: titleSize) if let titleView = self.title.view { if titleView.superview == nil { - self.addSubview(titleView) + component.overNavigationContainer.addSubview(titleView) } transition.setFrame(view: titleView, frame: titleFrame) } - let barButtonSize = CGSize(width: 40.0, height: 40.0) + let barButtonSize = CGSize(width: 44.0, height: 44.0) let cancelButtonSize = self.cancelButton.update( transition: transition, component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: environment.theme.overallDarkAppearance, - state: .generic, + state: .glass, component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", @@ -1602,7 +1594,7 @@ final class ComposeTodoScreenComponent: Component { let cancelButtonFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: 16.0), size: cancelButtonSize) if let cancelButtonView = self.cancelButton.view { if cancelButtonView.superview == nil { - self.addSubview(cancelButtonView) + component.overNavigationContainer.addSubview(cancelButtonView) } transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame) } @@ -1638,7 +1630,7 @@ final class ComposeTodoScreenComponent: Component { let doneButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - doneButtonSize.width, y: 16.0), size: doneButtonSize) if let doneButtonView = self.doneButton.view { if doneButtonView.superview == nil { - self.addSubview(doneButtonView) + component.overNavigationContainer.addSubview(doneButtonView) } transition.setFrame(view: doneButtonView, frame: doneButtonFrame) } @@ -1703,6 +1695,8 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont fileprivate let completion: (TelegramMediaTodo) -> Void private var isDismissed: Bool = false + private let overNavigationContainer: UIView + fileprivate private(set) var sendButtonItem: UIBarButtonItem? public var isMinimized: Bool = false @@ -1746,12 +1740,15 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont self.context = context self.completion = completion + self.overNavigationContainer = SparseContainerView() + super.init(context: context, component: ComposeTodoScreenComponent( context: context, + overNavigationContainer: self.overNavigationContainer, peer: peer, initialData: initialData, completion: completion - ), navigationBarAppearance: .transparent, theme: .default) + ), navigationBarAppearance: .default, theme: .default) self._hasGlassStyle = true @@ -1786,6 +1783,10 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont return componentView.attemptNavigation(complete: complete) } + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift b/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift index 0eaabdcaf7..793dd39372 100644 --- a/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift +++ b/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift @@ -28,7 +28,7 @@ public class EdgeEffectView: UIView { fatalError("init(coder:) has not been implemented") } - public func update(content: UIColor, blur: Bool = false, alpha: CGFloat = 0.65, rect: CGRect, edge: Edge, edgeSize: CGFloat, transition: ComponentTransition) { + public func update(content: UIColor, blur: Bool = false, alpha: CGFloat = 0.75, rect: CGRect, edge: Edge, edgeSize: CGFloat, transition: ComponentTransition) { transition.setBackgroundColor(view: self.contentView, color: content) switch edge { diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index 08000283b8..0a0b5a4028 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -1012,7 +1012,7 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer { isHiddenUpdatedImpl?(isHidden) }, openPremium: { openPremiumImpl?() - }), navigationBarAppearance: .transparent) + }), navigationBarAppearance: .default) let presentationData = context.sharedContext.currentPresentationData.with { $0 } let title: String diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index 6ccac87cf8..14026bf06d 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -34,6 +34,7 @@ final class GiftOptionsScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let starsContext: StarsContext let peerId: EnginePeer.Id let premiumOptions: [CachedPremiumGiftOption] @@ -42,6 +43,7 @@ final class GiftOptionsScreenComponent: Component { init( context: AccountContext, + overNavigationContainer: UIView, starsContext: StarsContext, peerId: EnginePeer.Id, premiumOptions: [CachedPremiumGiftOption], @@ -49,6 +51,7 @@ final class GiftOptionsScreenComponent: Component { completion: (() -> Void)? ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.starsContext = starsContext self.peerId = peerId self.premiumOptions = premiumOptions @@ -1144,14 +1147,14 @@ final class GiftOptionsScreenComponent: Component { } if isGlass { - let barButtonSize = CGSize(width: 40.0, height: 40.0) + let barButtonSize = CGSize(width: 44.0, height: 44.0) let cancelButtonSize = self.cancelButton.update( transition: transition, component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: theme.overallDarkAppearance, - state: .generic, + state: .glass, component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", @@ -1922,6 +1925,8 @@ final class GiftOptionsScreenComponent: Component { open class GiftOptionsScreen: ViewControllerComponentContainer, GiftOptionsScreenProtocol { private let context: AccountContext + private let overNavigationContainer: UIView + public var parentController: () -> ViewController? = { return nil } @@ -1936,8 +1941,11 @@ open class GiftOptionsScreen: ViewControllerComponentContainer, GiftOptionsScree ) { self.context = context + self.overNavigationContainer = SparseContainerView() + super.init(context: context, component: GiftOptionsScreenComponent( context: context, + overNavigationContainer: self.overNavigationContainer, starsContext: starsContext, peerId: peerId, premiumOptions: premiumOptions, @@ -1954,6 +1962,10 @@ open class GiftOptionsScreen: ViewControllerComponentContainer, GiftOptionsScree } componentView.scrollToTop() } + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD index 26cd12adc0..09e5fc712c 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD @@ -46,6 +46,7 @@ swift_library( "//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/TextFieldComponent", "//submodules/TelegramUI/Components/Gifts/GiftLoadingShimmerView", + "//submodules/TelegramUI/Components/EdgeEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift index f8fcc34ce1..676bb7aecc 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift @@ -27,6 +27,7 @@ import UndoUI import ContextUI import LottieComponent import GiftLoadingShimmerView +import EdgeEffect private let minimumCountToDisplayFilters = 18 @@ -34,17 +35,20 @@ final class GiftStoreScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let starsContext: StarsContext let peerId: EnginePeer.Id let gift: StarGift.Gift init( context: AccountContext, + overNavigationContainer: UIView, starsContext: StarsContext, peerId: EnginePeer.Id, gift: StarGift.Gift ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.starsContext = starsContext self.peerId = peerId self.gift = gift @@ -77,8 +81,7 @@ final class GiftStoreScreenComponent: Component { private let emptyResultsTitle = ComponentView() private let clearFilters = ComponentView() - private let topPanel = ComponentView() - private let topSeparator = ComponentView() + private let edgeEffectView: EdgeEffectView private let cancelButton = ComponentView() private let sortButton = ComponentView() @@ -120,12 +123,16 @@ final class GiftStoreScreenComponent: Component { self.loadingView = GiftLoadingShimmerView() + self.edgeEffectView = EdgeEffectView() + super.init(frame: frame) self.scrollView.delegate = self self.addSubview(self.scrollView) self.addSubview(self.loadingView) + self.addSubview(self.edgeEffectView) + self.scrollView.layer.addSublayer(self.topOverscrollLayer) } @@ -169,13 +176,6 @@ final class GiftStoreScreenComponent: Component { let availableWidth = self.scrollView.bounds.width let availableHeight = self.scrollView.bounds.height - let contentOffset = self.scrollView.contentOffset.y - - let topPanelAlpha = min(20.0, max(0.0, contentOffset)) / 20.0 - if let topPanelView = self.topPanel.view, let topSeparator = self.topSeparator.view { - transition.setAlpha(view: topPanelView, alpha: topPanelAlpha) - transition.setAlpha(view: topSeparator, alpha: topPanelAlpha) - } var topInset = environment.navigationHeight + 39.0 if let initialCount = self.initialCount, initialCount < minimumCountToDisplayFilters { @@ -893,37 +893,12 @@ final class GiftStoreScreenComponent: Component { if let initialCount = self.initialCount, initialCount < minimumCountToDisplayFilters { topPanelHeight = environment.navigationHeight } - - let topPanelSize = self.topPanel.update( - transition: transition, - component: AnyComponent(BlurredBackgroundComponent( - color: theme.rootController.navigationBar.blurredBackgroundColor - )), - environment: {}, - containerSize: CGSize(width: availableSize.width, height: topPanelHeight) - ) - let topSeparatorSize = self.topSeparator.update( - transition: transition, - component: AnyComponent(Rectangle( - color: theme.rootController.navigationBar.separatorColor - )), - environment: {}, - containerSize: CGSize(width: availableSize.width, height: UIScreenPixel) - ) - let topPanelFrame = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: topPanelSize.height)) - let topSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelSize.height), size: CGSize(width: topSeparatorSize.width, height: topSeparatorSize.height)) - if let topPanelView = self.topPanel.view, let topSeparatorView = self.topSeparator.view { - if topPanelView.superview == nil { - topPanelView.alpha = 0.0 - topSeparatorView.alpha = 0.0 - - self.addSubview(topPanelView) - self.addSubview(topSeparatorView) - } - transition.setFrame(view: topPanelView, frame: topPanelFrame) - transition.setFrame(view: topSeparatorView, frame: topSeparatorFrame) - } + + let edgeEffectHeight: CGFloat = environment.navigationHeight + 56.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) + transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, blur: true, rect: edgeEffectFrame, edge: .top, edgeSize: min(30, edgeEffectFrame.height), transition: transition) let balanceTitleSize = self.balanceTitle.update( transition: .immediate, @@ -962,12 +937,12 @@ final class GiftStoreScreenComponent: Component { if let balanceTitleView = self.balanceTitle.view, let balanceValueView = self.balanceValue.view, let balanceIconView = self.balanceIcon.view { if balanceTitleView.superview == nil { - self.addSubview(balanceTitleView) - self.addSubview(balanceValueView) - self.addSubview(balanceIconView) + component.overNavigationContainer.addSubview(balanceTitleView) + component.overNavigationContainer.addSubview(balanceValueView) + component.overNavigationContainer.addSubview(balanceIconView) } let navigationHeight = environment.navigationHeight - environment.statusBarHeight - let topBalanceOriginY = environment.statusBarHeight + (navigationHeight - balanceTitleSize.height - balanceValueSize.height) / 2.0 + let topBalanceOriginY = environment.statusBarHeight + (navigationHeight - balanceTitleSize.height - balanceValueSize.height) / 2.0 + 3.0 balanceTitleView.center = CGPoint(x: availableSize.width - 16.0 - environment.safeInsets.right - balanceTitleSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height / 2.0) balanceTitleView.bounds = CGRect(origin: .zero, size: balanceTitleSize) balanceValueView.center = CGPoint(x: availableSize.width - 16.0 - environment.safeInsets.right - balanceValueSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0) @@ -991,9 +966,9 @@ final class GiftStoreScreenComponent: Component { ) if let titleView = self.title.view { if titleView.superview == nil { - self.addSubview(titleView) + component.overNavigationContainer.addSubview(titleView) } - transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: topInset + 10.0), size: titleSize)) + transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: topInset + 22.0), size: titleSize)) } let effectiveCount: Int32 @@ -1018,10 +993,10 @@ final class GiftStoreScreenComponent: Component { environment: {}, containerSize: CGSize(width: availableSize.width - headerSideInset * 2.0, height: 100.0) ) - let subtitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - subtitleSize.width) / 2.0), y: topInset + 31.0), size: subtitleSize) + let subtitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - subtitleSize.width) / 2.0), y: topInset + 43.0), size: subtitleSize) if let subtitleView = self.subtitle.view { if subtitleView.superview == nil { - self.addSubview(subtitleView) + component.overNavigationContainer.addSubview(subtitleView) } transition.setFrame(view: subtitleView, frame: subtitleFrame) } @@ -1157,9 +1132,9 @@ final class GiftStoreScreenComponent: Component { if let filterSelectorView = self.filterSelector.view { if filterSelectorView.superview == nil { filterSelectorView.alpha = 0.0 - self.addSubview(filterSelectorView) + component.overNavigationContainer.addSubview(filterSelectorView) } - transition.setFrame(view: filterSelectorView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - filterSize.width) / 2.0), y: topInset + 56.0), size: filterSize)) + transition.setFrame(view: filterSelectorView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - filterSize.width) / 2.0), y: topInset + 60.0 + 12.0), size: filterSize)) if let initialCount = self.initialCount, initialCount >= minimumCountToDisplayFilters { loadingTransition.setAlpha(view: filterSelectorView, alpha: 1.0) @@ -1281,6 +1256,8 @@ final class GiftStoreScreenComponent: Component { public class GiftStoreScreen: ViewControllerComponentContainer { private let context: AccountContext + private let overNavigationContainer: UIView + public var parentController: () -> ViewController? = { return nil } @@ -1293,8 +1270,11 @@ public class GiftStoreScreen: ViewControllerComponentContainer { ) { self.context = context + self.overNavigationContainer = SparseContainerView() + super.init(context: context, component: GiftStoreScreenComponent( context: context, + overNavigationContainer: self.overNavigationContainer, starsContext: starsContext, peerId: peerId, gift: gift @@ -1308,6 +1288,10 @@ public class GiftStoreScreen: ViewControllerComponentContainer { } componentView.scrollToTop() } + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift index cb46688070..5d75f955eb 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift @@ -504,7 +504,11 @@ public class GlassBackgroundView: UIView { let glassEffectValue = UIGlassEffect(style: .regular) switch tintColor.kind { case .panel: - glassEffectValue.tintColor = UIColor(white: isDark ? 0.0 : 1.0, alpha: 0.1) + if isDark { + glassEffectValue.tintColor = UIColor(white: 1.0, alpha: 0.1) + } else { + glassEffectValue.tintColor = UIColor(white: 1.0, alpha: 0.1) + } case .custom: glassEffectValue.tintColor = tintColor.color } diff --git a/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift b/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift index eaef002a7c..4ed935590f 100644 --- a/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift +++ b/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift @@ -60,8 +60,8 @@ public final class GlassBarButtonComponent: Component { return true } - public final class View: HighlightTrackingButton { - private let containerView: UIView + public final class View: UIView { + private let containerView: HighlightTrackingButton private let genericContainerView: UIView private let genericBackgroundView: SimpleGlassView private let glassContainerView: UIView @@ -71,25 +71,23 @@ public final class GlassBarButtonComponent: Component { private var component: GlassBarButtonComponent? public override init(frame: CGRect) { - self.containerView = UIView() + self.containerView = HighlightTrackingButton() self.genericContainerView = UIView() self.genericBackgroundView = SimpleGlassView() self.glassContainerView = UIView() super.init(frame: frame) - self.containerView.isUserInteractionEnabled = false self.containerView.layer.rasterizationScale = UIScreenScale - self.addSubview(self.containerView) - self.containerView.addSubview(self.genericContainerView) - self.containerView.addSubview(self.glassContainerView) + self.addSubview(self.genericContainerView) + self.addSubview(self.glassContainerView) self.genericContainerView.addSubview(self.genericBackgroundView) - self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + self.containerView.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) - self.highligthedChanged = { [weak self] highlighted in + self.containerView.highligthedChanged = { [weak self] highlighted in guard let self else { return } @@ -112,11 +110,27 @@ public final class GlassBarButtonComponent: Component { action(self) } + @objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + guard let component = self.component, let action = component.action else { + return + } + action(self) + } + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + guard let result = super.hitTest(point, with: event) else { + return nil + } + return result + } + func update(component: GlassBarButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { let previousComponent = self.component self.component = component - self.isEnabled = component.isEnabled + self.containerView.isEnabled = component.isEnabled var componentView: ComponentView var animateAppearance = false @@ -159,6 +173,7 @@ public final class GlassBarButtonComponent: Component { let componentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((containerSize.width - componentSize.width) / 2.0), y: floorToScreenPixels((containerSize.height - componentSize.height) / 2.0)), size: componentSize) if let view = componentView.view { if view.superview == nil { + view.isUserInteractionEnabled = false self.containerView.addSubview(view) if animateAppearance { transition.animateScale(view: view, from: 0.01, to: 1.0) @@ -213,9 +228,29 @@ public final class GlassBarButtonComponent: Component { self.glassContainerView.addSubview(glassBackgroundView) self.glassBackgroundView = glassBackgroundView + glassBackgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:)))) + transition.animateAlpha(view: glassBackgroundView, from: 0.0, to: 1.0) } - glassBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: effectiveState == .tintedGlass ? .custom : .panel , color: backgroundColor.withMultipliedAlpha(effectiveState == .tintedGlass ? 1.0 : 0.7)), transition: glassBackgroundTransition) + glassBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: effectiveState == .tintedGlass ? .custom : .panel , color: backgroundColor.withMultipliedAlpha(effectiveState == .tintedGlass ? 1.0 : 0.7)), isInteractive: true, transition: glassBackgroundTransition) + glassBackgroundTransition.setFrame(view: glassBackgroundView, frame: bounds) + } else if case .glass = component.state { + let glassBackgroundView: GlassBackgroundView + var glassBackgroundTransition = transition + if let current = self.glassBackgroundView { + glassBackgroundView = current + } else { + glassBackgroundTransition = .immediate + glassBackgroundView = GlassBackgroundView() + glassBackgroundView.isUserInteractionEnabled = false + self.glassContainerView.addSubview(glassBackgroundView) + self.glassBackgroundView = glassBackgroundView + + glassBackgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:)))) + + transition.animateAlpha(view: glassBackgroundView, from: 0.0, to: 1.0) + } + glassBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: .panel, color: UIColor(white: component.isDark ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: glassBackgroundTransition) glassBackgroundTransition.setFrame(view: glassBackgroundView, frame: bounds) } else if let glassBackgroundView = self.glassBackgroundView { self.glassBackgroundView = nil @@ -224,6 +259,15 @@ public final class GlassBarButtonComponent: Component { }) } + if let glassBackgroundView = self.glassBackgroundView { + self.containerView.isUserInteractionEnabled = false + if self.containerView.superview !== glassBackgroundView.contentView { + glassBackgroundView.contentView.addSubview(self.containerView) + } + } else if self.containerView.superview !== self { + self.addSubview(self.containerView) + } + return containerSize } } diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift index d02a4154cf..d8dcb0c72d 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift @@ -48,7 +48,7 @@ final class GroupStickerSearchNavigationContentNode: NavigationBarContentNode, I didSet { if self.activity != oldValue { if let params = self.params { - self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) + let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) } } } @@ -140,7 +140,7 @@ final class GroupStickerSearchNavigationContentNode: NavigationBarContentNode, I return 60.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { self.params = Params(size: size, leftInset: leftInset, rightInset: rightInset) let transition = ComponentTransition(transition) @@ -216,6 +216,8 @@ final class GroupStickerSearchNavigationContentNode: NavigationBarContentNode, I transition.setFrame(view: self.close.background, frame: closeFrame) self.close.background.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + + return size } func activate() { diff --git a/submodules/TelegramUI/Components/HeaderPanelContainerComponent/Sources/HeaderPanelContainerComponent.swift b/submodules/TelegramUI/Components/HeaderPanelContainerComponent/Sources/HeaderPanelContainerComponent.swift index 6d7f77a83b..44bb4b7357 100644 --- a/submodules/TelegramUI/Components/HeaderPanelContainerComponent/Sources/HeaderPanelContainerComponent.swift +++ b/submodules/TelegramUI/Components/HeaderPanelContainerComponent/Sources/HeaderPanelContainerComponent.swift @@ -5,6 +5,10 @@ import ComponentFlow import TelegramPresentationData import GlassBackgroundComponent +public protocol HeaderPanelContainerChildView: UIView { + func setOverlayContainerView(overlayContainerView: UIView) +} + public final class HeaderPanelContainerComponent: Component { public final class Panel: Equatable { public let key: AnyHashable @@ -94,6 +98,7 @@ public final class HeaderPanelContainerComponent: Component { self.backgroundContainer = GlassBackgroundContainerView() self.backgroundView = GlassBackgroundView() self.contentContainer = UIView() + self.contentContainer.clipsToBounds = true super.init(frame: frame) @@ -142,6 +147,9 @@ public final class HeaderPanelContainerComponent: Component { if let tabsComponentView = tabsView.view { if tabsComponentView.superview == nil { self.contentContainer.addSubview(tabsComponentView) + if let tabsComponentView = tabsComponentView as? HeaderPanelContainerChildView { + tabsComponentView.setOverlayContainerView(overlayContainerView: self.backgroundContainer.contentView) + } transition.animateAlpha(view: tabsComponentView, from: 0.0, to: 1.0) } tabsTransition.setFrame(view: tabsComponentView, frame: tabsFrame) diff --git a/submodules/TelegramUI/Components/ChatList/ChatListTabsComponent/BUILD b/submodules/TelegramUI/Components/HorizontalTabsComponent/BUILD similarity index 76% rename from submodules/TelegramUI/Components/ChatList/ChatListTabsComponent/BUILD rename to submodules/TelegramUI/Components/HorizontalTabsComponent/BUILD index cb37ab01b2..7f31dd2dcd 100644 --- a/submodules/TelegramUI/Components/ChatList/ChatListTabsComponent/BUILD +++ b/submodules/TelegramUI/Components/HorizontalTabsComponent/BUILD @@ -1,8 +1,8 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") swift_library( - name = "ChatListTabsComponent", - module_name = "ChatListTabsComponent", + name = "HorizontalTabsComponent", + module_name = "HorizontalTabsComponent", srcs = glob([ "Sources/**/*.swift", ]), @@ -20,7 +20,8 @@ swift_library( "//submodules/Components/ComponentDisplayAdapters", "//submodules/Components/MultilineTextWithEntitiesComponent", "//submodules/TelegramUI/Components/LiquidLens", - "//submodules/TelegramUI/Components/TextBadgeComponent" + "//submodules/TelegramUI/Components/TextBadgeComponent", + "//submodules/TelegramUI/Components/HeaderPanelContainerComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/HorizontalTabsComponent/Sources/HorizontalTabsComponent.swift b/submodules/TelegramUI/Components/HorizontalTabsComponent/Sources/HorizontalTabsComponent.swift new file mode 100644 index 0000000000..79b06f9e79 --- /dev/null +++ b/submodules/TelegramUI/Components/HorizontalTabsComponent/Sources/HorizontalTabsComponent.swift @@ -0,0 +1,1194 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramPresentationData +import AccountContext +import TelegramCore +import MultilineTextWithEntitiesComponent +import TextBadgeComponent +import LiquidLens +import HeaderPanelContainerComponent + +private class ReorderingGestureRecognizerTimerTarget: NSObject { + private let f: () -> Void + + init(_ f: @escaping () -> Void) { + self.f = f + + super.init() + } + + @objc func timerEvent() { + self.f() + } +} + +private final class InternalGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate { + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if otherGestureRecognizer is UIPanGestureRecognizer { + return true + } else { + return false + } + } +} + +private final class ReorderingGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate { + private let internalDelegate = InternalGestureRecognizerDelegate() + + private let shouldBegin: (CGPoint) -> Bool + private let began: (CGPoint) -> Void + private let ended: () -> Void + private let moved: (CGFloat) -> Void + + private var initialLocation: CGPoint? + private var delayTimer: Foundation.Timer? + + var currentLocation: CGPoint? + + init(shouldBegin: @escaping (CGPoint) -> Bool, began: @escaping (CGPoint) -> Void, ended: @escaping () -> Void, moved: @escaping (CGFloat) -> Void) { + self.shouldBegin = shouldBegin + self.began = began + self.ended = ended + self.moved = moved + + super.init(target: nil, action: nil) + + self.delegate = self.internalDelegate + } + + override func reset() { + super.reset() + + self.initialLocation = nil + self.delayTimer?.invalidate() + self.delayTimer = nil + self.currentLocation = nil + } + + override func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) + + guard let location = touches.first?.location(in: self.view) else { + self.state = .failed + return + } + + if self.state == .possible { + if self.delayTimer == nil { + if !self.shouldBegin(location) { + self.state = .failed + return + } + self.initialLocation = location + let timer = Foundation.Timer(timeInterval: 0.2, target: ReorderingGestureRecognizerTimerTarget { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.delayTimer = nil + strongSelf.state = .began + strongSelf.began(location) + }, selector: #selector(ReorderingGestureRecognizerTimerTarget.timerEvent), userInfo: nil, repeats: false) + self.delayTimer = timer + RunLoop.main.add(timer, forMode: .common) + } else { + self.state = .failed + } + } + } + + override func touchesEnded(_ touches: Set, with event: UIEvent) { + super.touchesEnded(touches, with: event) + + self.delayTimer?.invalidate() + + if self.state == .began || self.state == .changed { + self.ended() + } + + self.state = .failed + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent) { + super.touchesCancelled(touches, with: event) + + if self.state == .began || self.state == .changed { + self.delayTimer?.invalidate() + self.ended() + self.state = .failed + } + } + + override func touchesMoved(_ touches: Set, with event: UIEvent) { + super.touchesMoved(touches, with: event) + + guard let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) else { + return + } + let offset = location.x - initialLocation.x + self.currentLocation = location + + if self.delayTimer != nil { + if abs(offset) > 4.0 { + self.delayTimer?.invalidate() + self.state = .failed + return + } + } else { + if self.state == .began || self.state == .changed { + self.state = .changed + self.moved(offset) + } + } + } +} + + +public final class HorizontalTabsComponent: Component { + public final class Tab: Equatable { + public typealias Id = AnyHashable + + public struct Badge: Equatable { + public var title: String + public var isAccent: Bool + + public init(title: String, isAccent: Bool) { + self.title = title + self.isAccent = isAccent + } + } + + public struct Title: Equatable { + public let text: String + public let entities: [MessageTextEntity] + public let enableAnimations: Bool + + public init(text: String, entities: [MessageTextEntity], enableAnimations: Bool) { + self.text = text + self.entities = entities + self.enableAnimations = enableAnimations + } + } + + public enum Content: Equatable { + case title(Title) + case custom(AnyComponent) + } + + public let id: AnyHashable + public let content: Content + public let badge: Badge? + public let action: () -> Void + public let contextAction: ((ContextExtractedContentContainingView, ContextGesture?) -> Void)? + public let deleteAction: (() -> Void)? + + public init(id: AnyHashable, content: Content, badge: Badge?, action: @escaping () -> Void, contextAction: ((ContextExtractedContentContainingView, ContextGesture?) -> Void)?, deleteAction: (() -> Void)?) { + self.id = id + self.content = content + self.badge = badge + self.action = action + self.contextAction = contextAction + self.deleteAction = deleteAction + } + + public static func ==(lhs: Tab, rhs: Tab) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.content != rhs.content { + return false + } + if lhs.badge != rhs.badge { + return false + } + if (lhs.contextAction == nil) != (rhs.contextAction == nil) { + return false + } + if (lhs.deleteAction == nil) != (rhs.deleteAction == nil) { + return false + } + return true + } + } + + public enum Layout { + case fit + case fill + } + + public let context: AccountContext? + public let theme: PresentationTheme + public let tabs: [Tab] + public let selectedTab: Tab.Id? + public let isEditing: Bool + public let layout: Layout + + public init( + context: AccountContext?, + theme: PresentationTheme, + tabs: [Tab], + selectedTab: Tab.Id?, + isEditing: Bool, + layout: Layout = .fill + ) { + self.context = context + self.theme = theme + self.tabs = tabs + self.selectedTab = selectedTab + self.isEditing = isEditing + self.layout = layout + } + + public static func ==(lhs: HorizontalTabsComponent, rhs: HorizontalTabsComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.tabs != rhs.tabs { + return false + } + if lhs.selectedTab != rhs.selectedTab { + return false + } + if lhs.isEditing != rhs.isEditing { + return false + } + if lhs.layout != rhs.layout { + return false + } + return true + } + + private final class ScrollView: UIScrollView { + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return super.hitTest(point, with: event) + } + + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + } + + private struct LayoutData { + var size: CGSize + var selectedItemFrame: CGRect + + init(size: CGSize, selectedItemFrame: CGRect) { + self.size = size + self.selectedItemFrame = selectedItemFrame + } + } + + private final class ItemView { + let regularView = ComponentView() + let selectedView = ComponentView() + + init() { + } + } + + public final class View: UIView, UIScrollViewDelegate, HeaderPanelContainerChildView { + private let lensView: LiquidLensView + private let scrollView: ScrollView + private let selectedScrollView: UIView + private var itemViews: [Tab.Id: ItemView] = [:] + + private var ignoreScrolling: Bool = false + private var tabSwitchFraction: CGFloat = 0.0 + private var isDraggingTabs: Bool = false + private var temporaryLiftTimer: Foundation.Timer? + + private var tapRecognizer: UITapGestureRecognizer? + + private var reorderingGesture: ReorderingGestureRecognizer? + private var reorderingItem: AnyHashable? + private var reorderingItemPosition: (initial: CGFloat, offset: CGFloat)? + private var reorderingAutoScrollAnimator: ConstantDisplayLinkAnimator? + private var initialReorderedItemIds: [AnyHashable]? + private var reorderedItemIds: [AnyHashable]? + + private var layoutData: LayoutData? + + private var component: HorizontalTabsComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + self.lensView = LiquidLensView(kind: .noContainer) + self.scrollView = ScrollView() + + self.selectedScrollView = UIView() + self.selectedScrollView.clipsToBounds = true + + super.init(frame: frame) + + self.scrollView.delaysContentTouches = false + self.scrollView.canCancelContentTouches = true + self.scrollView.contentInsetAdjustmentBehavior = .never + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.alwaysBounceHorizontal = false + self.scrollView.alwaysBounceVertical = false + self.scrollView.scrollsToTop = false + self.scrollView.clipsToBounds = true + self.scrollView.delegate = self + + self.scrollView.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in + guard let self else { + return false + } + return self.scrollView.contentOffset.x > .ulpOfOne + } + + self.addSubview(self.lensView) + + self.lensView.contentView.addSubview(self.scrollView) + self.lensView.selectedContentView.addSubview(self.selectedScrollView) + /*self.lensView.onUpdatedIsAnimating = { [weak self] _ in + guard let self else { + return + } + self.alpha = self.lensView.isAnimating ? 1.0 : 0.7 + }*/ + + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))) + self.tapRecognizer = tapRecognizer + self.addGestureRecognizer(tapRecognizer) + + let reorderingGesture = ReorderingGestureRecognizer(shouldBegin: { [weak self] point in + guard let self else { + return false + } + for (_, itemView) in self.itemViews { + guard let itemView = itemView.regularView.view else { + continue + } + if itemView.convert(itemView.bounds, to: self).contains(point) { + return true + } + } + return false + }, began: { [weak self] point in + guard let self else { + return + } + self.initialReorderedItemIds = self.reorderedItemIds + for (id, itemView) in self.itemViews { + guard let regularItemView = itemView.regularView.view, let selectedItemView = itemView.selectedView.view else { + continue + } + let itemFrame = regularItemView.convert(regularItemView.bounds, to: self) + if itemFrame.contains(point) { + HapticFeedback().impact() + + self.reorderingItem = id + regularItemView.frame = itemFrame + selectedItemView.frame = itemFrame + + self.reorderingAutoScrollAnimator = ConstantDisplayLinkAnimator(update: { [weak self] in + guard let self, let currentLocation = self.reorderingGesture?.currentLocation else { + return + } + let edgeWidth: CGFloat = 20.0 + if currentLocation.x <= edgeWidth { + var contentOffset = self.scrollView.contentOffset + contentOffset.x = max(0.0, contentOffset.x - 3.0) + self.scrollView.setContentOffset(contentOffset, animated: false) + } else if currentLocation.x >= self.bounds.width - edgeWidth { + var contentOffset = self.scrollView.contentOffset + contentOffset.x = max(0.0, min(self.scrollView.contentSize.width - self.scrollView.bounds.width, contentOffset.x + 3.0)) + self.scrollView.setContentOffset(contentOffset, animated: false) + } + }) + self.reorderingAutoScrollAnimator?.isPaused = false + self.addSubview(regularItemView) + self.addSubview(selectedItemView) + + self.reorderingItemPosition = (regularItemView.frame.minX, 0.0) + self.state?.updated(transition: .easeInOut(duration: 0.25)) + + return + } + } + }, ended: { [weak self] in + guard let self, let reorderingItem = self.reorderingItem else { + return + } + + if let itemView = self.itemViews[reorderingItem], let regularItemView = itemView.regularView.view, let selectedItemView = itemView.selectedView.view { + let projectedItemFrame = regularItemView.convert(regularItemView.bounds, to: self.scrollView) + regularItemView.frame = projectedItemFrame + selectedItemView.frame = projectedItemFrame + self.scrollView.addSubview(regularItemView) + self.selectedScrollView.addSubview(selectedItemView) + } + + /*if strongSelf.currentParams?.canReorderAllChats == false, let firstItem = strongSelf.reorderedItemIds?.first, case .filter = firstItem { + strongSelf.reorderedItemIds = strongSelf.initialReorderedItemIds + strongSelf.presentPremiumTip?() + }*/ + + self.reorderingItem = nil + self.reorderingItemPosition = nil + self.reorderingAutoScrollAnimator?.invalidate() + self.reorderingAutoScrollAnimator = nil + + self.state?.updated(transition: .easeInOut(duration: 0.25)) + }, moved: { [weak self] offset in + guard let self, let reorderingItem = self.reorderingItem else { + return + } + + let minIndex = 0 + if let reorderingItemView = self.itemViews[reorderingItem], let regularItemView = reorderingItemView.regularView.view, let _ = reorderingItemView.selectedView.view, let (initial, _) = self.reorderingItemPosition, let reorderedItemIds = self.reorderedItemIds, let currentItemIndex = reorderedItemIds.firstIndex(of: reorderingItem) { + + for (id, otherItemView) in self.itemViews { + guard let itemIndex = reorderedItemIds.firstIndex(of: id) else { + continue + } + guard let otherRegularItemView = otherItemView.regularView.view else { + continue + } + if id != reorderingItem { + let itemFrame = otherRegularItemView.convert(otherRegularItemView.bounds, to: self) + if regularItemView.frame.intersects(itemFrame) { + let targetIndex: Int + if regularItemView.frame.midX < itemFrame.midX { + targetIndex = max(minIndex, itemIndex - 1) + } else { + targetIndex = max(minIndex, min(reorderedItemIds.count - 1, itemIndex)) + } + if targetIndex != currentItemIndex { + HapticFeedback().tap() + + var updatedReorderedItemIds = reorderedItemIds + if targetIndex > currentItemIndex { + updatedReorderedItemIds.insert(reorderingItem, at: targetIndex + 1) + updatedReorderedItemIds.remove(at: currentItemIndex) + } else { + updatedReorderedItemIds.remove(at: currentItemIndex) + updatedReorderedItemIds.insert(reorderingItem, at: targetIndex) + } + self.reorderedItemIds = updatedReorderedItemIds + + self.state?.updated(transition: .easeInOut(duration: 0.25)) + } + break + } + } + } + + self.reorderingItemPosition = (initial, offset) + } + + self.state?.updated(transition: .immediate) + }) + self.reorderingGesture = reorderingGesture + self.addGestureRecognizer(reorderingGesture) + reorderingGesture.isEnabled = false + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func setOverlayContainerView(overlayContainerView: UIView) { + self.lensView.setLiftedContainer(view: overlayContainerView) + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return self.scrollView.hitTest(self.convert(point, to: self.scrollView), with: event) + } + + @objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) { + guard let component = self.component else { + return + } + if case .ended = recognizer.state { + let point = recognizer.location(in: self) + for (id, itemView) in self.itemViews { + guard let itemView = itemView.regularView.view else { + continue + } + if itemView.convert(itemView.bounds, to: self).contains(point) { + if let tab = component.tabs.first(where: { $0.id == id }) { + tab.action() + } + } + } + } + } + + public func updateTabSwitchFraction(fraction: CGFloat, isDragging: Bool, transition: ComponentTransition) { + self.tabSwitchFraction = -fraction + self.isDraggingTabs = isDragging + self.state?.updated(transition: transition, isLocal: true) + + /*if self.isDraggingTabs != isDragging { + self.isDraggingTabs = isDragging + + if !isDragging { + self.temporaryLiftTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false, block: { [weak self] timer in + guard let self else { + return + } + if self.temporaryLiftTimer === timer { + self.temporaryLiftTimer = nil + self.state?.updated(transition: .spring(duration: 0.5)) + } + }) + } else { + self.state?.updated(transition: .spring(duration: 0.4), isLocal: true) + } + }*/ + } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + if self.ignoreScrolling { + return + } + self.updateScrolling(transition: .immediate) + } + + private func updateScrolling(transition: ComponentTransition) { + guard let component = self.component, let layoutData = self.layoutData else { + return + } + self.lensView.update(size: CGSize(width: layoutData.size.width - 3.0 * 2.0, height: layoutData.size.height - 3.0 * 2.0), selectionX: -self.scrollView.contentOffset.x + layoutData.selectedItemFrame.minX, selectionWidth: layoutData.selectedItemFrame.width + 6.0, inset: 0.0, isDark: component.theme.overallDarkAppearance, isLifted: self.temporaryLiftTimer != nil, transition: transition) + + transition.setPosition(view: self.selectedScrollView, position: CGRect(origin: CGPoint(x: 3.0, y: 0.0), size: CGSize(width: layoutData.size.width - 3.0 * 2.0, height: layoutData.size.height - 3.0 * 2.0)).center) + transition.setBounds(view: self.selectedScrollView, bounds: CGRect(origin: CGPoint(x: self.scrollView.contentOffset.x, y: 0.0), size: CGSize(width: layoutData.size.width - 3.0 * 2.0, height: layoutData.size.height - 3.0 * 2.0))) + } + + func update(component: HorizontalTabsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + var shouldFocusOnSelectedTab = self.isDraggingTabs + + if component.isEditing { + if self.reorderedItemIds == nil { + self.reorderedItemIds = component.tabs.map(\.id) + } + } else { + self.reorderedItemIds = nil + } + + if self.component?.selectedTab != component.selectedTab { + self.tabSwitchFraction = 0.0 + if !self.isDraggingTabs { + self.temporaryLiftTimer?.invalidate() + self.temporaryLiftTimer = nil + + if !transition.animation.isImmediate { + self.temporaryLiftTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.3, repeats: false, block: { [weak self] timer in + guard let self else { + return + } + if self.temporaryLiftTimer === timer { + self.temporaryLiftTimer = nil + self.state?.updated(transition: .spring(duration: 0.5)) + } + }) + } + } + shouldFocusOnSelectedTab = true + } + + self.component = component + self.state = state + + self.reorderingGesture?.isEnabled = component.isEditing + + let sizeHeight: CGFloat = 40.0 + + let sideInset: CGFloat = 0.0 + + var contentWidth: CGFloat = sideInset + + var validIds: [Tab.Id] = [] + + var orderedTabs = component.tabs + if let reorderedItemIds = self.reorderedItemIds { + orderedTabs.removeAll() + for id in reorderedItemIds { + if let item = component.tabs.first(where: { $0.id == id }) { + orderedTabs.append(item) + } + } + for tab in component.tabs { + if !orderedTabs.contains(where: { $0.id == tab.id }) { + orderedTabs.append(tab) + } + } + } + + for tab in orderedTabs { + let tabId = tab.id + validIds.append(tabId) + + var itemTransition = transition + let itemView: ItemView + if let current = self.itemViews[tabId] { + itemView = current + } else { + itemTransition = itemTransition.withAnimation(.none) + itemView = ItemView() + self.itemViews[tabId] = itemView + } + + var itemEditing: ItemComponent.Editing? + if component.isEditing { + itemEditing = ItemComponent.Editing(isEditable: true) + } + + let itemSize = itemView.regularView.update( + transition: itemTransition, + component: AnyComponent(ItemComponent( + context: component.context, + theme: component.theme, + tab: tab, + isSelected: false, + editing: itemEditing + )), + environment: {}, + containerSize: CGSize(width: 1000.0, height: sizeHeight - 3.0 * 2.0) + ) + let _ = itemView.selectedView.update( + transition: itemTransition, + component: AnyComponent(ItemComponent( + context: component.context, + theme: component.theme, + tab: tab, + isSelected: true, + editing: itemEditing + )), + environment: {}, + containerSize: CGSize(width: 1000.0, height: sizeHeight - 3.0 * 2.0) + ) + + var itemFrame = CGRect(origin: CGPoint(x: contentWidth, y: 0.0), size: itemSize) + if tabId == self.reorderingItem, let (initial, offset) = self.reorderingItemPosition { + itemFrame.origin = CGPoint(x: initial + offset, y: 3.0 + itemFrame.minY) + } + + if let itemRegularView = itemView.regularView.view, let itemSelectedView = itemView.selectedView.view { + if itemRegularView.superview == nil { + self.scrollView.addSubview(itemRegularView) + self.selectedScrollView.addSubview(itemSelectedView) + + transition.animateAlpha(view: itemRegularView, from: 0.0, to: 1.0) + transition.animateScale(view: itemRegularView, from: 0.001, to: 1.0) + + transition.animateAlpha(view: itemSelectedView, from: 0.0, to: 1.0) + transition.animateScale(view: itemSelectedView, from: 0.001, to: 1.0) + } + itemTransition.setFrame(view: itemRegularView, frame: itemFrame) + itemTransition.setFrame(view: itemSelectedView, frame: itemFrame) + + if tabId == self.reorderingItem { + itemTransition.setSublayerTransform(view: itemRegularView, transform: CATransform3DMakeScale(1.2, 1.2, 1.0)) + itemTransition.setSublayerTransform(view: itemSelectedView, transform: CATransform3DMakeScale(1.2, 1.2, 1.0)) + itemTransition.setAlpha(view: itemRegularView, alpha: 0.9) + itemTransition.setAlpha(view: itemSelectedView, alpha: 0.0) + } else { + itemTransition.setSublayerTransform(view: itemRegularView, transform: CATransform3DIdentity) + itemTransition.setSublayerTransform(view: itemSelectedView, transform: CATransform3DIdentity) + itemTransition.setAlpha(view: itemRegularView, alpha: 1.0) + itemTransition.setAlpha(view: itemSelectedView, alpha: 1.0) + } + } + + contentWidth += itemSize.width + } + contentWidth += sideInset + + var removedIds: [Tab.Id] = [] + for (id, itemView) in self.itemViews { + if !validIds.contains(id) { + removedIds.append(id) + if let itemRegularView = itemView.regularView.view, let itemSelectedView = itemView.selectedView.view { + transition.setScale(view: itemRegularView, scale: 0.001) + transition.setAlpha(view: itemRegularView, alpha: 0.0, completion: { [weak itemRegularView] _ in + itemRegularView?.removeFromSuperview() + }) + transition.setScale(view: itemSelectedView, scale: 0.001) + transition.setAlpha(view: itemSelectedView, alpha: 0.0, completion: { [weak itemSelectedView] _ in + itemSelectedView?.removeFromSuperview() + }) + } + } + } + for id in removedIds { + self.itemViews.removeValue(forKey: id) + } + + var selectedItemFrame: CGRect? + if let selectedTab = component.selectedTab { + for i in 0 ..< component.tabs.count { + if component.tabs[i].id == selectedTab { + if let itemView = self.itemViews[component.tabs[i].id]?.regularView.view { + var selectedItemFrameValue = itemView.frame + if selectedTab == self.reorderingItem { + selectedItemFrameValue = itemView.convert(itemView.bounds, to: self.scrollView) + } + + var pendingItemFrame: CGRect? + if self.tabSwitchFraction != 0.0 { + if self.tabSwitchFraction > 0.0 && i != component.tabs.count - 1 { + if let nextItemView = self.itemViews[component.tabs[i + 1].id]?.regularView.view { + pendingItemFrame = nextItemView.frame + } + } else if self.tabSwitchFraction < 0.0 && i != 0 { + if let previousItemView = self.itemViews[component.tabs[i - 1].id]?.regularView.view { + pendingItemFrame = previousItemView.frame + } + } + } + if let pendingItemFrame { + let fraction = abs(self.tabSwitchFraction) + selectedItemFrameValue.origin.x = selectedItemFrameValue.minX * (1.0 - fraction) + pendingItemFrame.minX * fraction + selectedItemFrameValue.size.width = selectedItemFrameValue.width * (1.0 - fraction) + pendingItemFrame.width * fraction + } + + selectedItemFrame = selectedItemFrameValue + } + break + } + } + } + + let contentSize = CGSize(width: contentWidth, height: sizeHeight - 3.0 * 2.0) + + let sizeWidth: CGFloat + switch component.layout { + case .fill: + sizeWidth = availableSize.width + case .fit: + sizeWidth = min(availableSize.width, contentWidth + 3.0 * 2.0) + } + + let size = CGSize(width: sizeWidth, height: sizeHeight) + + self.layoutData = LayoutData( + size: size, + selectedItemFrame: selectedItemFrame ?? CGRect() + ) + + self.ignoreScrolling = true + let scrollViewFrame = CGRect(origin: CGPoint(x: 3.0, y: 0.0), size: CGSize(width: size.width - 3.0 * 2.0, height: size.height - 3.0 * 2.0)) + transition.setPosition(view: self.scrollView, position: scrollViewFrame.center) + if self.scrollView.contentSize != contentSize { + self.scrollView.contentSize = contentSize + } + + var scrollViewBounds = CGRect(origin: self.scrollView.bounds.origin, size: scrollViewFrame.size) + if shouldFocusOnSelectedTab || self.scrollView.bounds.size != scrollViewBounds.size { + if shouldFocusOnSelectedTab, let selectedItemFrame { + if scrollViewBounds.minX + scrollViewBounds.width < selectedItemFrame.maxX { + scrollViewBounds.origin.x = selectedItemFrame.maxX - scrollViewBounds.width + } + if scrollViewBounds.minX > selectedItemFrame.minX { + scrollViewBounds.origin.x = selectedItemFrame.minX + } + transition.setBounds(view: self.scrollView, bounds: scrollViewBounds) + } + } + + self.scrollView.layer.cornerRadius = (size.height - 3.0 * 2.0) * 0.5 + self.selectedScrollView.layer.cornerRadius = (size.height - 3.0 * 2.0) * 0.5 + + transition.setFrame(view: self.lensView, frame: CGRect(origin: CGPoint(x: 3.0, y: 3.0), size: CGSize(width: size.width - 3.0 * 2.0, height: size.height - 3.0 * 2.0))) + self.lensView.clipsToBounds = true + self.lensView.layer.cornerRadius = (size.height - 3.0 * 2.0) * 0.5 + self.ignoreScrolling = false + + self.updateScrolling(transition: transition) + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class ItemComponent: Component { + struct Editing: Equatable { + var isEditable: Bool + + init(isEditable: Bool) { + self.isEditable = isEditable + } + } + + let context: AccountContext? + let theme: PresentationTheme + let tab: HorizontalTabsComponent.Tab + let isSelected: Bool + let editing: Editing? + + init(context: AccountContext?, theme: PresentationTheme, tab: HorizontalTabsComponent.Tab, isSelected: Bool, editing: Editing?) { + self.context = context + self.theme = theme + self.tab = tab + self.isSelected = isSelected + self.editing = editing + } + + static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.tab != rhs.tab { + return false + } + if lhs.isSelected != rhs.isSelected { + return false + } + if lhs.editing != rhs.editing { + return false + } + return true + } + + final class View: UIView { + let extractedContainerView: ContextExtractedContentContainingView + let containerView: ContextControllerSourceView + + var titleContent: ComponentView? + var customContent: ComponentView? + var badge: ComponentView? + var deleteIcon: (button: HighlightTrackingButton, icon: UIImageView)? + + var tapRecognizer: UITapGestureRecognizer? + + var component: ItemComponent? + + override init(frame: CGRect) { + self.extractedContainerView = ContextExtractedContentContainingView() + self.containerView = ContextControllerSourceView() + + super.init(frame: frame) + + //self.extractedContainerView.contentView.addSubview(self.extractedBackgroundNode) + + self.containerView.addSubview(self.extractedContainerView) + self.containerView.targetViewForActivationProgress = self.extractedContainerView.contentView + self.addSubview(self.containerView) + + self.containerView.activated = { [weak self] gesture, _ in + guard let self, let component = self.component else { + return + } + component.tab.contextAction?(self.extractedContainerView, gesture) + } + + self.extractedContainerView.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in + guard let self, let component else { + return + } + let _ = component + + /*if isExtracted, let theme = strongSelf.theme { + strongSelf.extractedBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: theme.contextMenu.backgroundColor) + } + transition.updateAlpha(node: strongSelf.extractedBackgroundNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in + if !isExtracted { + self?.extractedBackgroundNode.image = nil + } + })*/ + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func updateIsShaking(animated: Bool) { + guard let component = self.component else { + return + } + + if component.editing != nil { + if self.layer.animation(forKey: "shaking_position") == nil { + let degreesToRadians: (_ x: CGFloat) -> CGFloat = { x in + return .pi * x / 180.0 + } + + let duration: Double = 0.4 + let displacement: CGFloat = 1.0 + let degreesRotation: CGFloat = 2.0 + + let negativeDisplacement = -1.0 * displacement + let position = CAKeyframeAnimation.init(keyPath: "position") + position.beginTime = 0.8 + position.duration = duration + position.values = [ + NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)), + NSValue(cgPoint: CGPoint(x: 0, y: 0)), + NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: 0)), + NSValue(cgPoint: CGPoint(x: 0, y: negativeDisplacement)), + NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)) + ] + position.calculationMode = .linear + position.isRemovedOnCompletion = false + position.repeatCount = Float.greatestFiniteMagnitude + position.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100)) + position.isAdditive = true + + let transform = CAKeyframeAnimation.init(keyPath: "transform") + transform.beginTime = 2.6 + transform.duration = 0.3 + transform.valueFunction = CAValueFunction(name: CAValueFunctionName.rotateZ) + transform.values = [ + degreesToRadians(-1.0 * degreesRotation), + degreesToRadians(degreesRotation), + degreesToRadians(-1.0 * degreesRotation) + ] + transform.calculationMode = .linear + transform.isRemovedOnCompletion = false + transform.repeatCount = Float.greatestFiniteMagnitude + transform.isAdditive = true + transform.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100)) + + self.layer.add(position, forKey: "shaking_position") + self.layer.add(transform, forKey: "shaking_rotation") + } + } else if self.layer.animation(forKey: "shaking_position") != nil { + if let presentationLayer = self.layer.presentation() { + let transition: ComponentTransition = .easeInOut(duration: 0.1) + if presentationLayer.position != self.layer.position { + transition.animatePosition(layer: self.layer, from: CGPoint(x: presentationLayer.position.x - self.layer.position.x, y: presentationLayer.position.y - self.layer.position.y), to: CGPoint(), additive: true) + } + if !CATransform3DIsIdentity(presentationLayer.transform) { + transition.setTransform(layer: self.layer, transform: CATransform3DIdentity) + } + } + + self.layer.removeAnimation(forKey: "shaking_position") + self.layer.removeAnimation(forKey: "shaking_rotation") + } + } + + @objc private func deleteButtonPressed() { + self.component?.tab.deleteAction?() + } + + func update(component: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + self.containerView.isGestureEnabled = component.editing == nil + self.tapRecognizer?.isEnabled = component.editing == nil + + let sideInset: CGFloat = 16.0 + let badgeSpacing: CGFloat = 5.0 + + var size = CGSize(width: sideInset, height: availableSize.height) + + var titleContentSize: CGSize? + if case let .title(title) = component.tab.content { + let titleContent: ComponentView + if let current = self.titleContent { + titleContent = current + } else { + titleContent = ComponentView() + self.titleContent = titleContent + } + + let font = Font.medium(15.0) + + let rawAttributedString = ChatTextInputStateText(text: title.text, attributes: title.entities.compactMap { entity -> ChatTextInputStateTextAttribute? in + if case let .CustomEmoji(_, fileId) = entity.type { + return ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: fileId, enableAnimation: title.enableAnimations), range: entity.range) + } + return nil + }).attributedText() + + let titleString = NSMutableAttributedString(attributedString: rawAttributedString) + titleString.addAttributes([ + .font: font, + .foregroundColor: component.theme.chat.inputPanel.panelControlColor + ], range: NSRange(location: 0, length: titleString.length)) + + titleContentSize = titleContent.update( + transition: .immediate, + component: AnyComponent(MultilineTextWithEntitiesComponent( + context: component.context, + animationCache: component.context?.animationCache, + animationRenderer: component.context?.animationRenderer, + placeholderColor: component.theme.chat.inputPanel.panelControlColor.withMultipliedAlpha(0.1), + text: .plain(titleString), + displaysAsynchronously: false + )), + environment: {}, + containerSize: CGSize(width: 300.0, height: 100.0) + ) + } else if let titleContent = self.titleContent { + self.titleContent = nil + titleContent.view?.removeFromSuperview() + } + + var customContentSize: CGSize? + if case let .custom(custom) = component.tab.content { + let customContent: ComponentView + if let current = self.customContent { + customContent = current + } else { + customContent = ComponentView() + self.customContent = customContent + } + + customContentSize = customContent.update( + transition: transition, + component: custom, + environment: {}, + containerSize: CGSize(width: 300.0, height: 100.0) + ) + } else if let customContent = self.customContent { + self.customContent = nil + customContent.view?.removeFromSuperview() + } + + if let titleContentSize { + size.width += titleContentSize.width + } + if let customContentSize { + size.width += customContentSize.width + } + + if let badgeData = component.tab.badge, component.tab.deleteAction == nil { + let badge: ComponentView + var badgeTransition = transition + if let current = self.badge { + badge = current + } else { + badgeTransition = badgeTransition.withAnimation(.none) + badge = ComponentView() + self.badge = badge + } + let badgeSize = badge.update( + transition: badgeTransition, + component: AnyComponent(TextBadgeComponent( + text: badgeData.title, + font: Font.medium(12.0), + background: badgeData.isAccent ? component.theme.list.itemCheckColors.fillColor : component.theme.chatList.unreadBadgeInactiveBackgroundColor, + foreground: component.theme.list.itemCheckColors.foregroundColor, + insets: UIEdgeInsets(top: 1.0, left: 5.0, bottom: 2.0, right: 5.0) + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + size.width += badgeSpacing + let badgeFrame = CGRect(origin: CGPoint(x: size.width, y: floorToScreenPixels((size.height - badgeSize.height) * 0.5)), size: badgeSize) + if let badgeView = badge.view { + if badgeView.superview == nil { + self.extractedContainerView.contentView.addSubview(badgeView) + transition.animateAlpha(view: badgeView, from: 0.0, to: 1.0) + transition.animateScale(view: badgeView, from: 0.001, to: 1.0) + } + badgeTransition.setFrame(view: badgeView, frame: badgeFrame) + } + size.width += badgeSize.width - 2.0 + } else if let badge = self.badge { + self.badge = nil + if let badgeView = badge.view { + transition.setFrame(view: badgeView, frame: badgeView.bounds.size.centered(around: CGPoint(x: size.width + sideInset - badgeView.bounds.width * 0.5, y: size.height * 0.5))) + transition.setScale(view: badgeView, scale: 0.001) + transition.setAlpha(view: badgeView, alpha: 0.0, completion: { [weak badgeView] _ in + badgeView?.removeFromSuperview() + }) + } + } + + if component.tab.deleteAction != nil { + let deleteIcon: (button: HighlightTrackingButton, icon: UIImageView) + if let current = self.deleteIcon { + deleteIcon = current + } else { + deleteIcon = (HighlightTrackingButton(), UIImageView()) + self.deleteIcon = deleteIcon + deleteIcon.button.addSubview(deleteIcon.icon) + deleteIcon.icon.image = generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(UIColor.white.cgColor) + context.setLineWidth(1.33) + context.setLineCap(.round) + context.move(to: CGPoint(x: 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0)) + context.strokePath() + context.move(to: CGPoint(x: size.width - 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0)) + context.strokePath() + })?.withRenderingMode(.alwaysTemplate) + deleteIcon.button.addTarget(self, action: #selector(self.deleteButtonPressed), for: .touchUpInside) + } + deleteIcon.icon.tintColor = component.theme.chat.inputPanel.panelControlColor + if let image = deleteIcon.icon.image { + let deleteButtonFrame = CGRect(origin: CGPoint(x: size.width + 2.0, y: 0.0), size: CGSize(width: image.size.width + 6.0 * 2.0, height: size.height)) + let deleteIconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((deleteButtonFrame.width - image.size.width) * 0.5), y: floorToScreenPixels((deleteButtonFrame.height - image.size.height) * 0.5)), size: image.size) + if deleteIcon.button.superview == nil { + self.addSubview(deleteIcon.button) + deleteIcon.button.frame = deleteButtonFrame + deleteIcon.icon.frame = deleteIconFrame + transition.animateAlpha(view: deleteIcon.button, from: 0.0, to: 1.0) + transition.animateScale(view: deleteIcon.button, from: 0.001, to: 1.0) + } + transition.setFrame(view: deleteIcon.button, frame: deleteButtonFrame) + transition.setFrame(view: deleteIcon.icon, frame: deleteIconFrame) + size.width += deleteButtonFrame.width - 3.0 + } + } else if let deleteIcon = self.deleteIcon { + self.deleteIcon = nil + let (button, _) = deleteIcon + transition.setScale(view: button, scale: 0.001) + transition.setAlpha(view: button, alpha: 0.0, completion: { [weak button] _ in + button?.removeFromSuperview() + }) + } + + size.width += sideInset + + if let titleView = self.titleContent?.view, let titleContentSize { + let titleFrame = CGRect(origin: CGPoint(x: sideInset, y: floorToScreenPixels((size.height - titleContentSize.height) * 0.5)), size: titleContentSize) + if titleView.superview == nil { + titleView.layer.anchorPoint = CGPoint() + self.extractedContainerView.contentView.addSubview(titleView) + } + transition.setPosition(view: titleView, position: titleFrame.origin) + titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) + } + + if let customView = self.customContent?.view, let customContentSize { + let customFrame = CGRect(origin: CGPoint(x: sideInset, y: floorToScreenPixels((size.height - customContentSize.height) * 0.5)), size: customContentSize) + if customView.superview == nil { + customView.layer.anchorPoint = CGPoint() + self.extractedContainerView.contentView.addSubview(customView) + } + transition.setFrame(view: customView, frame: customFrame) + } + + transition.setFrame(view: self.extractedContainerView, frame: CGRect(origin: CGPoint(), size: size)) + transition.setFrame(view: self.extractedContainerView.contentView, frame: CGRect(origin: CGPoint(), size: size)) + + let extractedBackgroundFrame = CGRect(origin: CGPoint(), size: size) + + self.extractedContainerView.contentRect = CGRect(origin: CGPoint(x: extractedBackgroundFrame.minX, y: 0.0), size: CGSize(width: extractedBackgroundFrame.width, height: size.height)) + transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(), size: size)) + + self.updateIsShaking(animated: !transition.animation.isImmediate) + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/LiquidLens/Sources/LiquidLensView.swift b/submodules/TelegramUI/Components/LiquidLens/Sources/LiquidLensView.swift index 182541cad6..401f0ffee7 100644 --- a/submodules/TelegramUI/Components/LiquidLens/Sources/LiquidLensView.swift +++ b/submodules/TelegramUI/Components/LiquidLens/Sources/LiquidLensView.swift @@ -68,14 +68,16 @@ public final class LiquidLensView: UIView { var size: CGSize var selectionX: CGFloat var selectionWidth: CGFloat + var inset: CGFloat var isDark: Bool var isLifted: Bool var isCollapsed: Bool - init(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, isDark: Bool, isLifted: Bool, isCollapsed: Bool) { + init(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, inset: CGFloat, isDark: Bool, isLifted: Bool, isCollapsed: Bool) { self.size = size self.selectionX = selectionX self.selectionWidth = selectionWidth + self.inset = inset self.isLifted = isLifted self.isDark = isDark self.isCollapsed = isCollapsed @@ -84,10 +86,12 @@ public final class LiquidLensView: UIView { private struct LensParams: Equatable { var baseFrame: CGRect + var inset: CGFloat var isLifted: Bool - init(baseFrame: CGRect, isLifted: Bool) { + init(baseFrame: CGRect, inset: CGFloat, isLifted: Bool) { self.baseFrame = baseFrame + self.inset = inset self.isLifted = isLifted } } @@ -124,6 +128,15 @@ public final class LiquidLensView: UIView { public var selectionWidth: CGFloat? { return self.params?.selectionWidth } + + public private(set) var isAnimating: Bool = false { + didSet { + if self.isAnimating != oldValue { + self.onUpdatedIsAnimating?(self.isAnimating) + } + } + } + public var onUpdatedIsAnimating: ((Bool) -> Void)? public init(kind: Kind) { self.containerView = UIView() @@ -185,10 +198,7 @@ public final class LiquidLensView: UIView { } lensView.layer.zPosition = 10.0 - if case .noContainer = kind { - } else { - self.liftedContainerView.addSubview(self.restingBackgroundView) - } + self.liftedContainerView.addSubview(self.restingBackgroundView) self.containerView.addSubview(self.liftedContainerView) self.containerView.addSubview(lensView) @@ -199,11 +209,8 @@ public final class LiquidLensView: UIView { } else if let genericBackgroundContainer = self.genericBackgroundContainer { lensView.perform(NSSelectorFromString("setLiftedContainerView:"), with: genericBackgroundContainer) } - if case .noContainer = kind { - } else { - lensView.perform(NSSelectorFromString("setLiftedContentView:"), with: self.liftedContainerView) - lensView.perform(NSSelectorFromString("setOverridePunchoutView:"), with: self.contentView) - } + lensView.perform(NSSelectorFromString("setLiftedContentView:"), with: self.liftedContainerView) + lensView.perform(NSSelectorFromString("setOverridePunchoutView:"), with: self.contentView) do { let selector = NSSelectorFromString("setLiftedContentMode:") @@ -271,9 +278,16 @@ public final class LiquidLensView: UIView { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + public func setLiftedContainer(view: UIView) { + guard let lensView = self.lensView else { + return + } + lensView.perform(NSSelectorFromString("setLiftedContainerView:"), with: view) + } - public func update(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, isDark: Bool, isLifted: Bool, isCollapsed: Bool = false, transition: ComponentTransition) { - let params = Params(size: size, selectionX: selectionX, selectionWidth: selectionWidth, isDark: isDark, isLifted: isLifted, isCollapsed: isCollapsed) + public func update(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, inset: CGFloat, isDark: Bool, isLifted: Bool, isCollapsed: Bool = false, transition: ComponentTransition) { + let params = Params(size: size, selectionX: selectionX, selectionWidth: selectionWidth, inset: inset, isDark: isDark, isLifted: isLifted, isCollapsed: isCollapsed) if self.params == params { return } @@ -287,7 +301,7 @@ public final class LiquidLensView: UIView { self.update(params: params, transition: transition) } - private func updateLens(params: LensParams, animated: Bool) { + private func updateLens(params: LensParams, transition: ComponentTransition) { guard let lensView = self.lensView else { return } @@ -298,22 +312,23 @@ public final class LiquidLensView: UIView { } self.isApplyingLensParams = true let previousParams = self.appliedLensParams - - let transition: ComponentTransition = animated ? .easeInOut(duration: 0.3) : .immediate + self.appliedLensParams = params if previousParams?.isLifted != params.isLifted { + self.isAnimating = true + let selector = NSSelectorFromString("setLifted:animated:alongsideAnimations:completion:") var shouldScheduleUpdate = false var didProcessUpdate = false self.pendingLensParams = params if let lensView = self.lensView, let method = lensView.method(for: selector) { - typealias ObjCMethod = @convention(c) (AnyObject, Selector, Bool, Bool, @escaping () -> Void, AnyObject?) -> Void + typealias ObjCMethod = @convention(c) (AnyObject, Selector, Bool, Bool, @escaping () -> Void, (() -> Void)?) -> Void let function = unsafeBitCast(method, to: ObjCMethod.self) function(lensView, selector, params.isLifted, !transition.animation.isImmediate, { [weak self] in guard let self else { return } - let liftedInset: CGFloat = params.isLifted ? 4.0 : -4.0 + let liftedInset: CGFloat = params.isLifted ? 4.0 : -params.inset lensView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: params.baseFrame.width + liftedInset * 2.0, height: params.baseFrame.height + liftedInset * 2.0)) didProcessUpdate = true if shouldScheduleUpdate { @@ -323,10 +338,17 @@ public final class LiquidLensView: UIView { } self.isApplyingLensParams = false self.pendingLensParams = nil - self.updateLens(params: pendingLensParams, animated: !transition.animation.isImmediate) + self.updateLens(params: pendingLensParams, transition: transition) } } - }, nil) + }, { [weak self] in + guard let self else { + return + } + if !self.isApplyingLensParams { + self.isAnimating = false + } + }) } if didProcessUpdate { transition.animateView { @@ -338,11 +360,32 @@ public final class LiquidLensView: UIView { shouldScheduleUpdate = true } } else { + let liftedInset: CGFloat = params.isLifted ? 4.0 : -params.inset + let lensBounds = CGRect(origin: CGPoint(), size: CGSize(width: params.baseFrame.width + liftedInset * 2.0, height: params.baseFrame.height + liftedInset * 2.0)) + let lensCenter = CGPoint(x: params.baseFrame.midX, y: params.baseFrame.midY) + + let previousBounds: CGRect = lensView.bounds transition.animateView { - let liftedInset: CGFloat = params.isLifted ? 4.0 : -4.0 - lensView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: params.baseFrame.width + liftedInset * 2.0, height: params.baseFrame.height + liftedInset * 2.0)) - lensView.center = CGPoint(x: params.baseFrame.midX, y: params.baseFrame.midY) + lensView.bounds = lensBounds } + + lensView.layer.removeAllAnimations() + lensView.bounds = lensBounds + + if !transition.animation.isImmediate { + self.isAnimating = true + } + transition.setPosition(view: lensView, position: lensCenter, completion: { [weak self] flag in + guard let self, flag else { + return + } + if !self.isApplyingLensParams { + self.isAnimating = false + } + }) + // No idea why + transition.animatePosition(layer: lensView.layer, from: CGPoint(x: (lensBounds.width - previousBounds.width) * 0.5, y: 0.0), to: CGPoint(), additive: true) + self.isApplyingLensParams = false } } @@ -402,13 +445,13 @@ public final class LiquidLensView: UIView { } let baseLensFrame = CGRect(origin: CGPoint(x: params.selectionX, y: 0.0), size: CGSize(width: params.selectionWidth, height: params.size.height)) - self.updateLens(params: LensParams(baseFrame: baseLensFrame, isLifted: params.isLifted), animated: !transition.animation.isImmediate) + self.updateLens(params: LensParams(baseFrame: baseLensFrame, inset: params.inset, isLifted: params.isLifted), transition: transition) if let legacyContentMaskView = self.legacyContentMaskView { transition.setFrame(view: legacyContentMaskView, frame: CGRect(origin: CGPoint(), size: params.size)) } if let legacyContentMaskBlobView = self.legacyContentMaskBlobView, let legacyLiftedContentBlobMaskView = self.legacyLiftedContentBlobMaskView, let legacySelectionView = self.legacySelectionView { - let lensFrame = baseLensFrame.insetBy(dx: 4.0, dy: 4.0) + let lensFrame = baseLensFrame.insetBy(dx: params.inset, dy: params.inset) let effectiveLensFrame = lensFrame.insetBy(dx: params.isLifted ? -2.0 : 0.0, dy: params.isLifted ? -2.0 : 0.0) if legacyContentMaskBlobView.image?.size.height != lensFrame.height { diff --git a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift index a1fe774c24..40bdaf5502 100644 --- a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift +++ b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift @@ -20,7 +20,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { private var validLayout: (size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, additionalCutout: CGSize?, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool)? private var requestedLayout: Bool = false - public var requestContainerLayout: (ContainedViewLayoutTransition) -> Void = { _ in } + public var requestContainerLayout: ((ContainedViewLayoutTransition) -> Void)? public var backPressed: () -> () = { } @@ -102,10 +102,45 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { } } - self.titleView = item.titleView - self.itemTitleViewListenerKey = item.addSetTitleViewListener { [weak self] titleView in - if let strongSelf = self { - strongSelf.titleView = titleView + let itemTitleView = item.titleView + if self.titleView !== itemTitleView { + if let oldTitleView = self.titleView as? NavigationBarTitleView { + oldTitleView.requestUpdate = nil + } + self.titleView = itemTitleView + if let titleView = self.titleView as? NavigationBarTitleView { + titleView.requestUpdate = { [weak self, weak titleView] transition in + guard let self, let titleView, self.titleView === titleView else { + return + } + if let requestContainerLayout = self.requestContainerLayout { + requestContainerLayout(transition) + } else { + self.requestLayout() + } + } + } + } + self.itemTitleViewListenerKey = item.addSetTitleViewListener { [weak self] itemTitleView in + guard let self else { + return + } + + if let oldTitleView = self.titleView as? NavigationBarTitleView { + oldTitleView.requestUpdate = nil + } + self.titleView = itemTitleView + if let titleView = self.titleView as? NavigationBarTitleView { + titleView.requestUpdate = { [weak self, weak titleView] transition in + guard let self, let titleView, self.titleView === titleView else { + return + } + if let requestContainerLayout = self.requestContainerLayout { + requestContainerLayout(transition) + } else { + self.requestLayout() + } + } } } @@ -470,8 +505,12 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { self.rightButtonNodeUpdated = true if !items.isEmpty { - self.rightButtonNodeImpl.updateItems([], animated: animated) - self.rightButtonNodeImpl.updateItems(items, animated: animated) + if self.rightButtonNodeImpl.isEmpty { + self.rightButtonNodeImpl.updateItems(items, animated: false) + } else { + self.rightButtonNodeImpl.updateItems([], animated: animated) + self.rightButtonNodeImpl.updateItems(items, animated: animated) + } if self.rightButtonNodeImpl.view.superview == nil { if let rightButtonsBackgroundView = self.rightButtonsBackgroundView { rightButtonsBackgroundView.contentView.addSubview(self.rightButtonNodeImpl.view) @@ -490,6 +529,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { } } self.rightButtonNodeImpl.view.removeFromSuperview() + self.rightButtonNodeImpl.updateItems([], animated: false) } } else { if animated, self.rightButtonNodeImpl.view.superview != nil { @@ -502,6 +542,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { } } self.rightButtonNodeImpl.view.removeFromSuperview() + self.rightButtonNodeImpl.updateItems([], animated: false) } self.updateAccessibilityElements() @@ -548,7 +589,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { } } - public var customOverBackgroundContentSubview: UIView? + public let customOverBackgroundContentView: UIView public init(presentationData: NavigationBarPresentationData) { self.presentationData = presentationData @@ -605,6 +646,8 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { self.secondaryContentHeight = NavigationBarImpl.defaultSecondaryContentHeight + self.customOverBackgroundContentView = SparseContainerView() + super.init() if case .glass = presentationData.theme.style { @@ -616,6 +659,8 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { self.backgroundContainer = backgroundContainer self.view.addSubview(backgroundContainer) + backgroundContainer.contentView.addSubview(self.customOverBackgroundContentView) + let leftButtonsBackgroundView = GlassBackgroundView() self.leftButtonsBackgroundView = leftButtonsBackgroundView backgroundContainer.contentView.addSubview(leftButtonsBackgroundView) @@ -625,6 +670,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { backgroundContainer.contentView.addSubview(rightButtonsBackgroundView) } else { self.addSubnode(self.backgroundNode) + self.view.addSubview(self.customOverBackgroundContentView) } self.addSubnode(self.buttonsContainerNode) self.addSubnode(self.clippingNode) @@ -786,10 +832,13 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { case .replacement: expansionHeight = contentNode.height - defaultHeight if case .glass = self.presentationData.theme.style { - contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: contentVerticalOrigin), size: CGSize(width: size.width, height: defaultHeight)) + contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: contentVerticalOrigin), size: CGSize(width: size.width, height: contentNode.nominalHeight)) } else { contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height - additionalContentHeight)) } + + transition.updateFrame(node: contentNode, frame: contentNodeFrame) + let _ = contentNode.updateLayout(size: contentNodeFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) case .expansion: expansionHeight = contentNode.height @@ -803,22 +852,23 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { contentNodeFrame.origin.y += self.secondaryContentHeight * self.secondaryContentNodeDisplayFraction } } + + transition.updateFrame(node: contentNode, frame: contentNodeFrame) + let _ = contentNode.updateLayout(size: contentNodeFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) } - transition.updateFrame(node: contentNode, frame: contentNodeFrame) - contentNode.updateLayout(size: contentNodeFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) } transition.updateFrame(node: self.stripeNode, frame: CGRect(x: (additionalCutout?.width ?? 0.0), y: size.height + additionalBackgroundHeight, width: size.width - (additionalCutout?.width ?? 0.0), height: UIScreenPixel)) let nominalHeight: CGFloat = 60.0 - var leftTitleInset: CGFloat = leftInset + 1.0 - var rightTitleInset: CGFloat = rightInset + 1.0 + var leftTitleInset: CGFloat = leftInset + var rightTitleInset: CGFloat = rightInset var leftButtonsWidth: CGFloat = 0.0 if self.backButtonNodeImpl.view.superview != nil { let backButtonSize = self.backButtonNodeImpl.updateLayout(constrainedSize: CGSize(width: size.width, height: 44.0), isLandscape: isLandscape, isLeftAligned: true) - leftTitleInset = backButtonSize.width + backButtonInset + 1.0 + leftTitleInset = backButtonSize.width + backButtonInset if case .glass = self.presentationData.theme.style { } else { @@ -892,7 +942,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { if case .glass = self.presentationData.theme.style { transition.updateFrame(node: self.rightButtonNodeImpl, frame: CGRect(origin: CGPoint(x: 0.0, y: floor((44.0 - rightButtonSize.height) / 2.0)), size: rightButtonSize)) } else { - rightTitleInset = rightButtonSize.width + leftButtonInset + 1.0 + rightTitleInset = rightButtonSize.width + leftButtonInset + 4.0 transition.updateFrame(node: self.rightButtonNodeImpl, frame: CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize)) } } @@ -900,7 +950,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { if let leftButtonsBackgroundView = self.leftButtonsBackgroundView { if leftButtonsWidth != 0.0 { - leftTitleInset = leftInset + 16.0 + leftButtonsWidth + 4.0 + leftTitleInset = leftInset + 16.0 + leftButtonsWidth + 10.0 } if self.backButtonNodeImpl.view.superview != nil { @@ -921,7 +971,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { if let rightButtonsBackgroundView = self.rightButtonsBackgroundView { if rightButtonsWidth != 0.0 { - rightTitleInset = rightInset + 16.0 + rightButtonsWidth + 4.0 + rightTitleInset = rightInset + 16.0 + rightButtonsWidth + 10.0 let rightButtonsBackgroundFrame = CGRect(origin: CGPoint(x: size.width - rightInset - 16.0 - rightButtonsWidth, y: contentVerticalOrigin + floor((nominalHeight - 44.0) * 0.5)), size: CGSize(width: rightButtonsWidth, height: 44.0)) var rightButtonsBackgroundTransition = ComponentTransition(transition) @@ -937,11 +987,6 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { } } - leftTitleInset = floor(leftTitleInset) - if Int(leftTitleInset) % 2 != 0 { - leftTitleInset -= 1.0 - } - if self.titleNode.view.superview != nil { let titleSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight)) @@ -958,31 +1003,30 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { } if let titleView = self.titleView { - let titleSize = CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight) - let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) - var titleViewTransition = transition - if titleView.frame.isEmpty { - titleViewTransition = .immediate - titleView.frame = titleFrame - } - - titleViewTransition.updateFrame(view: titleView, frame: titleFrame) - if let titleView = titleView as? NavigationBarTitleView { - let titleWidth = size.width - (leftTitleInset > 0.0 ? leftTitleInset : rightTitleInset) - (rightTitleInset > 0.0 ? rightTitleInset : leftTitleInset) - - titleView.updateLayout(size: titleFrame.size, clearBounds: CGRect(origin: CGPoint(x: leftTitleInset - titleFrame.minX, y: 0.0), size: CGSize(width: titleWidth, height: titleFrame.height)), transition: titleViewTransition) - } - - do { - if self.hintAnimateTitleNodeOnNextLayout { - self.hintAnimateTitleNodeOnNextLayout = false - if let titleView = titleView as? NavigationBarTitleView { - titleView.animateLayoutTransition() - } + var titleViewTransition = transition + if titleView.frame.isEmpty { + titleViewTransition = .immediate } - titleView.alpha = 1.0 - transition.updateFrame(view: titleView, frame: titleFrame) + + let titleSize = titleView.updateLayout(availableSize: CGSize(width: size.width - leftTitleInset - rightTitleInset, height: nominalHeight), transition: titleViewTransition) + + var titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) * 0.5), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) + if titleFrame.origin.x < leftTitleInset { + titleFrame.origin.x = leftTitleInset + floorToScreenPixels((size.width - leftTitleInset - rightTitleInset - titleFrame.width) * 0.5) + } + + titleViewTransition.updateFrame(view: titleView, frame: titleFrame) + } else { + let titleSize = CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight) + let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) + var titleViewTransition = transition + if titleView.frame.isEmpty { + titleViewTransition = .immediate + titleView.frame = titleFrame + } + + titleViewTransition.updateFrame(view: titleView, frame: titleFrame) } } } @@ -1038,7 +1082,10 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { } self.contentNode = contentNode self.contentNode?.requestContainerLayout = { [weak self] transition in - self?.requestContainerLayout(transition) + guard let self else { + return + } + self.requestContainerLayout?(transition) } if let contentNode { contentNode.layer.removeAnimation(forKey: "opacity") @@ -1144,6 +1191,13 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { } guard let result = super.hitTest(point, with: event) else { + if !self.bounds.contains(point) { + if let result = self.customOverBackgroundContentView.hitTest(self.view.convert(point, to: self.customOverBackgroundContentView), with: event) { + if result !== self.backgroundContainer?.contentView { + return result + } + } + } return nil } diff --git a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift index 0940ba237c..9f933e24f4 100644 --- a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift @@ -64,13 +64,16 @@ final class AffiliateProgramSetupScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let initialContent: AffiliateProgramSetupScreen.Content init( context: AccountContext, + overNavigationContainer: UIView, initialContent: AffiliateProgramSetupScreen.Content ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.initialContent = initialContent } @@ -93,6 +96,7 @@ final class AffiliateProgramSetupScreenComponent: Component { private let coinIcon = ComponentView() private let title = ComponentView() + private let titleContainer: UIView private let titleTransformContainer: UIView private var titleNeutralScale: CGFloat = 1.0 private let subtitle = ComponentView() @@ -157,6 +161,8 @@ final class AffiliateProgramSetupScreenComponent: Component { self.scrollView.contentInsetAdjustmentBehavior = .never self.scrollView.alwaysBounceVertical = true + self.titleContainer = SparseContainerView() + self.titleTransformContainer = UIView() self.titleTransformContainer.isUserInteractionEnabled = false @@ -337,6 +343,8 @@ final class AffiliateProgramSetupScreenComponent: Component { transition.setSublayerTransform(view: self.titleTransformContainer, transform: CATransform3DMakeTranslation(0.0, titleY - self.titleTransformContainer.center.y, 0.0)) + transition.setSublayerTransform(view: self.titleContainer, transform: CATransform3DMakeTranslation(0.0, -self.scrollView.contentOffset.y, 0.0)) + let titleYDistance: CGFloat = titleY - titleCenterY let titleTransformFraction: CGFloat = 1.0 - max(0.0, min(1.0, titleYDistance / titleTransformDistance)) let titleMinScale: CGFloat = 17.0 / 30.0 @@ -625,6 +633,10 @@ final class AffiliateProgramSetupScreenComponent: Component { self.component = component self.state = state + if self.titleContainer.superview == nil { + component.overNavigationContainer.addSubview(self.titleContainer) + } + let topInset: CGFloat = environment.navigationHeight + 90.0 let bottomInset: CGFloat = 8.0 let sideInset: CGFloat = 16.0 + environment.safeInsets.left @@ -648,7 +660,7 @@ final class AffiliateProgramSetupScreenComponent: Component { let coinIconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - coinIconSize.width) * 0.5), y: contentHeight - coinIconSize.height + 30.0), size: coinIconSize) if let coinIconView = self.coinIcon.view { if coinIconView.superview == nil { - self.scrollView.addSubview(coinIconView) + self.titleContainer.addSubview(coinIconView) } transition.setFrame(view: coinIconView, frame: coinIconFrame) } @@ -1620,16 +1632,21 @@ public class AffiliateProgramSetupScreen: ViewControllerComponentContainer { private let context: AccountContext private var isDismissed: Bool = false + private let overNavigationContainer: UIView + public init( context: AccountContext, initialContent: AffiliateProgramSetupScreenInitialData ) { self.context = context + self.overNavigationContainer = SparseContainerView() + let initialContent = initialContent as! AffiliateProgramSetupScreen.Content super.init(context: context, component: AffiliateProgramSetupScreenComponent( context: context, + overNavigationContainer: self.overNavigationContainer, initialContent: initialContent ), navigationBarAppearance: .default, theme: .default) @@ -1647,6 +1664,10 @@ public class AffiliateProgramSetupScreen: ViewControllerComponentContainer { return componentView.attemptNavigation(complete: complete) } + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/PeerInfo/CollectionTabItemComponent/Sources/CollectionTabItemComponent.swift b/submodules/TelegramUI/Components/PeerInfo/CollectionTabItemComponent/Sources/CollectionTabItemComponent.swift index f4d9a8ba11..484ee0d1f0 100644 --- a/submodules/TelegramUI/Components/PeerInfo/CollectionTabItemComponent/Sources/CollectionTabItemComponent.swift +++ b/submodules/TelegramUI/Components/PeerInfo/CollectionTabItemComponent/Sources/CollectionTabItemComponent.swift @@ -63,8 +63,8 @@ public final class CollectionTabItemComponent: Component { let iconSpacing: CGFloat = 3.0 - let normalColor = component.theme.list.itemSecondaryTextColor - let selectedColor = component.theme.list.freeTextColor + let normalColor = component.theme.list.itemPrimaryTextColor + let selectedColor = component.theme.list.itemPrimaryTextColor let effectiveColor = normalColor.mixedWith(selectedColor, alpha: environment.selectionFraction) let titleSize = self.title.update( @@ -109,7 +109,7 @@ public final class CollectionTabItemComponent: Component { transition: .immediate, component: AnyComponent(BundleIconComponent( name: "Chat/Input/Media/PanelBadgeAdd", - tintColor: component.theme.list.itemSecondaryTextColor + tintColor: component.theme.list.itemPrimaryTextColor )), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift index 32e8056694..ec78d27165 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift @@ -60,7 +60,7 @@ private final class SearchNavigationContentNode: ASDisplayNode, PeerInfoPanelNod let size = CGSize(width: width, height: defaultHeight) transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: size)) - self.contentNode.updateLayout(size: size, leftInset: insets.left, rightInset: insets.right, transition: transition) + let _ = self.contentNode.updateLayout(size: size, leftInset: insets.left, rightInset: insets.right, transition: transition) var contentHeight: CGFloat = size.height + 10.0 diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift index c3845d1ca8..4b7d6c5e55 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift @@ -54,7 +54,7 @@ private final class SearchNavigationContentNode: ASDisplayNode, PeerInfoPanelNod let size = CGSize(width: width, height: defaultHeight) transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: size)) - self.contentNode.updateLayout(size: size, leftInset: insets.left, rightInset: insets.right, transition: transition) + let _ = self.contentNode.updateLayout(size: size, leftInset: insets.left, rightInset: insets.right, transition: transition) var contentHeight: CGFloat = size.height + 10.0 diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD index 71742d960b..06bc92c3e5 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD @@ -169,6 +169,7 @@ swift_library( "//submodules/TelegramUI/Components/TextFieldComponent", "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/HorizontalTabsComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index 5a5ff6fb6c..f27025c670 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -153,7 +153,6 @@ final class PeerInfoHeaderNode: ASDisplayNode { let usernameNode: MultiScaleTextNode var actionButtonNodes: [PeerInfoHeaderButtonKey: PeerInfoHeaderActionButtonNode] = [:] var buttonNodes: [PeerInfoHeaderButtonKey: PeerInfoHeaderButtonNode] = [:] - let panelsExpansionBackgroundView: UIView let headerEdgeEffectView: EdgeEffectView var navigationTitle: String? let navigationButtonContainer: PeerInfoHeaderNavigationButtonContainerNode @@ -170,6 +169,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { var cancelUpload: (() -> Void)? var requestUpdateLayout: ((Bool) -> Void)? var animateOverlaysFadeIn: (() -> Void)? + var updateUnderHeaderContentsAlpha: ((CGFloat, ContainedViewLayoutTransition) -> Void)? var displayAvatarContextMenu: ((ASDisplayNode, ContextGesture?) -> Void)? var displayCopyContextMenu: ((ASDisplayNode, Bool, Bool) -> Void)? @@ -289,7 +289,6 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.searchBarContainer = SparseNode() self.searchContainer = ASDisplayNode() - self.panelsExpansionBackgroundView = UIView() self.headerEdgeEffectView = EdgeEffectView() self.headerEdgeEffectView.isUserInteractionEnabled = false @@ -305,7 +304,6 @@ final class PeerInfoHeaderNode: ASDisplayNode { self?.requestUpdateLayout?(false) } - self.view.addSubview(self.panelsExpansionBackgroundView) self.addSubnode(self.searchContainer) self.view.addSubview(self.headerEdgeEffectView) self.view.addSubview(self.backgroundBannerView) @@ -2507,20 +2505,14 @@ final class PeerInfoHeaderNode: ASDisplayNode { let edgeEffectHeight: CGFloat = 40.0 let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: backgroundFrame.width, height: navigationHeight + 18.0)) - let panelsExpansionBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -2000.0 + paneContainerY - contentOffset), size: CGSize(width: width, height: 2000.0)) - if additive { transition.updateFrameAdditive(layer: self.headerEdgeEffectView.layer, frame: edgeEffectFrame) - transition.updateFrameAdditive(view: self.panelsExpansionBackgroundView, frame: panelsExpansionBackgroundFrame) } else { transition.updateFrame(view: self.headerEdgeEffectView, frame: edgeEffectFrame) - transition.updateFrame(view: self.panelsExpansionBackgroundView, frame: panelsExpansionBackgroundFrame) } - self.panelsExpansionBackgroundView.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor - transition.updateAlpha(layer: self.panelsExpansionBackgroundView.layer, alpha: realAreaExpansionFraction) - if isSettings { - self.panelsExpansionBackgroundView.isHidden = true + if !isSettings { + self.updateUnderHeaderContentsAlpha?(1.0 - realAreaExpansionFraction, transition) } self.headerEdgeEffectView.update(content: presentationData.theme.rootController.navigationBar.opaqueBackgroundColor, blur: true, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectHeight, transition: ComponentTransition(transition)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift index 47d81e1bcd..ffde375e18 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift @@ -16,10 +16,14 @@ import PeerInfoChatPaneNode import TextFormat import EmojiTextAttachmentView import ComponentFlow +import ComponentDisplayAdapters import TabSelectorComponent import MultilineTextComponent import BottomButtonPanelComponent import UndoUI +import HorizontalTabsComponent +import GlassBackgroundComponent +import EdgeEffect final class PeerInfoPaneWrapper { let key: PeerInfoPaneKey @@ -44,8 +48,6 @@ final class PeerInfoPaneWrapper { } private final class GiftsTabItemComponent: Component { - typealias EnvironmentType = TabSelectorComponent.ItemEnvironment - let context: AccountContext let icons: [ProfileGiftsContext.State.StarGift] let title: String @@ -83,22 +85,19 @@ private final class GiftsTabItemComponent: Component { private var component: GiftsTabItemComponent? - func update(component: GiftsTabItemComponent, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { + func update(component: GiftsTabItemComponent, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { self.component = component - let environment = environment[EnvironmentType.self].value - let textSpacing: CGFloat = 2.0 let iconSpacing: CGFloat = 1.0 - let normalColor = component.theme.list.itemSecondaryTextColor - let selectedColor = component.theme.list.itemAccentColor - let effectiveColor = normalColor.mixedWith(selectedColor, alpha: environment.selectionFraction) + let normalColor = component.theme.chat.inputPanel.panelControlColor + let effectiveColor = normalColor let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.title, font: Font.medium(14.0), textColor: effectiveColor)) + text: .plain(NSAttributedString(string: component.title, font: Font.medium(15.0), textColor: effectiveColor)) )), environment: {}, containerSize: CGSize(width: availableSize.width, height: 100.0) @@ -399,292 +398,6 @@ private func interpolateFrame(from fromValue: CGRect, to toValue: CGRect, t: CGF return CGRect(x: floorToScreenPixels(toValue.origin.x * t + fromValue.origin.x * (1.0 - t)), y: floorToScreenPixels(toValue.origin.y * t + fromValue.origin.y * (1.0 - t)), width: floorToScreenPixels(toValue.size.width * t + fromValue.size.width * (1.0 - t)), height: floorToScreenPixels(toValue.size.height * t + fromValue.size.height * (1.0 - t))) } -final class PeerInfoPaneTabsContainerNode: ASDisplayNode { - private let context: AccountContext - private let scrollNode: ASScrollNode - private var paneNodes: [PeerInfoPaneKey: PeerInfoPaneTabsContainerPaneNode] = [:] - private let selectedLineNode: ASImageNode - - private var currentParams: ([PeerInfoPaneSpecifier], PeerInfoPaneKey?, Bool, PresentationData)? - - var requestSelectPane: ((PeerInfoPaneKey) -> Void)? - - init(context: AccountContext) { - self.context = context - self.scrollNode = ASScrollNode() - - self.selectedLineNode = ASImageNode() - self.selectedLineNode.displaysAsynchronously = false - self.selectedLineNode.displayWithoutProcessing = true - - super.init() - - self.scrollNode.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in - guard let strongSelf = self else { - return false - } - return strongSelf.scrollNode.view.contentOffset.x > .ulpOfOne - } - self.scrollNode.view.showsHorizontalScrollIndicator = false - self.scrollNode.view.scrollsToTop = false - if #available(iOS 11.0, *) { - self.scrollNode.view.contentInsetAdjustmentBehavior = .never - } - - self.addSubnode(self.scrollNode) - self.scrollNode.addSubnode(self.selectedLineNode) - } - - func update(size: CGSize, presentationData: PresentationData, paneList: [PeerInfoPaneSpecifier], selectedPane: PeerInfoPaneKey?, disableSwitching: Bool, transitionFraction: CGFloat, transition: ContainedViewLayoutTransition) { - transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size)) - - let focusOnSelectedPane = self.currentParams?.1 != selectedPane - - if self.currentParams?.3.theme !== presentationData.theme { - self.selectedLineNode.image = generateImage(CGSize(width: 7.0, height: 4.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(presentationData.theme.list.itemAccentColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width))) - })?.stretchableImage(withLeftCapWidth: 4, topCapHeight: 1) - } - - if self.currentParams?.0 != paneList || self.currentParams?.1 != selectedPane || self.currentParams?.3 !== presentationData { - for specifier in paneList { - let paneNode: PeerInfoPaneTabsContainerPaneNode - if let current = self.paneNodes[specifier.key] { - paneNode = current - } else { - paneNode = PeerInfoPaneTabsContainerPaneNode(pressed: { [weak self] in - self?.paneSelected(specifier.key) - }) - self.paneNodes[specifier.key] = paneNode - } - paneNode.updateText(context: self.context, title: specifier.title, icons: specifier.icons, isSelected: selectedPane == specifier.key, presentationData: presentationData) - } - var removeKeys: [PeerInfoPaneKey] = [] - for (key, _) in self.paneNodes { - if !paneList.contains(where: { $0.key == key }) { - removeKeys.append(key) - } - } - for key in removeKeys { - if let paneNode = self.paneNodes.removeValue(forKey: key) { - paneNode.removeFromSupernode() - } - } - } - self.currentParams = (paneList, selectedPane, disableSwitching, presentationData) - - var tabSizes: [(PeerInfoPaneKey, CGSize, PeerInfoPaneTabsContainerPaneNode, Bool)] = [] - var totalRawTabSize: CGFloat = 0.0 - var selectionFrames: [CGRect] = [] - - for specifier in paneList { - guard let paneNode = self.paneNodes[specifier.key] else { - continue - } - let wasAdded = paneNode.supernode == nil - if wasAdded { - self.scrollNode.addSubnode(paneNode) - } - let paneNodeWidth = paneNode.updateLayout(height: size.height) - let paneNodeSize = CGSize(width: paneNodeWidth, height: size.height) - tabSizes.append((specifier.key, paneNodeSize, paneNode, wasAdded)) - totalRawTabSize += paneNodeSize.width - } - - let minSpacing: CGFloat = 26.0 - if tabSizes.count <= 1 { - for i in 0 ..< tabSizes.count { - let (paneKey, paneNodeSize, paneNode, wasAdded) = tabSizes[i] - let leftOffset: CGFloat = 16.0 - - let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize) - - let paneAlpha: CGFloat - if disableSwitching { - paneAlpha = paneKey == selectedPane ? 1.0 : 0.5 - } else { - paneAlpha = 1.0 - } - - if wasAdded { - paneNode.frame = paneFrame - paneNode.alpha = 0.0 - } else { - transition.updateFrameAdditiveToCenter(node: paneNode, frame: paneFrame) - } - transition.updateAlpha(node: paneNode, alpha: paneAlpha) - - let areaSideInset: CGFloat = 16.0 - paneNode.updateArea(size: paneFrame.size, sideInset: areaSideInset) - paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -areaSideInset, bottom: 0.0, right: -areaSideInset) - - selectionFrames.append(paneFrame) - } - self.scrollNode.view.contentSize = CGSize(width: size.width, height: size.height) - } else if totalRawTabSize + CGFloat(tabSizes.count + 1) * minSpacing <= size.width { - let availableSpace = size.width - let availableSpacing = availableSpace - totalRawTabSize - let perTabSpacing = floor(availableSpacing / CGFloat(tabSizes.count + 1)) - - let normalizedPerTabWidth = floor(availableSpace / CGFloat(tabSizes.count)) - var maxSpacing: CGFloat = 0.0 - var minSpacing: CGFloat = .greatestFiniteMagnitude - for i in 0 ..< tabSizes.count - 1 { - let distanceToNextBoundary = (normalizedPerTabWidth - tabSizes[i].1.width) / 2.0 - let nextDistanceToBoundary = (normalizedPerTabWidth - tabSizes[i + 1].1.width) / 2.0 - let distance = nextDistanceToBoundary + distanceToNextBoundary - maxSpacing = max(distance, maxSpacing) - minSpacing = min(distance, minSpacing) - } - - if minSpacing >= 100.0 || (maxSpacing / minSpacing) < 0.2 { - for i in 0 ..< tabSizes.count { - let (paneKey, paneNodeSize, paneNode, wasAdded) = tabSizes[i] - - let paneFrame = CGRect(origin: CGPoint(x: CGFloat(i) * normalizedPerTabWidth + floor((normalizedPerTabWidth - paneNodeSize.width) / 2.0), y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize) - - let paneAlpha: CGFloat - if disableSwitching { - paneAlpha = paneKey == selectedPane ? 1.0 : 0.5 - } else { - paneAlpha = 1.0 - } - - if wasAdded { - paneNode.frame = paneFrame - paneNode.alpha = 0.0 - } else { - transition.updateFrameAdditiveToCenter(node: paneNode, frame: paneFrame) - } - - transition.updateAlpha(node: paneNode, alpha: paneAlpha) - - let areaSideInset = floor((normalizedPerTabWidth - paneNodeSize.width) / 2.0) - paneNode.updateArea(size: paneFrame.size, sideInset: areaSideInset) - paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -areaSideInset, bottom: 0.0, right: -areaSideInset) - - selectionFrames.append(paneFrame) - } - } else { - var leftOffset = perTabSpacing - for i in 0 ..< tabSizes.count { - let (paneKey, paneNodeSize, paneNode, wasAdded) = tabSizes[i] - - let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize) - - let paneAlpha: CGFloat - if disableSwitching { - paneAlpha = paneKey == selectedPane ? 1.0 : 0.5 - } else { - paneAlpha = 1.0 - } - - if wasAdded { - paneNode.frame = paneFrame - paneNode.alpha = 0.0 - } else { - transition.updateFrameAdditiveToCenter(node: paneNode, frame: paneFrame) - } - - transition.updateAlpha(node: paneNode, alpha: paneAlpha) - - let areaSideInset = floor(perTabSpacing / 2.0) - paneNode.updateArea(size: paneFrame.size, sideInset: areaSideInset) - paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -areaSideInset, bottom: 0.0, right: -areaSideInset) - - leftOffset += paneNodeSize.width + perTabSpacing - - selectionFrames.append(paneFrame) - } - } - self.scrollNode.view.contentSize = CGSize(width: size.width, height: size.height) - } else { - let sideInset: CGFloat = 16.0 - var leftOffset: CGFloat = sideInset - for i in 0 ..< tabSizes.count { - let (paneKey, paneNodeSize, paneNode, wasAdded) = tabSizes[i] - let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize) - - let paneAlpha: CGFloat - if disableSwitching { - paneAlpha = paneKey == selectedPane ? 1.0 : 0.5 - } else { - paneAlpha = 1.0 - } - - if wasAdded { - paneNode.frame = paneFrame - paneNode.alpha = 0.0 - } else { - transition.updateFrameAdditiveToCenter(node: paneNode, frame: paneFrame) - } - - transition.updateAlpha(node: paneNode, alpha: paneAlpha) - - paneNode.updateArea(size: paneFrame.size, sideInset: minSpacing) - paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -minSpacing, bottom: 0.0, right: -minSpacing) - - selectionFrames.append(paneFrame) - - leftOffset += paneNodeSize.width + minSpacing - } - self.scrollNode.view.contentSize = CGSize(width: leftOffset - minSpacing + sideInset, height: size.height) - } - - var selectedFrame: CGRect? - if let selectedPane = selectedPane, let currentIndex = paneList.firstIndex(where: { $0.key == selectedPane }) { - if currentIndex != 0 && transitionFraction > 0.0 { - let currentFrame = selectionFrames[currentIndex] - let previousFrame = selectionFrames[currentIndex - 1] - selectedFrame = interpolateFrame(from: currentFrame, to: previousFrame, t: abs(transitionFraction)) - } else if currentIndex != paneList.count - 1 && transitionFraction < 0.0 { - let currentFrame = selectionFrames[currentIndex] - let previousFrame = selectionFrames[currentIndex + 1] - selectedFrame = interpolateFrame(from: currentFrame, to: previousFrame, t: abs(transitionFraction)) - } else { - selectedFrame = selectionFrames[currentIndex] - } - } - - if let selectedFrame = selectedFrame { - let wasAdded = self.selectedLineNode.isHidden - self.selectedLineNode.isHidden = false - let lineFrame = CGRect(origin: CGPoint(x: selectedFrame.minX, y: size.height - 4.0), size: CGSize(width: selectedFrame.width, height: 4.0)) - if wasAdded { - self.selectedLineNode.frame = lineFrame - self.selectedLineNode.alpha = 0.0 - transition.updateAlpha(node: self.selectedLineNode, alpha: 1.0) - } else { - transition.updateFrame(node: self.selectedLineNode, frame: lineFrame) - } - if focusOnSelectedPane { - if selectedPane == paneList.first?.key { - transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size)) - } else if selectedPane == paneList.last?.key { - transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: max(0.0, self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width), y: 0.0), size: self.scrollNode.bounds.size)) - } else { - let contentOffsetX = max(0.0, min(self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width, floor(selectedFrame.midX - self.scrollNode.bounds.width / 2.0))) - transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size)) - } - } - } else { - self.selectedLineNode.isHidden = true - } - } - - private func paneSelected(_ key: PeerInfoPaneKey) { - guard let currentParams = self.currentParams else { - return - } - if currentParams.2 { - return - } - self.requestSelectPane?(key) - } -} - private final class PeerInfoPendingPane { let pane: PeerInfoPaneWrapper private var disposable: Disposable? @@ -874,11 +587,10 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat weak var parentController: ViewController? - private let coveringBackgroundNode: NavigationBackgroundNode - private let additionalBackgroundNode: ASDisplayNode - private let separatorNode: ASDisplayNode + private let edgeEffectView: EdgeEffectView + private let tabsBackgroundContainer: GlassBackgroundContainerView + private let tabsBackgroundView: GlassBackgroundView private let tabsContainer = ComponentView() - private let tabsSeparatorNode: ASDisplayNode private var didJustReorderTabs = false private var actionPanel: ComponentView? @@ -914,6 +626,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat private var initialStoryFolderId: Int64? private var initialGiftCollectionId: Int64? + private var isDraggingTabs: Bool = false private var transitionFraction: CGFloat = 0.0 var selectionPanelNode: PeerInfoSelectionPanelNode? @@ -951,22 +664,18 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat self.initialStoryFolderId = initialStoryFolderId self.initialGiftCollectionId = initialGiftCollectionId - self.additionalBackgroundNode = ASDisplayNode() + self.tabsBackgroundContainer = GlassBackgroundContainerView() + self.tabsBackgroundView = GlassBackgroundView() - self.separatorNode = ASDisplayNode() - self.separatorNode.isLayerBacked = true - - self.coveringBackgroundNode = NavigationBackgroundNode(color: .clear) - self.coveringBackgroundNode.isUserInteractionEnabled = false - - self.tabsSeparatorNode = ASDisplayNode() + self.edgeEffectView = EdgeEffectView() + self.edgeEffectView.isUserInteractionEnabled = false super.init() -// self.addSubnode(self.separatorNode) - self.addSubnode(self.additionalBackgroundNode) - self.addSubnode(self.coveringBackgroundNode) - self.addSubnode(self.tabsSeparatorNode) + self.view.addSubview(self.edgeEffectView) + + self.tabsBackgroundContainer.contentView.addSubview(self.tabsBackgroundView) + self.view.addSubview(self.tabsBackgroundContainer) } override func didLoad() { @@ -1031,6 +740,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) { switch recognizer.state { case .began: + self.isDraggingTabs = true + func cancelContextGestures(view: UIView) { if let gestureRecognizers = view.gestureRecognizers { for gesture in gestureRecognizers { @@ -1065,6 +776,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat self.currentPaneUpdated?(false) } case .cancelled, .ended: + self.isDraggingTabs = false + if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = self.currentParams, let availablePanes = data?.availablePanes, availablePanes.count > 1, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { let translation = recognizer.translation(in: self.view) let velocity = recognizer.velocity(in: self.view) @@ -1158,8 +871,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat } } - func openTabContextMenu(key: PeerInfoPaneKey, sourceNode: ASDisplayNode, gesture: ContextGesture?) { - guard let params = self.currentParams, let sourceNode = sourceNode as? ContextExtractedContentContainingNode else { + func openTabContextMenu(key: PeerInfoPaneKey, sourceView: UIView, gesture: ContextGesture?) { + guard let params = self.currentParams, let sourceView = sourceView as? ContextExtractedContentContainingView else { return } @@ -1190,7 +903,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat let contextController = ContextController( presentationData: params.presentationData, - source: .extracted(TabsExtractedContentSource(sourceNode: sourceNode)), + source: .reference(TabsReferenceContentSource(sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture @@ -1244,20 +957,12 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat self.currentParams = (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) - transition.updateAlpha(node: self.coveringBackgroundNode, alpha: expansionFraction) - -// transition.updateAlpha(node: self.additionalBackgroundNode, alpha: 1.0 - expansionFraction) - - self.backgroundColor = presentationData.theme.list.plainBackgroundColor - self.coveringBackgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.opaqueBackgroundColor, transition: .immediate) - self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor - self.additionalBackgroundNode.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor - self.tabsSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor + self.backgroundColor = presentationData.theme.list.blocksBackgroundColor let isScrollingLockedAtTop = expansionFraction < 1.0 - CGFloat.ulpOfOne - let tabsHeight: CGFloat = 48.0 - let effectiveTabsHeight: CGFloat = areTabsHidden ? 0.0 : tabsHeight + let tabsHeight: CGFloat = 40.0 + let effectiveTabsHeight: CGFloat = areTabsHidden ? 0.0 : (10.0 + tabsHeight + 10.0 + 6.0) let paneFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)) @@ -1445,7 +1150,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat if let index = availablePanes.firstIndex(of: key), let updatedCurrentIndex = updatedCurrentIndex { var paneWasAdded = false if pane.node.supernode == nil { - self.insertSubnode(pane.node, belowSubnode: self.coveringBackgroundNode) + self.insertSubnode(pane.node, at: 0) paneWasAdded = true } let indexOffset = CGFloat(index - updatedCurrentIndex) @@ -1493,12 +1198,14 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat } var tabsOffset: CGFloat = 0.0 - if let currentPane = self.currentPane { - tabsOffset = currentPane.node.tabBarOffset - } - tabsOffset = max(0.0, min(tabsHeight, tabsOffset)) - if isScrollingLockedAtTop || self.isMediaOnly { - tabsOffset = 0.0 + if !"".isEmpty { + if let currentPane = self.currentPane { + tabsOffset = currentPane.node.tabBarOffset + } + tabsOffset = max(0.0, min(tabsHeight, tabsOffset)) + if isScrollingLockedAtTop || self.isMediaOnly { + tabsOffset = 0.0 + } } var tabsAlpha: CGFloat @@ -1510,12 +1217,11 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat } tabsAlpha *= tabsAlpha - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel - tabsOffset), size: CGSize(width: size.width, height: UIScreenPixel))) - transition.updateFrame(node: self.coveringBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel - tabsOffset), size: CGSize(width: size.width, height: tabsHeight + UIScreenPixel))) - transition.updateFrame(node: self.additionalBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel - tabsOffset), size: CGSize(width: size.width, height: tabsHeight + UIScreenPixel))) - self.coveringBackgroundNode.update(size: self.coveringBackgroundNode.bounds.size, transition: transition) - - transition.updateFrame(node: self.tabsSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: tabsHeight - tabsOffset), size: CGSize(width: size.width, height: UIScreenPixel))) + let edgeEffectHeight: CGFloat = 60.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: 60.0 + 6.0)) + transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: presentationData.theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectHeight, transition: ComponentTransition(transition)) + ComponentTransition(transition).setAlpha(view: self.edgeEffectView, alpha: tabsAlpha) var canManageTabs = false if let peer = data?.peer { @@ -1528,124 +1234,138 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat } } - let items: [TabSelectorComponent.Item] = availablePanes.map { key in - let content: TabSelectorComponent.Item.Content - var canReorder = false - switch key { - case .stories: - content = .text(presentationData.strings.PeerInfo_PaneStories) - canReorder = true - case .storyArchive: - content = .text(presentationData.strings.PeerInfo_PaneArchivedStories) - case .botPreview: - content = .text(presentationData.strings.PeerInfo_PaneBotPreviews) - case .media: - content = .text(presentationData.strings.PeerInfo_PaneMedia) - canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel - case .files: - content = .text(presentationData.strings.PeerInfo_PaneFiles) - canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel - case .links: - content = .text(presentationData.strings.PeerInfo_PaneLinks) - canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel - case .voice: - content = .text(presentationData.strings.PeerInfo_PaneVoiceAndVideo) - canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel - case .gifs: - content = .text(presentationData.strings.PeerInfo_PaneGifs) - canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel - case .music: - content = .text(presentationData.strings.PeerInfo_PaneAudio) - canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel - case .groupsInCommon: - content = .text(presentationData.strings.PeerInfo_PaneGroups) - case .members: - content = .text(presentationData.strings.PeerInfo_PaneMembers) - case .similarChannels: - content = .text(presentationData.strings.PeerInfo_PaneRecommended) - case .similarBots: - content = .text(presentationData.strings.PeerInfo_PaneRecommendedBots) - case .savedMessagesChats: - content = .text(presentationData.strings.DialogList_TabTitle) - case .savedMessages: - content = .text(presentationData.strings.PeerInfo_SavedMessagesTabTitle) - case .gifts: - var icons: [ProfileGiftsContext.State.StarGift] = [] - if let gifts = data?.profileGiftsContext?.currentState?.gifts.prefix(3) { - icons = Array(gifts) - } - content = .component(AnyComponent( - GiftsTabItemComponent(context: self.context, icons: icons, title: presentationData.strings.PeerInfo_PaneGifts, theme: presentationData.theme) - )) - canReorder = true - } - return TabSelectorComponent.Item(id: key, content: content, isReorderable: false, contextAction: key != availablePanes.first && canManageTabs && canReorder ? { [weak self] node, gesture in - self?.openTabContextMenu(key: key, sourceNode: node, gesture: gesture) - } : nil) - } + let tabsSideInset: CGFloat = sideInset + 16.0 - let tabsContainerSize = CGSize(width: size.width - sideInset * 2.0, height: tabsHeight) + let tabsContainerSize = CGSize(width: size.width - tabsSideInset * 2.0, height: tabsHeight) let tabsContainerEffectiveSize = self.tabsContainer.update( transition: ComponentTransition(transition), - component: AnyComponent(TabSelectorComponent( - colors: TabSelectorComponent.Colors( - foreground: presentationData.theme.list.itemSecondaryTextColor, - selection: presentationData.theme.list.itemAccentColor - ), + component: AnyComponent(HorizontalTabsComponent( + context: self.context, theme: presentationData.theme, - customLayout: TabSelectorComponent.CustomLayout( - font: Font.medium(14.0), - spacing: 6.0, - fillWidth: true, - lineSelection: true - ), - items: items, - selectedId: self.currentPaneKey, - setSelectedId: { [weak self] id in - guard let strongSelf = self, let key = id.base as? PeerInfoPaneKey else { - return - } - if strongSelf.currentPaneKey == key { - if let requestExpandTabs = strongSelf.requestExpandTabs, requestExpandTabs() { - } else { - let _ = strongSelf.currentPane?.node.scrollToTop() - } - return - } - if strongSelf.currentPanes[key] != nil { - strongSelf.currentPaneKey = key - - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) - - strongSelf.currentPaneUpdated?(true) - - strongSelf.currentPaneStatusPromise.set(strongSelf.currentPane?.node.status ?? .single(nil)) - strongSelf.nextPaneStatusPromise.set(.single(nil)) - strongSelf.paneTransitionPromise.set(nil) - } - } else if strongSelf.pendingSwitchToPaneKey != key { - strongSelf.pendingSwitchToPaneKey = key - strongSelf.expandOnSwitch = true - - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) + tabs: availablePanes.map { paneKey -> HorizontalTabsComponent.Tab in + var canReorder = false + let content: HorizontalTabsComponent.Tab.Content + switch paneKey { + case .stories: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneStories, entities: [], enableAnimations: false)) + canReorder = true + case .storyArchive: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneArchivedStories, entities: [], enableAnimations: false)) + case .botPreview: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneBotPreviews, entities: [], enableAnimations: false)) + case .media: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneMedia, entities: [], enableAnimations: false)) + canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel + case .files: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneFiles, entities: [], enableAnimations: false)) + canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel + case .links: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneLinks, entities: [], enableAnimations: false)) + canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel + case .voice: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneVoiceAndVideo, entities: [], enableAnimations: false)) + canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel + case .gifs: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneGifs, entities: [], enableAnimations: false)) + canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel + case .music: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneAudio, entities: [], enableAnimations: false)) + canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel + case .groupsInCommon: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneGroups, entities: [], enableAnimations: false)) + case .members: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneMembers, entities: [], enableAnimations: false)) + case .similarChannels: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneRecommended, entities: [], enableAnimations: false)) + case .similarBots: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneRecommendedBots, entities: [], enableAnimations: false)) + case .savedMessagesChats: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.DialogList_TabTitle, entities: [], enableAnimations: false)) + case .savedMessages: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_SavedMessagesTabTitle, entities: [], enableAnimations: false)) + case .gifts: + var icons: [ProfileGiftsContext.State.StarGift] = [] + if let gifts = data?.profileGiftsContext?.currentState?.gifts.prefix(3) { + icons = Array(gifts) } + content = .custom(AnyComponent( + GiftsTabItemComponent(context: self.context, icons: icons, title: presentationData.strings.PeerInfo_PaneGifts, theme: presentationData.theme) + )) + canReorder = true } + + return HorizontalTabsComponent.Tab( + id: AnyHashable(paneKey), + content: content, + badge: nil, + action: { [weak self] in + guard let self else { + return + } + if self.currentPaneKey == paneKey { + if let requestExpandTabs = self.requestExpandTabs, requestExpandTabs() { + } else { + let _ = self.currentPane?.node.scrollToTop() + } + return + } + if self.currentPanes[paneKey] != nil { + self.currentPaneKey = paneKey + + if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = self.currentParams { + self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) + + self.currentPaneUpdated?(true) + + self.currentPaneStatusPromise.set(self.currentPane?.node.status ?? .single(nil)) + self.nextPaneStatusPromise.set(.single(nil)) + self.paneTransitionPromise.set(nil) + } + } else if self.pendingSwitchToPaneKey != paneKey { + self.pendingSwitchToPaneKey = paneKey + self.expandOnSwitch = true + + if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = self.currentParams { + self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) + } + } + }, + contextAction: paneKey != availablePanes.first && canManageTabs && canReorder ? { [weak self] sourceView, gesture in + guard let self else { + return + } + self.openTabContextMenu(key: paneKey, sourceView: sourceView, gesture: gesture) + } : nil, + deleteAction: nil + ) }, - transitionFraction: -self.transitionFraction + selectedTab: self.currentPaneKey.flatMap { HorizontalTabsComponent.Tab.Id($0) }, + isEditing: false, + layout: .fit )), environment: {}, containerSize: tabsContainerSize ) - let tabContainerFrameOriginX = items.count == 1 ? sideInset : floorToScreenPixels((size.width - tabsContainerEffectiveSize.width) / 2.0) - let tabContainerFrame = CGRect(origin: CGPoint(x: tabContainerFrameOriginX, y: 10.0 - tabsOffset), size: tabsContainerSize) - if let tabsContainerView = self.tabsContainer.view { + + let tabContainerFrameOriginX = floorToScreenPixels((size.width - tabsContainerEffectiveSize.width) / 2.0) + let tabContainerFrame = CGRect(origin: CGPoint(x: tabContainerFrameOriginX, y: 10.0), size: tabsContainerEffectiveSize) + + transition.updateFrame(view: self.tabsBackgroundContainer, frame: tabContainerFrame) + self.tabsBackgroundContainer.update(size: tabContainerFrame.size, isDark: presentationData.theme.overallDarkAppearance, transition: ComponentTransition(transition)) + + transition.updateFrame(view: self.tabsBackgroundView, frame: CGRect(origin: CGPoint(), size: tabContainerFrame.size)) + self.tabsBackgroundView.update(size: tabContainerFrame.size, cornerRadius: tabContainerFrame.height * 0.5, isDark: presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), transition: ComponentTransition(transition)) + + ComponentTransition(transition).setAlpha(view: self.tabsBackgroundContainer, alpha: tabsAlpha) + + if let tabsContainerView = self.tabsContainer.view as? HorizontalTabsComponent.View { if tabsContainerView.superview == nil { - self.view.insertSubview(tabsContainerView, belowSubview: self.tabsSeparatorNode.view) + self.tabsBackgroundView.contentView.addSubview(tabsContainerView) + tabsContainerView.setOverlayContainerView(overlayContainerView: self.view) } - transition.updateFrame(view: tabsContainerView, frame: tabContainerFrame) - transition.updateAlpha(layer: tabsContainerView.layer, alpha: tabsAlpha) + transition.updateFrame(view: tabsContainerView, frame: CGRect(origin: CGPoint(), size: tabContainerFrame.size)) + + tabsContainerView.updateTabSwitchFraction(fraction: self.transitionFraction, isDragging: self.isDraggingTabs, transition: ComponentTransition(transition)) } for (_, pane) in self.pendingPanes { @@ -1717,3 +1437,22 @@ private final class TabsExtractedContentSource: ContextExtractedContentSource { return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) } } + +private final class TabsReferenceContentSource: ContextReferenceContentSource { + let keepInPlace: Bool = true + let actionsHorizontalAlignment: ContextActionsHorizontalAlignment = .center + + private let sourceView: ContextExtractedContentContainingView + + init(sourceView: ContextExtractedContentContainingView) { + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo( + referenceView: self.sourceView.contentView, + contentAreaInScreenSpace: UIScreen.main.bounds, + actionsPosition: .bottom + ) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index f409d9067d..962f534e6b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -4840,6 +4840,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } + self.headerNode.updateUnderHeaderContentsAlpha = { [weak self] alpha, transition in + guard let self else { + return + } + for (_, section) in self.regularSections { + transition.updateAlpha(node: section, alpha: alpha) + } + } + let screenData: Signal if self.isSettings { self.notificationExceptions.set(.single(NotificationExceptionsList(peers: [:], settings: [:])) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD index 16458e0041..3719417c55 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD @@ -58,7 +58,8 @@ swift_library( "//submodules/TelegramUI/Components/BottomButtonPanelComponent", "//submodules/PromptUI", "//submodules/TelegramUI/Components/EmojiTextAttachmentView", - "//submodules/TelegramUI/Components/PeerInfo/CollectionTabItemComponent" + "//submodules/TelegramUI/Components/PeerInfo/CollectionTabItemComponent", + "//submodules/TelegramUI/Components/EdgeEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index c1409f7b9d..b7a2bb5caf 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -35,6 +35,7 @@ import EmojiTextAttachmentView import TextFormat import PromptUI import CollectionTabItemComponent +import EdgeEffect public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate { public enum GiftCollection: Equatable { @@ -91,8 +92,8 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr private let tabSelector = ComponentView() public private(set) var currentCollection: GiftCollection = .all - private var panelBackground: NavigationBackgroundNode? - private var panelSeparator: ASDisplayNode? + private var panelEdgeEffectView: EdgeEffectView? + private var panelContentContainer: UIView? private var panelButton: ComponentView? private var panelCheck: ComponentView? @@ -657,13 +658,13 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr component: AnyComponent(TabSelectorComponent( context: self.context, colors: TabSelectorComponent.Colors( - foreground: params.presentationData.theme.list.itemSecondaryTextColor, + foreground: params.presentationData.theme.list.itemPrimaryTextColor, selection: params.presentationData.theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15), simple: true ), theme: params.presentationData.theme, customLayout: TabSelectorComponent.CustomLayout( - font: Font.medium(14.0), + font: Font.medium(15.0), spacing: 2.0 ), items: tabSelectorItems, @@ -712,9 +713,9 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr tabSelectorView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } } - transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(x: floor((params.size.width - tabSelectorSize.width) / 2.0), y: 60.0), size: tabSelectorSize)) + transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(x: floor((params.size.width - tabSelectorSize.width) / 2.0), y: 67.0), size: tabSelectorSize)) - topInset += tabSelectorSize.height + 14.0 + topInset += tabSelectorSize.height + 28.0 } } else if let tabSelectorView = self.tabSelector.view { tabSelectorView.alpha = 0.0 @@ -731,32 +732,31 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr let bottomInset = params.bottomInset let presentationData = params.presentationData - let themeUpdated = self.theme !== presentationData.theme self.theme = presentationData.theme - let panelBackground: NavigationBackgroundNode - let panelSeparator: ASDisplayNode + let panelEdgeEffectView: EdgeEffectView + let panelContentContainer: UIView var panelVisibility = params.expandProgress < 1.0 ? 0.0 : 1.0 if !self.canGift || self.resultsAreEmpty { panelVisibility = 0.0 } - let panelTransition: ComponentTransition = .immediate - if let current = self.panelBackground { - panelBackground = current + if let current = self.panelContentContainer { + panelContentContainer = current } else { - panelBackground = NavigationBackgroundNode(color: presentationData.theme.rootController.tabBar.backgroundColor) - self.addSubnode(panelBackground) - self.panelBackground = panelBackground + panelContentContainer = UIView() + self.view.addSubview(panelContentContainer) + self.panelContentContainer = panelContentContainer } - if let current = self.panelSeparator { - panelSeparator = current + let panelTransition: ComponentTransition = .immediate + if let current = self.panelEdgeEffectView { + panelEdgeEffectView = current } else { - panelSeparator = ASDisplayNode() - panelBackground.addSubnode(panelSeparator) - self.panelSeparator = panelSeparator + panelEdgeEffectView = EdgeEffectView() + panelContentContainer.addSubview(panelEdgeEffectView) + self.panelEdgeEffectView = panelEdgeEffectView } let panelButton: ComponentView @@ -767,7 +767,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr self.panelButton = panelButton } - let buttonSideInset = sideInset + 16.0 + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: params.bottomInset, innerDiameter: 52.0 * 0.5, sideInset: sideInset + 16.0) let buttonTitle: String var buttonIconName: String? @@ -800,6 +800,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr component: AnyComponent( ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: presentationData.theme.list.itemCheckColors.fillColor, foreground: presentationData.theme.list.itemCheckColors.foregroundColor, pressedColor: presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) @@ -815,12 +816,12 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr ) ), environment: {}, - containerSize: CGSize(width: size.width - buttonSideInset * 2.0, height: 50.0) + containerSize: CGSize(width: size.width - buttonInsets.left * 2.0, height: 52.0) ) var scrollOffset: CGFloat = max(0.0, size.height - params.visibleHeight) - let effectiveBottomInset = max(8.0, bottomInset) + let effectiveBottomInset = max(buttonInsets.bottom, bottomInset) var bottomPanelHeight = effectiveBottomInset + panelButtonSize.height + 8.0 if params.visibleHeight < 110.0 { scrollOffset -= bottomPanelHeight @@ -828,15 +829,12 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr if let panelButtonView = panelButton.view { if panelButtonView.superview == nil { - panelBackground.view.addSubview(panelButtonView) + panelContentContainer.addSubview(panelButtonView) } - panelButtonView.frame = CGRect(origin: CGPoint(x: buttonSideInset, y: 8.0), size: panelButtonSize) + panelButtonView.frame = CGRect(origin: CGPoint(x: buttonInsets.left, y: 8.0), size: panelButtonSize) } - if themeUpdated { - panelBackground.updateColor(color: presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate) - panelSeparator.backgroundColor = presentationData.theme.rootController.tabBar.separatorColor - } + panelTransition.setFrame(view: panelContentContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - bottomPanelHeight - scrollOffset), size: CGSize(width: size.width, height: bottomPanelHeight))) if self.canManage { bottomPanelHeight -= 9.0 @@ -903,7 +901,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr ) if let panelCheckView = panelCheck.view { if panelCheckView.superview == nil { - panelBackground.view.addSubview(panelCheckView) + panelContentContainer.addSubview(panelCheckView) } panelCheckView.frame = CGRect(origin: CGPoint(x: floor((size.width - panelCheckSize.width) / 2.0), y: 16.0), size: panelCheckSize) } @@ -912,11 +910,11 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } } - panelTransition.setFrame(view: panelBackground.view, frame: CGRect(x: 0.0, y: size.height - bottomPanelHeight - scrollOffset, width: size.width, height: bottomPanelHeight)) - ComponentTransition.spring(duration: 0.4).setSublayerTransform(view: panelBackground.view, transform: CATransform3DMakeTranslation(0.0, bottomPanelHeight * (1.0 - panelVisibility), 0.0)) + let edgeEffectFrame = CGRect(x: 0.0, y: 0.0, width: size.width, height: bottomPanelHeight) + panelTransition.setFrame(view: panelEdgeEffectView, frame: edgeEffectFrame) + panelEdgeEffectView.update(content: presentationData.theme.list.blocksBackgroundColor, blur: false, rect: edgeEffectFrame, edge: .bottom, edgeSize: 40.0, transition: panelTransition) - panelBackground.update(size: CGSize(width: size.width, height: bottomPanelHeight), transition: transition.containedViewLayoutTransition) - panelTransition.setFrame(view: panelSeparator.view, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: UIScreenPixel)) + ComponentTransition.spring(duration: 0.4).setSublayerTransform(view: panelContentContainer, transform: CATransform3DMakeTranslation(0.0, bottomPanelHeight * (1.0 - panelVisibility), 0.0)) contentHeight += bottomPanelHeight bottomScrollInset = bottomPanelHeight - 40.0 diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index d6810166f0..1a40cc775a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -1824,6 +1824,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr self.maxStoriesPerFolder = maxStoriesPerFolder super.init() + + self.clipsToBounds = true if case .peer = self.scope { let _ = (ApplicationSpecificNotice.getSharedMediaScrollingTooltip(accountManager: context.sharedContext.accountManager) @@ -2112,7 +2114,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } if case .botPreview = scope { - let backgroundColor = presentationData.theme.list.plainBackgroundColor + let backgroundColor = presentationData.theme.list.blocksBackgroundColor let foregroundColor = presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.6) return SparseItemGrid.ShimmerColors(background: backgroundColor.argb, foreground: foregroundColor.argb) @@ -4024,12 +4026,14 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr transition: folderTabTransition, component: AnyComponent(TabSelectorComponent( colors: TabSelectorComponent.Colors( - foreground: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.8), - selection: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05) + foreground: self.presentationData.theme.list.itemPrimaryTextColor, + selection: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05), + normal: self.presentationData.theme.list.itemPrimaryTextColor, + simple: true ), theme: self.presentationData.theme, customLayout: TabSelectorComponent.CustomLayout( - font: Font.medium(14.0), + font: Font.medium(15.0), spacing: 9.0, verticalInset: 11.0 ), @@ -4092,7 +4096,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr 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 folderTabFrame = CGRect(origin: CGPoint(x: floor((size.width - folderTabSize.width) * 0.5), y: topInset - 19.0), size: folderTabSize) let effectiveScrollingOffset: CGFloat effectiveScrollingOffset = self.itemGrid.scrollingOffset @@ -4236,7 +4240,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } var hasBarBackground = false - if self.isProfileEmbedded { + if self.isProfileEmbedded && !"".isEmpty { if case .botPreview = self.scope { hasBarBackground = true } else if case let .peer(_, _, isArchived) = self.scope, ((self.canManageStories && !isArchived) || !self.currentStoryFolders.isEmpty) { @@ -4278,10 +4282,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr if case .botPreview = self.scope { updateBotPreviewFooter(size: size, bottomInset: 0.0, transition: transition) if let botPreviewFooterView = self.botPreviewFooter?.view { - listBottomInset += 18.0 + botPreviewFooterView.bounds.height + 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 @@ -4848,7 +4852,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } else if self.isProfileEmbedded, case let .peer(_, _, isArchived) = self.scope, ((self.canManageStories && !isArchived) || !self.currentStoryFolders.isEmpty), 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) + subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor) } else { subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor) } diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift index 8dceebcb26..6e85dd1eb2 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift @@ -1022,7 +1022,7 @@ final class QuickReplySetupScreenComponent: Component { theme: searchBarTheme, presentationTheme: environment.theme, strings: environment.strings, - fieldStyle: .modern, + fieldStyle: .glass, displayBackground: false ) searchBarNode.placeholderString = NSAttributedString(string: environment.strings.Common_Search, font: Font.regular(17.0), textColor: searchBarTheme.placeholder) diff --git a/submodules/TelegramUI/Components/Settings/PasskeysScreen/Sources/PasskeysScreen.swift b/submodules/TelegramUI/Components/Settings/PasskeysScreen/Sources/PasskeysScreen.swift index ba88ee22dc..8543f3c0b4 100644 --- a/submodules/TelegramUI/Components/Settings/PasskeysScreen/Sources/PasskeysScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PasskeysScreen/Sources/PasskeysScreen.swift @@ -397,7 +397,7 @@ public final class PasskeysScreen: ViewControllerComponentContainer { public init(context: AccountContext, displaySkip: Bool, initialPasskeysData: [TelegramPasskey]?, passkeysDataUpdated: @escaping ([TelegramPasskey]) -> Void, completion: @escaping () -> Void, cancel: @escaping () -> Void) { self.context = context - super.init(context: context, component: PasskeysScreenComponent(context: context, displaySkip: displaySkip, initialPasskeysData: initialPasskeysData, passkeysDataUpdated: passkeysDataUpdated, completion: completion, cancel: cancel), navigationBarAppearance: .transparent) + super.init(context: context, component: PasskeysScreenComponent(context: context, displaySkip: displaySkip, initialPasskeysData: initialPasskeysData, passkeysDataUpdated: passkeysDataUpdated, completion: completion, cancel: cancel), navigationBarAppearance: .default) } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift index 0886211e8c..2803e5865d 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift @@ -1959,7 +1959,7 @@ public class UserAppearanceScreen: ViewControllerComponentContainer { } if let navigationBar = self.navigationBar { - navigationBar.view.insertSubview(self.overNavigationContainer, aboveSubview: navigationBar.backgroundView) + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) } } diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift index b0a69117a4..a733e61b22 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -504,6 +504,7 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let starsContext: StarsContext let options: [Any] let purpose: StarsPurchasePurpose @@ -515,6 +516,7 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { init( context: AccountContext, + overNavigationContainer: UIView, starsContext: StarsContext, options: [Any], purpose: StarsPurchasePurpose, @@ -525,6 +527,7 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { completion: @escaping (Int64) -> Void ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.starsContext = starsContext self.options = options self.purpose = purpose @@ -759,8 +762,6 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { let scrollContent = Child(ScrollComponent.self) let star = Child(PremiumStarComponent.self) let avatar = Child(GiftAvatarComponent.self) - let topPanel = Child(BlurredBackgroundComponent.self) - let topSeparator = Child(Rectangle.self) let title = Child(MultilineTextComponent.self) let balanceTitle = Child(MultilineTextComponent.self) let balanceValue = Child(MultilineTextComponent.self) @@ -816,29 +817,13 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { UIColor(rgb: 0xfdd219) ], particleColor: UIColor(rgb: 0xf9b004), - backgroundColor: environment.theme.list.blocksBackgroundColor + backgroundColor: nil ), availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0), transition: context.transition ) } - let topPanel = topPanel.update( - component: BlurredBackgroundComponent( - color: environment.theme.rootController.navigationBar.blurredBackgroundColor - ), - availableSize: CGSize(width: context.availableSize.width, height: environment.navigationHeight), - transition: context.transition - ) - - let topSeparator = topSeparator.update( - component: Rectangle( - color: environment.theme.rootController.navigationBar.separatorColor - ), - availableSize: CGSize(width: context.availableSize.width, height: UIScreenPixel), - transition: context.transition - ) - let titleText: String switch context.component.purpose { case .generic: @@ -948,14 +933,12 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) ) - let topPanelAlpha: CGFloat let titleOffset: CGFloat let titleScale: CGFloat let titleOffsetDelta = (topInset + 160.0) - (environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0) let titleAlpha: CGFloat if let topContentOffset = state.topContentOffset { - topPanelAlpha = min(20.0, max(0.0, topContentOffset - 95.0)) / 20.0 let topContentOffset = topContentOffset + max(0.0, min(1.0, topContentOffset / titleOffsetDelta)) * 10.0 titleOffset = topContentOffset let fraction = max(0.0, min(1.0, titleOffset / titleOffsetDelta)) @@ -963,42 +946,37 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { titleAlpha = 1.0 } else { - topPanelAlpha = 0.0 titleScale = 1.0 titleOffset = 0.0 titleAlpha = 1.0 } - context.add(header + context.addWithExternalContainer(header .position(CGPoint(x: context.availableSize.width / 2.0, y: topInset + header.size.height / 2.0 - 30.0 - titleOffset * titleScale)) - .scale(titleScale) + .scale(titleScale), + container: context.component.overNavigationContainer ) - context.add(topPanel - .position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height / 2.0)) - .opacity(topPanelAlpha) - ) - context.add(topSeparator - .position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height)) - .opacity(topPanelAlpha) - ) - - context.add(title + context.addWithExternalContainer(title .position(CGPoint(x: context.availableSize.width / 2.0, y: max(topInset + 160.0 - titleOffset, environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0))) .scale(titleScale) - .opacity(titleAlpha) + .opacity(titleAlpha), + container: context.component.overNavigationContainer ) let navigationHeight = environment.navigationHeight - environment.statusBarHeight let topBalanceOriginY = environment.statusBarHeight + (navigationHeight - balanceTitle.size.height - balanceValue.size.height) / 2.0 - context.add(balanceTitle - .position(CGPoint(x: context.availableSize.width - 16.0 - environment.safeInsets.right - balanceTitle.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height / 2.0)) + context.addWithExternalContainer(balanceTitle + .position(CGPoint(x: context.availableSize.width - 16.0 - environment.safeInsets.right - balanceTitle.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height / 2.0)), + container: context.component.overNavigationContainer ) - context.add(balanceValue - .position(CGPoint(x: context.availableSize.width - 16.0 - environment.safeInsets.right - balanceValue.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0)) + context.addWithExternalContainer(balanceValue + .position(CGPoint(x: context.availableSize.width - 16.0 - environment.safeInsets.right - balanceValue.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0)), + container: context.component.overNavigationContainer ) - context.add(balanceIcon - .position(CGPoint(x: context.availableSize.width - 16.0 - environment.safeInsets.right - balanceValue.size.width - balanceIcon.size.width / 2.0 - 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0 - UIScreenPixel)) + context.addWithExternalContainer(balanceIcon + .position(CGPoint(x: context.availableSize.width - 16.0 - environment.safeInsets.right - balanceValue.size.width - balanceIcon.size.width / 2.0 - 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0 - UIScreenPixel)), + container: context.component.overNavigationContainer ) return context.availableSize @@ -1010,6 +988,8 @@ public final class StarsPurchaseScreen: ViewControllerComponentContainer { fileprivate let context: AccountContext fileprivate let starsContext: StarsContext + private let overNavigationContainer: UIView + private var didSetReady = false private let _ready = Promise() public override var ready: Promise { @@ -1027,6 +1007,8 @@ public final class StarsPurchaseScreen: ViewControllerComponentContainer { ) { self.context = context self.starsContext = starsContext + + self.overNavigationContainer = SparseContainerView() var openAppExamplesImpl: (() -> Void)? var updateInProgressImpl: ((Bool) -> Void)? @@ -1034,6 +1016,7 @@ public final class StarsPurchaseScreen: ViewControllerComponentContainer { var completionImpl: ((Int64) -> Void)? super.init(context: context, component: StarsPurchaseScreenComponent( context: context, + overNavigationContainer: self.overNavigationContainer, starsContext: starsContext, options: options, purpose: purpose, @@ -1050,7 +1033,7 @@ public final class StarsPurchaseScreen: ViewControllerComponentContainer { completion: { stars in completionImpl?(stars) } - ), navigationBarAppearance: .transparent, presentationMode: .modal, theme: customTheme.flatMap { .custom($0) } ?? .default) + ), navigationBarAppearance: .default, presentationMode: .modal, theme: customTheme.flatMap { .custom($0) } ?? .default) let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -1090,6 +1073,10 @@ public final class StarsPurchaseScreen: ViewControllerComponentContainer { completion(stars) } } + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } } required public init(coder aDecoder: NSCoder) { @@ -1129,10 +1116,10 @@ public final class StarsPurchaseScreen: ViewControllerComponentContainer { super.containerLayoutUpdated(layout, transition: transition) if !self.didSetReady { - if let view = self.node.hostView.findTaggedView(tag: PremiumStarComponent.View.Tag()) as? PremiumStarComponent.View { + if let view = findTaggedComponentViewImpl(view: self.node.view, tag: PremiumStarComponent.View.Tag()) as? PremiumStarComponent.View { self.didSetReady = true self._ready.set(view.ready) - } else if let view = self.node.hostView.findTaggedView(tag: GiftAvatarComponent.View.Tag()) as? GiftAvatarComponent.View { + } else if let view = findTaggedComponentViewImpl(view: self.node.view, tag: GiftAvatarComponent.View.Tag()) as? GiftAvatarComponent.View { self.didSetReady = true self._ready.set(view.ready) } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift index e07614b179..c4207b6e35 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift @@ -26,6 +26,7 @@ final class StarsStatisticsScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let peerId: EnginePeer.Id let revenueContext: StarsRevenueStatsContext let openTransaction: (StarsContext.State.Transaction) -> Void @@ -36,6 +37,7 @@ final class StarsStatisticsScreenComponent: Component { init( context: AccountContext, + overNavigationContainer: UIView, peerId: EnginePeer.Id, revenueContext: StarsRevenueStatsContext, openTransaction: @escaping (StarsContext.State.Transaction) -> Void, @@ -45,6 +47,7 @@ final class StarsStatisticsScreenComponent: Component { buyAds: @escaping () -> Void ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.peerId = peerId self.revenueContext = revenueContext self.openTransaction = openTransaction @@ -125,10 +128,6 @@ final class StarsStatisticsScreenComponent: Component { private let scrollView: ScrollViewImpl private var currentSelectedPanelId: AnyHashable? - - private let navigationBackgroundView: BlurredBackgroundView - private let navigationSeparatorLayer: SimpleLayer - private let navigationSeparatorLayerContainer: SimpleLayer private let headerView = ComponentView() private let headerOffsetContainer: UIView @@ -175,14 +174,6 @@ final class StarsStatisticsScreenComponent: Component { self.headerOffsetContainer = UIView() self.headerOffsetContainer.isUserInteractionEnabled = false - self.navigationBackgroundView = BlurredBackgroundView(color: nil, enableBlur: true) - self.navigationBackgroundView.alpha = 0.0 - - self.navigationSeparatorLayer = SimpleLayer() - self.navigationSeparatorLayer.opacity = 0.0 - self.navigationSeparatorLayerContainer = SimpleLayer() - self.navigationSeparatorLayerContainer.opacity = 0.0 - self.scrollContainerView = UIView() self.scrollView = ScrollViewImpl() @@ -208,11 +199,6 @@ final class StarsStatisticsScreenComponent: Component { self.scrollView.addSubview(self.scrollContainerView) self.scrollContainerView.addSubview(self.transactionsBackground) - self.addSubview(self.navigationBackgroundView) - - self.navigationSeparatorLayerContainer.addSublayer(self.navigationSeparatorLayer) - self.layer.addSublayer(self.navigationSeparatorLayerContainer) - self.addSubview(self.headerOffsetContainer) } @@ -307,18 +293,10 @@ final class StarsStatisticsScreenComponent: Component { let isLockedAtPanels = scrollBounds.maxY == self.scrollView.contentSize.height if let _ = self.navigationMetrics { - let topContentOffset = self.scrollView.contentOffset.y - let navigationBackgroundAlpha = min(20.0, max(0.0, topContentOffset)) / 20.0 - - let animatedTransition = ComponentTransition(animation: .curve(duration: 0.18, curve: .easeInOut)) - animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha) - animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha) - let expansionDistance: CGFloat = 32.0 var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor)) - transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor) if let panelContainerView = self.panelContainer.view as? StarsTransactionsPanelContainerComponent.View { panelContainerView.updateNavigationMergeFactor(value: 1.0 - expansionDistanceFactor, transition: transition) } @@ -417,19 +395,7 @@ final class StarsStatisticsScreenComponent: Component { self.controller = environment.controller self.navigationMetrics = (environment.navigationHeight, environment.statusBarHeight) - - self.navigationSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor - - let navigationFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: environment.navigationHeight)) - self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.navigationBackgroundView.update(size: navigationFrame.size, transition: transition.containedViewLayoutTransition) - transition.setFrame(view: self.navigationBackgroundView, frame: navigationFrame) - - let navigationSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel)) - - transition.setFrame(layer: self.navigationSeparatorLayerContainer, frame: navigationSeparatorFrame) - transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(), size: navigationSeparatorFrame.size)) - + self.backgroundColor = environment.theme.list.blocksBackgroundColor var contentHeight: CGFloat = 0.0 @@ -454,7 +420,7 @@ final class StarsStatisticsScreenComponent: Component { ) if let titleView = self.titleView.view { if titleView.superview == nil { - self.addSubview(titleView) + component.overNavigationContainer.addSubview(titleView) } let titlePosition = CGPoint(x: availableSize.width / 2.0, y: environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0) transition.setPosition(view: titleView, position: titlePosition) @@ -847,6 +813,8 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { private let peerId: EnginePeer.Id private let revenueContext: StarsRevenueStatsContext + private let overNavigationContainer: UIView + private weak var tooltipScreen: UndoOverlayController? private var timer: Foundation.Timer? @@ -857,6 +825,8 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { self.peerId = peerId self.revenueContext = revenueContext + self.overNavigationContainer = SparseContainerView() + var buyImpl: (() -> Void)? var withdrawImpl: (() -> Void)? var buyAdsImpl: (() -> Void)? @@ -864,6 +834,7 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { var openTransactionImpl: ((StarsContext.State.Transaction) -> Void)? super.init(context: context, component: StarsStatisticsScreenComponent( context: context, + overNavigationContainer: self.overNavigationContainer, peerId: peerId, revenueContext: revenueContext, openTransaction: { transaction in @@ -881,7 +852,7 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { buyAds: { buyAdsImpl?() } - ), navigationBarAppearance: .transparent) + ), navigationBarAppearance: .default) self.navigationPresentation = .modalInLargeLayout @@ -1060,6 +1031,10 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { } componentView.scrollToTop() } + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 9b16a84d72..65c1df1e8c 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -1252,7 +1252,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { gift: { giftImpl?() } - ), navigationBarAppearance: .transparent) + ), navigationBarAppearance: .default) self.navigationPresentation = .modalInLargeLayout diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/BUILD b/submodules/TelegramUI/Components/StorageUsageScreen/BUILD index 096451d9b2..4b984b59f0 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/BUILD +++ b/submodules/TelegramUI/Components/StorageUsageScreen/BUILD @@ -45,6 +45,7 @@ swift_library( "//submodules/GalleryData", "//submodules/SegmentedControlNode", "//submodules/TelegramUIPreferences", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift index bac4438def..50ed55b13d 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift @@ -24,22 +24,26 @@ import GalleryData import AnimatedTextComponent import TelegramUIPreferences import SegmentControlComponent +import GlassBackgroundComponent final class DataUsageScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let statsSet: StatsSet let mediaAutoDownloadSettings: MediaAutoDownloadSettings let makeAutodownloadSettingsController: (Bool) -> ViewController init( context: AccountContext, + overNavigationContainer: UIView, statsSet: StatsSet, mediaAutoDownloadSettings: MediaAutoDownloadSettings, makeAutodownloadSettingsController: @escaping (Bool) -> ViewController ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.statsSet = statsSet self.mediaAutoDownloadSettings = mediaAutoDownloadSettings self.makeAutodownloadSettingsController = makeAutodownloadSettingsController @@ -313,12 +317,9 @@ final class DataUsageScreenComponent: Component { private var mediaAutoDownloadSettings: MediaAutoDownloadSettings = .defaultSettings private var mediaAutoDownloadSettingsDisposable: Disposable? - private let navigationBackgroundView: BlurredBackgroundView - private let navigationSeparatorLayer: SimpleLayer - private let navigationSeparatorLayerContainer: SimpleLayer - private let headerView = ComponentView() private let headerOffsetContainer: HeaderContainer + private let headerContentContainer: HeaderContainer private let headerDescriptionView = ComponentView() private var doneLabel: ComponentView? @@ -356,14 +357,7 @@ final class DataUsageScreenComponent: Component { override init(frame: CGRect) { self.headerOffsetContainer = HeaderContainer() - - self.navigationBackgroundView = BlurredBackgroundView(color: nil, enableBlur: true) - self.navigationBackgroundView.alpha = 0.0 - - self.navigationSeparatorLayer = SimpleLayer() - self.navigationSeparatorLayer.opacity = 1.0 - self.navigationSeparatorLayerContainer = SimpleLayer() - self.navigationSeparatorLayerContainer.opacity = 0.0 + self.headerContentContainer = HeaderContainer() self.scrollContainerView = UIView() @@ -396,12 +390,7 @@ final class DataUsageScreenComponent: Component { self.scrollContainerView.addSubview(self.autoDownloadSettingsContainerView) - self.addSubview(self.navigationBackgroundView) - - self.navigationSeparatorLayerContainer.addSublayer(self.navigationSeparatorLayer) - self.layer.addSublayer(self.navigationSeparatorLayerContainer) - - self.addSubview(self.headerOffsetContainer) + self.headerOffsetContainer.addSubview(self.headerContentContainer) } required init?(coder: NSCoder) { @@ -447,41 +436,19 @@ final class DataUsageScreenComponent: Component { headerOffset = min(headerOffset, minOffset) let animatedTransition = ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut)) - let navigationBackgroundAlpha: CGFloat = abs(headerOffset - minOffset) < 4.0 ? 1.0 : 0.0 let navigationButtonAlpha: CGFloat = scrollBounds.minY >= navigationMetrics.navigationHeight ? 0.0 : 1.0 - animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha) - animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha) - - /*let expansionDistance: CGFloat = 32.0 - var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance - expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor)) - - transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor)*/ - - /*var offsetFraction: CGFloat = abs(headerOffset - minOffset) / 60.0 - offsetFraction = min(1.0, max(0.0, offsetFraction)) - transition.setScale(view: headerView, scale: 1.0 * offsetFraction + 0.8 * (1.0 - offsetFraction))*/ - transition.setBounds(view: self.headerOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: headerOffset), size: self.headerOffsetContainer.bounds.size)) + let headerContentsAlpha: CGFloat + let offsetFraction = abs(headerOffset - minOffset) / 60.0 + headerContentsAlpha = min(1.0, max(0.0, offsetFraction)) + + transition.setAlpha(view: self.headerContentContainer, alpha: headerContentsAlpha) + if let controller = self.controller?(), let backButtonNode = controller.navigationBar?.backButtonNode { backButtonNode.updateManualAlpha(alpha: navigationButtonAlpha, transition: animatedTransition.containedViewLayoutTransition) - - /*if backButtonNode.alpha != navigationButtonAlpha { - if backButtonNode.isHidden { - backButtonNode.alpha = 0.0 - backButtonNode.isHidden = false - } - animatedTransition.setAlpha(layer: backButtonNode.layer, alpha: navigationButtonAlpha, completion: { [weak backButtonNode] completed in - if let backButtonNode, completed { - if navigationButtonAlpha.isZero { - backButtonNode.isHidden = true - } - } - }) - }*/ } } } @@ -539,17 +506,9 @@ final class DataUsageScreenComponent: Component { self.navigationMetrics = (environment.navigationHeight, environment.statusBarHeight) - self.navigationSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor - - let navigationFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: environment.navigationHeight)) - self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.navigationBackgroundView.update(size: navigationFrame.size, transition: transition.containedViewLayoutTransition) - transition.setFrame(view: self.navigationBackgroundView, frame: navigationFrame) - - let navigationSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel)) - - transition.setFrame(layer: self.navigationSeparatorLayerContainer, frame: navigationSeparatorFrame) - transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(), size: navigationSeparatorFrame.size)) + if self.headerOffsetContainer.superview == nil { + component.overNavigationContainer.addSubview(self.headerOffsetContainer) + } self.backgroundColor = environment.theme.list.blocksBackgroundColor @@ -709,7 +668,7 @@ final class DataUsageScreenComponent: Component { let pieChartFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: pieChartSize) if let pieChartComponentView = self.pieChartView.view { if pieChartComponentView.superview == nil { - self.scrollView.addSubview(pieChartComponentView) + self.headerContentContainer.addSubview(pieChartComponentView) } pieChartTransition.setFrame(view: pieChartComponentView, frame: pieChartFrame) @@ -740,7 +699,7 @@ final class DataUsageScreenComponent: Component { if let doneLabelView = doneLabel.view { var animateIn = false if doneLabelView.superview == nil { - self.scrollView.addSubview(doneLabelView) + self.headerContentContainer.addSubview(doneLabelView) animateIn = true } doneLabelTransition.setFrame(view: doneLabelView, frame: doneLabelFrame) @@ -755,7 +714,7 @@ final class DataUsageScreenComponent: Component { if let doneSupLabelView = doneSupLabel.view { var animateIn = false if doneSupLabelView.superview == nil { - self.scrollView.addSubview(doneSupLabelView) + self.headerContentContainer.addSubview(doneSupLabelView) animateIn = true } doneLabelTransition.setFrame(view: doneSupLabelView, frame: doneSupLabelFrame) @@ -796,7 +755,7 @@ final class DataUsageScreenComponent: Component { let headerViewFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - headerViewSize.width) / 2.0), y: contentHeight), size: headerViewSize) if let headerComponentView = self.headerView.view { if headerComponentView.superview == nil { - self.scrollContainerView.addSubview(headerComponentView) + self.headerOffsetContainer.addSubview(headerComponentView) } transition.setPosition(view: headerComponentView, position: headerViewFrame.center) headerComponentView.bounds = CGRect(origin: CGPoint(), size: headerViewFrame.size) @@ -838,7 +797,7 @@ final class DataUsageScreenComponent: Component { let headerDescriptionFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - headerDescriptionSize.width) / 2.0), y: contentHeight), size: headerDescriptionSize) if let headerDescriptionComponentView = self.headerDescriptionView.view { if headerDescriptionComponentView.superview == nil { - self.scrollContainerView.addSubview(headerDescriptionComponentView) + self.headerContentContainer.addSubview(headerDescriptionComponentView) } transition.setPosition(view: headerDescriptionComponentView, position: headerDescriptionFrame.center) headerDescriptionComponentView.bounds = CGRect(origin: CGPoint(), size: headerDescriptionFrame.size) @@ -892,7 +851,7 @@ final class DataUsageScreenComponent: Component { ) if let chartTotalLabelView = self.chartTotalLabel.view { if chartTotalLabelView.superview == nil { - self.scrollContainerView.addSubview(chartTotalLabelView) + self.headerContentContainer.addSubview(chartTotalLabelView) } let chartAreaHeight: CGFloat @@ -926,7 +885,7 @@ final class DataUsageScreenComponent: Component { self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(value: .modeChanged))) })), environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) + containerSize: CGSize(width: availableSize.width - (environment.safeInsets.left + 16.0 + 44.0 + 10.0) * 2.0, height: 100.0) ) let segmentedControlFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - segmentedSize.width) * 0.5), y: contentHeight), size: segmentedSize) if let segmentedControlComponentView = self.segmentedControlView.view { @@ -1252,6 +1211,8 @@ final class DataUsageScreenComponent: Component { public final class DataUsageScreen: ViewControllerComponentContainer { private let context: AccountContext + private let overNavigationContainer: UIView + private let readyValue = Promise() override public var ready: Promise { return self.readyValue @@ -1260,8 +1221,14 @@ public final class DataUsageScreen: ViewControllerComponentContainer { public init(context: AccountContext, stats: NetworkUsageStats, mediaAutoDownloadSettings: MediaAutoDownloadSettings, makeAutodownloadSettingsController: @escaping (Bool) -> ViewController) { self.context = context + self.overNavigationContainer = SparseContainerView() + //let componentReady = Promise() - super.init(context: context, component: DataUsageScreenComponent(context: context, statsSet: DataUsageScreenComponent.StatsSet(stats: stats), mediaAutoDownloadSettings: mediaAutoDownloadSettings, makeAutodownloadSettingsController: makeAutodownloadSettingsController), navigationBarAppearance: .transparent) + super.init(context: context, component: DataUsageScreenComponent(context: context, overNavigationContainer: self.overNavigationContainer, statsSet: DataUsageScreenComponent.StatsSet(stats: stats), mediaAutoDownloadSettings: mediaAutoDownloadSettings, makeAutodownloadSettingsController: makeAutodownloadSettingsController), navigationBarAppearance: .default) + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } //self.readyValue.set(componentReady.get() |> timeout(0.3, queue: .mainQueue(), alternate: .single(true))) self.readyValue.set(.single(true)) diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift index b6629a6d46..16098f337b 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift @@ -24,6 +24,7 @@ import TelegramStringFormatting import GalleryData import AnimatedTextComponent import BottomButtonPanelComponent +import GlassBackgroundComponent #if DEBUG import os.signpost @@ -116,17 +117,20 @@ final class StorageUsageScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let makeStorageUsageExceptionsScreen: (CacheStorageSettings.PeerStorageCategory) -> ViewController? let peer: EnginePeer? let ready: Promise init( context: AccountContext, + overNavigationContainer: UIView, makeStorageUsageExceptionsScreen: @escaping (CacheStorageSettings.PeerStorageCategory) -> ViewController?, peer: EnginePeer?, ready: Promise ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.makeStorageUsageExceptionsScreen = makeStorageUsageExceptionsScreen self.peer = peer self.ready = ready @@ -740,9 +744,7 @@ final class StorageUsageScreenComponent: Component { private var isOtherCategoryExpanded: Bool = false - private let navigationBackgroundView: BlurredBackgroundView - private let navigationSeparatorLayer: SimpleLayer - private let navigationSeparatorLayerContainer: SimpleLayer + private let navigationRightButtonsBackground: GlassBackgroundView private let navigationEditButton = ComponentView() private let navigationDoneButton = ComponentView() @@ -799,16 +801,7 @@ final class StorageUsageScreenComponent: Component { private var keepScreenActiveDisposable: Disposable? override init(frame: CGRect) { - self.headerOffsetContainer = UIView() - self.headerOffsetContainer.isUserInteractionEnabled = false - - self.navigationBackgroundView = BlurredBackgroundView(color: nil, enableBlur: true) - self.navigationBackgroundView.alpha = 0.0 - - self.navigationSeparatorLayer = SimpleLayer() - self.navigationSeparatorLayer.opacity = 0.0 - self.navigationSeparatorLayerContainer = SimpleLayer() - self.navigationSeparatorLayerContainer.opacity = 0.0 + self.headerOffsetContainer = SparseContainerView() self.scrollContainerView = UIView() @@ -821,6 +814,8 @@ final class StorageUsageScreenComponent: Component { self.headerProgressBackgroundLayer = SimpleLayer() self.headerProgressForegroundLayer = SimpleLayer() + self.navigationRightButtonsBackground = GlassBackgroundView() + super.init(frame: frame) self.scrollView.delaysContentTouches = true @@ -846,13 +841,6 @@ final class StorageUsageScreenComponent: Component { self.scrollView.layer.addSublayer(self.headerProgressBackgroundLayer) self.scrollView.layer.addSublayer(self.headerProgressForegroundLayer) - - self.addSubview(self.navigationBackgroundView) - - self.navigationSeparatorLayerContainer.addSublayer(self.navigationSeparatorLayer) - self.layer.addSublayer(self.navigationSeparatorLayerContainer) - - self.addSubview(self.headerOffsetContainer) } required init?(coder: NSCoder) { @@ -946,9 +934,6 @@ final class StorageUsageScreenComponent: Component { let animatedTransition = ComponentTransition(animation: .curve(duration: 0.18, curve: .easeInOut)) let navigationBackgroundAlpha: CGFloat = abs(headerOffset - minOffset) < 4.0 ? 1.0 : 0.0 - animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha) - animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha) - var buttonsMasterAlpha: CGFloat = 1.0 if let component = self.component, component.peer != nil { buttonsMasterAlpha = 0.0 @@ -960,20 +945,12 @@ final class StorageUsageScreenComponent: Component { } } - let isSelectingPeers = self.aggregatedData?.isSelectingPeers ?? false - - if let navigationEditButtonView = self.navigationEditButton.view { - animatedTransition.setAlpha(view: navigationEditButtonView, alpha: (isSelectingPeers ? 0.0 : 1.0) * buttonsMasterAlpha * navigationBackgroundAlpha) - } - if let navigationDoneButtonView = self.navigationDoneButton.view { - animatedTransition.setAlpha(view: navigationDoneButtonView, alpha: (isSelectingPeers ? 1.0 : 0.0) * buttonsMasterAlpha * navigationBackgroundAlpha) - } + animatedTransition.setAlpha(view: self.navigationRightButtonsBackground, alpha: buttonsMasterAlpha * navigationBackgroundAlpha) let expansionDistance: CGFloat = 32.0 var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor)) - transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor) if let panelContainerView = self.panelContainer.view as? StorageUsagePanelContainerComponent.View { panelContainerView.updateNavigationMergeFactor(value: 1.0 - expansionDistanceFactor, transition: transition) } @@ -983,6 +960,17 @@ final class StorageUsageScreenComponent: Component { transition.setScale(view: headerView, scale: 1.0 * offsetFraction + 0.8 * (1.0 - offsetFraction)) transition.setBounds(view: self.headerOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: headerOffset), size: self.headerOffsetContainer.bounds.size)) + + let headerContentsAlpha = offsetFraction + if let chartAvatarNode = self.chartAvatarNode { + transition.setAlpha(view: chartAvatarNode.view, alpha: headerContentsAlpha) + } + if let pieChartComponentView = self.pieChartView.view { + transition.setAlpha(view: pieChartComponentView, alpha: headerContentsAlpha) + } + if let chartTotalLabelView = self.chartTotalLabel.view { + transition.setAlpha(view: chartTotalLabelView, alpha: headerContentsAlpha) + } } let _ = self.panelContainer.updateEnvironment( @@ -1109,6 +1097,13 @@ final class StorageUsageScreenComponent: Component { self.reloadStats(firstTime: true, completion: {}) } + if self.headerOffsetContainer.superview == nil { + component.overNavigationContainer.addSubview(self.headerOffsetContainer) + } + if self.navigationRightButtonsBackground.superview == nil { + component.overNavigationContainer.addSubview(self.navigationRightButtonsBackground) + } + var wasLockedAtPanels = false if let panelContainerView = self.panelContainer.view, let navigationMetrics = self.navigationMetrics { if self.scrollView.bounds.minY > 0.0 && abs(self.scrollView.bounds.minY - (panelContainerView.frame.minY - navigationMetrics.navigationHeight)) <= UIScreenPixel { @@ -1147,22 +1142,11 @@ final class StorageUsageScreenComponent: Component { self.navigationMetrics = (environment.navigationHeight, environment.statusBarHeight) - self.navigationSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor - - let navigationFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: environment.navigationHeight)) - self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.navigationBackgroundView.update(size: navigationFrame.size, transition: transition.containedViewLayoutTransition) - transition.setFrame(view: self.navigationBackgroundView, frame: navigationFrame) - - let navigationSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel)) - - transition.setFrame(layer: self.navigationSeparatorLayerContainer, frame: navigationSeparatorFrame) - transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(), size: navigationSeparatorFrame.size)) - let navigationEditButtonSize = self.navigationEditButton.update( transition: transition, component: AnyComponent(Button( - content: AnyComponent(Text(text: environment.strings.Common_Edit, font: Font.regular(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)), + content: AnyComponent(Text(text: environment.strings.Common_Edit, font: Font.regular(17.0), color: environment.theme.chat.inputPanel.panelControlColor)), + contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 6.0), action: { [weak self] in guard let self else { return @@ -1172,21 +1156,22 @@ final class StorageUsageScreenComponent: Component { self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring))) } } - ).minSize(CGSize(width: 16.0, height: environment.navigationHeight - environment.statusBarHeight))), + ).minSize(CGSize(width: 44.0, height: 44.0))), environment: {}, - containerSize: CGSize(width: 150.0, height: environment.navigationHeight - environment.statusBarHeight) + containerSize: CGSize(width: 150.0, height: 44.0) ) if let navigationEditButtonView = self.navigationEditButton.view { if navigationEditButtonView.superview == nil { - self.addSubview(navigationEditButtonView) + self.navigationRightButtonsBackground.contentView.addSubview(navigationEditButtonView) } - transition.setFrame(view: navigationEditButtonView, frame: CGRect(origin: CGPoint(x: availableSize.width - 12.0 - environment.safeInsets.right - navigationEditButtonSize.width, y: environment.statusBarHeight), size: navigationEditButtonSize)) + transition.setFrame(view: navigationEditButtonView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: navigationEditButtonSize)) } let navigationDoneButtonSize = self.navigationDoneButton.update( transition: transition, component: AnyComponent(Button( - content: AnyComponent(Text(text: environment.strings.Common_Done, font: Font.semibold(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)), + content: AnyComponent(Text(text: environment.strings.Common_Done, font: Font.semibold(17.0), color: environment.theme.chat.inputPanel.panelControlColor)), + contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 6.0), action: { [weak self] in guard let self, let aggregatedData = self.aggregatedData else { return @@ -1195,19 +1180,39 @@ final class StorageUsageScreenComponent: Component { aggregatedData.clearPeerSelection() self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring))) } - ).minSize(CGSize(width: 16.0, height: environment.navigationHeight - environment.statusBarHeight))), + ).minSize(CGSize(width: 44.0, height: 44.0))), environment: {}, - containerSize: CGSize(width: 150.0, height: environment.navigationHeight - environment.statusBarHeight) + containerSize: CGSize(width: 150.0, height: 44.0) ) if let navigationDoneButtonView = self.navigationDoneButton.view { if navigationDoneButtonView.superview == nil { - self.addSubview(navigationDoneButtonView) + self.navigationRightButtonsBackground.contentView.addSubview(navigationDoneButtonView) } - transition.setFrame(view: navigationDoneButtonView, frame: CGRect(origin: CGPoint(x: availableSize.width - 12.0 - environment.safeInsets.right - navigationDoneButtonSize.width, y: environment.statusBarHeight), size: navigationDoneButtonSize)) + transition.setFrame(view: navigationDoneButtonView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: navigationDoneButtonSize)) } let navigationRightButtonMaxWidth: CGFloat = max(navigationEditButtonSize.width, navigationDoneButtonSize.width) + var rightButtonsWidth: CGFloat = 0.0 + let isSelectingPeers = self.aggregatedData?.isSelectingPeers ?? false + if let navigationEditButtonView = self.navigationEditButton.view { + if !isSelectingPeers { + rightButtonsWidth += navigationEditButtonSize.width + } + transition.setAlpha(view: navigationEditButtonView, alpha: isSelectingPeers ? 0.0 : 1.0) + } + if let navigationDoneButtonView = self.navigationDoneButton.view { + if isSelectingPeers { + rightButtonsWidth += navigationDoneButtonSize.width + } + transition.setAlpha(view: navigationDoneButtonView, alpha: isSelectingPeers ? 1.0 : 0.0) + } + + let navigationRightButtonsBackgroundSize = CGSize(width: max(44.0, rightButtonsWidth), height: 44.0) + self.navigationRightButtonsBackground.update(size: navigationRightButtonsBackgroundSize, cornerRadius: 44.0 * 0.5, isDark: environment.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: environment.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + let navigationRightButtonsBackgroundFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - navigationRightButtonsBackgroundSize.width, y: environment.statusBarHeight + 2.0 + floor((environment.navigationHeight - environment.statusBarHeight - 44.0) * 0.5)), size: navigationRightButtonsBackgroundSize) + transition.setFrame(view: self.navigationRightButtonsBackground, frame: navigationRightButtonsBackgroundFrame) + self.backgroundColor = environment.theme.list.blocksBackgroundColor var contentHeight: CGFloat = 0.0 @@ -1436,7 +1441,7 @@ final class StorageUsageScreenComponent: Component { let pieChartFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: pieChartSize) if let pieChartComponentView = self.pieChartView.view { if pieChartComponentView.superview == nil { - self.scrollView.addSubview(pieChartComponentView) + self.headerOffsetContainer.addSubview(pieChartComponentView) } pieChartTransition.setFrame(view: pieChartComponentView, frame: pieChartFrame) @@ -1637,7 +1642,7 @@ final class StorageUsageScreenComponent: Component { } else { chartAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 17.0)) self.chartAvatarNode = chartAvatarNode - self.scrollContainerView.addSubview(chartAvatarNode.view) + self.headerOffsetContainer.addSubview(chartAvatarNode.view) chartAvatarNode.frame = avatarFrame if peer.id == component.context.account.peerId { @@ -1681,7 +1686,7 @@ final class StorageUsageScreenComponent: Component { ) if let chartTotalLabelView = self.chartTotalLabel.view { if chartTotalLabelView.superview == nil { - self.scrollContainerView.addSubview(chartTotalLabelView) + self.headerOffsetContainer.addSubview(chartTotalLabelView) } let totalLabelFrame = CGRect(origin: CGPoint(x: pieChartFrame.minX + floor((pieChartFrame.width - chartTotalLabelSize.width) / 2.0), y: pieChartFrame.minY + floor((pieChartFrame.height - chartTotalLabelSize.height) / 2.0)), size: chartTotalLabelSize) transition.setFrame(view: chartTotalLabelView, frame: totalLabelFrame) @@ -3303,6 +3308,8 @@ final class StorageUsageScreenComponent: Component { public final class StorageUsageScreen: ViewControllerComponentContainer { private let context: AccountContext + private let overNavigationContainer: UIView + private let readyValue = Promise() override public var ready: Promise { return self.readyValue @@ -3313,13 +3320,19 @@ public final class StorageUsageScreen: ViewControllerComponentContainer { public init(context: AccountContext, makeStorageUsageExceptionsScreen: @escaping (CacheStorageSettings.PeerStorageCategory) -> ViewController?, peer: EnginePeer? = nil) { self.context = context + self.overNavigationContainer = SparseContainerView() + let componentReady = Promise() - super.init(context: context, component: StorageUsageScreenComponent(context: context, makeStorageUsageExceptionsScreen: makeStorageUsageExceptionsScreen, peer: peer, ready: componentReady), navigationBarAppearance: .transparent) + super.init(context: context, component: StorageUsageScreenComponent(context: context, overNavigationContainer: self.overNavigationContainer, makeStorageUsageExceptionsScreen: makeStorageUsageExceptionsScreen, peer: peer, ready: componentReady), navigationBarAppearance: .default) if peer != nil { self.navigationPresentation = .modal } + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } + self.readyValue.set(componentReady.get() |> timeout(0.3, queue: .mainQueue(), alternate: .single(true))) } diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index 544deae7ab..6512a4d558 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -761,7 +761,7 @@ public final class StoryPeerListComponent: Component { let collapsedItemWidth: CGFloat = 24.0 let collapsedItemDistance: CGFloat = 14.0 - let collapsedItemOffsetY: CGFloat = -54.0 + let collapsedItemOffsetY: CGFloat = -66.0 let titleContentSpacing: CGFloat = 8.0 let collapsedItemCount: CGFloat = CGFloat(min(self.sortedItems.count - collapseStartIndex, 3)) @@ -1409,7 +1409,7 @@ public final class StoryPeerListComponent: Component { } if let titleIndicatorSize, let titleIndicatorView = self.titleIndicatorView?.view { - let titleIndicatorFrame = CGRect(origin: CGPoint(x: titleContentOffset - titleIndicatorSize.width - 9.0, y: collapsedItemOffsetY + 2.0 + floor((56.0 - titleIndicatorSize.height) * 0.5)), size: titleIndicatorSize) + let titleIndicatorFrame = CGRect(origin: CGPoint(x: titleContentOffset - titleIndicatorSize.width - 9.0, y: collapsedItemOffsetY + 14.0 + floor((56.0 - titleIndicatorSize.height) * 0.5)), size: titleIndicatorSize) if titleIndicatorView.superview == nil { self.addSubview(titleIndicatorView) } @@ -1427,7 +1427,7 @@ public final class StoryPeerListComponent: Component { titleIndicatorView.alpha = indicatorAlpha } - let titleFrame = CGRect(origin: CGPoint(x: titleContentOffset + titleLockOffset, y: collapsedItemOffsetY + 2.0 + floor((56.0 - titleSize.height) * 0.5)), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: titleContentOffset + titleLockOffset, y: collapsedItemOffsetY + 14.0 + floor((56.0 - titleSize.height) * 0.5)), size: titleSize) if let image = self.titleView.image { self.titleView.center = CGPoint(x: titleFrame.minX, y: titleFrame.midY) self.titleView.bounds = CGRect(origin: CGPoint(), size: image.size) @@ -1494,7 +1494,7 @@ public final class StoryPeerListComponent: Component { if let titleIconSize, let titleIconView = self.titleIconView?.view { titleContentOffset += titleIconSpacing - let titleIconFrame = CGRect(origin: CGPoint(x: titleContentOffset - 3.0 + titleIconSpacing + (collapsedState.titleWidth - (titleIconSpacing + titleIconSize.width)) * (1.0 - collapsedState.activityFraction), y: collapsedItemOffsetY + 2.0 + floor((56.0 - titleIconSize.height) * 0.5)), size: titleIconSize) + let titleIconFrame = CGRect(origin: CGPoint(x: titleContentOffset - 3.0 + titleIconSpacing + (collapsedState.titleWidth - (titleIconSpacing + titleIconSize.width)) * (1.0 - collapsedState.activityFraction), y: collapsedItemOffsetY + 14.0 + floor((56.0 - titleIconSize.height) * 0.5)), size: titleIconSize) if titleIconView.superview == nil { self.addSubview(titleIconView) @@ -1729,7 +1729,7 @@ public final class StoryPeerListComponent: Component { let itemLayout = ItemLayout( containerSize: availableSize, - containerInsets: UIEdgeInsets(top: 4.0, left: component.sideInset - 4.0, bottom: 0.0, right: component.sideInset - 4.0), + containerInsets: UIEdgeInsets(top: 16.0, left: component.sideInset - 4.0, bottom: 0.0, right: component.sideInset - 4.0), itemSize: CGSize(width: 60.0, height: 77.0), itemSpacing: 14.0, itemCount: self.sortedItems.count diff --git a/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift b/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift index 81bed27b27..17ceb59177 100644 --- a/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift +++ b/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift @@ -771,7 +771,10 @@ public final class TabBarComponent: Component { lensSize = CGSize(width: 48.0, height: 48.0) lensSelection = (0.0, 48.0) } - self.liquidLensView.update(size: lensSize, selectionX: lensSelection.x, selectionWidth: lensSelection.width, isDark: component.theme.overallDarkAppearance, isLifted: self.selectionGestureState != nil, isCollapsed: isLensCollapsed, transition: transition) + + lensSelection.x = max(0.0, min(lensSelection.x, lensSize.width - lensSelection.width)) + + self.liquidLensView.update(size: lensSize, selectionX: lensSelection.x, selectionWidth: lensSelection.width, inset: 4.0, isDark: component.theme.overallDarkAppearance, isLifted: self.selectionGestureState != nil, isCollapsed: isLensCollapsed, transition: transition) var size = tabsSize diff --git a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift index 7d93cb30f9..94c933b2c7 100644 --- a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift +++ b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift @@ -632,7 +632,7 @@ public final class TabSelectorComponent: Component { if case .component = item.content { useSelectionFraction = true } - if let _ = component.colors.normal { + if let normal = component.colors.normal, normal != component.colors.foreground { useSelectionFraction = true } diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index f8c979464d..9da3206638 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -238,12 +238,12 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController guard case .glass = self.style else { return } - let barButtonSize = CGSize(width: 40.0, height: 40.0) + let barButtonSize = CGSize(width: 44.0, height: 44.0) let closeComponent: AnyComponentWithIdentity = AnyComponentWithIdentity( id: "close", component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: self.presentationData.theme.overallDarkAppearance, state: .generic, component: AnyComponentWithIdentity(id: "close", component: AnyComponent( @@ -264,7 +264,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController id: "search", component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: self.presentationData.theme.overallDarkAppearance, state: .generic, component: AnyComponentWithIdentity(id: "search", component: AnyComponent( @@ -291,15 +291,19 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController self.closeButtonNode = closeButtonNode self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: closeButtonNode) } - - let searchButtonNode: BarComponentHostNode - if let current = self.searchButtonNode { - searchButtonNode = current - searchButtonNode.component = searchComponent + + if searchComponent != nil { + let searchButtonNode: BarComponentHostNode + if let current = self.searchButtonNode { + searchButtonNode = current + searchButtonNode.component = searchComponent + } else { + searchButtonNode = BarComponentHostNode(component: searchComponent, size: barButtonSize) + self.searchButtonNode = searchButtonNode + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: searchButtonNode) + } } else { - searchButtonNode = BarComponentHostNode(component: searchComponent, size: barButtonSize) - self.searchButtonNode = searchButtonNode - self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: searchButtonNode) + self.navigationItem.rightBarButtonItem = nil } } @@ -593,10 +597,12 @@ final class ContactsSearchNavigationContentNode: NavigationBarContentNode { return 56.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 56.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } func activate() { diff --git a/submodules/WebSearchUI/Sources/WebSearchNavigationContentNode.swift b/submodules/WebSearchUI/Sources/WebSearchNavigationContentNode.swift index e1316cd736..f7af067849 100644 --- a/submodules/WebSearchUI/Sources/WebSearchNavigationContentNode.swift +++ b/submodules/WebSearchUI/Sources/WebSearchNavigationContentNode.swift @@ -54,10 +54,12 @@ final class WebSearchNavigationContentNode: NavigationBarContentNode { return 56.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 56.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } func activate(select: Bool = false) {