mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Revert "Revert "[WIP] Tab bar and search""
This reverts commit 89f96e28b3.
This commit is contained in:
parent
6dba110767
commit
8047d0fa2a
33 changed files with 1474 additions and 816 deletions
|
|
@ -8,8 +8,6 @@ load("@build_bazel_rules_apple//apple:ios.bzl",
|
|||
"ios_application",
|
||||
"ios_extension",
|
||||
"ios_framework",
|
||||
"ios_unit_test",
|
||||
"ios_ui_test",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_apple//apple:resources.bzl",
|
||||
|
|
@ -22,7 +20,6 @@ load("@build_bazel_rules_swift//swift:swift.bzl",
|
|||
|
||||
load(
|
||||
"@rules_xcodeproj//xcodeproj:defs.bzl",
|
||||
"top_level_target",
|
||||
"top_level_targets",
|
||||
"xcodeproj",
|
||||
"xcode_provisioning_profile",
|
||||
|
|
|
|||
|
|
@ -155,6 +155,8 @@ public final class CallListController: TelegramBaseController {
|
|||
if case .navigation = self.mode {
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
}
|
||||
|
||||
self.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: false), transition: .immediate)
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
"//submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ import TextFormat
|
|||
import AvatarUploadToastScreen
|
||||
import AdsInfoScreen
|
||||
import AdsReportScreen
|
||||
import SearchBarNode
|
||||
import ChatListFilterTabContainerNode
|
||||
|
||||
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||
let controller: ViewController
|
||||
|
|
@ -768,6 +770,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
})
|
||||
|
||||
self.updateNavigationMetadata()
|
||||
|
||||
self.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: false), transition: .immediate)
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
|
@ -2838,14 +2842,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
func updateHeaderContent() -> (primaryContent: ChatListHeaderComponent.Content?, secondaryContent: ChatListHeaderComponent.Content?) {
|
||||
var primaryContent: ChatListHeaderComponent.Content?
|
||||
if let primaryContext = self.primaryContext {
|
||||
var backTitle: String?
|
||||
if let previousItem = self.previousItem {
|
||||
switch previousItem {
|
||||
case let .item(item):
|
||||
backTitle = item.title ?? self.presentationData.strings.Common_Back
|
||||
case .close:
|
||||
backTitle = self.presentationData.strings.Common_Close
|
||||
}
|
||||
var displayBackButton: Bool = false
|
||||
if self.previousItem != nil {
|
||||
displayBackButton = true
|
||||
}
|
||||
var navigationBackTitle: String?
|
||||
if case .chatList(.archive) = self.location {
|
||||
|
|
@ -2858,8 +2857,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
chatListTitle: primaryContext.chatListTitle,
|
||||
leftButton: primaryContext.leftButton,
|
||||
rightButtons: primaryContext.rightButtons,
|
||||
backTitle: backTitle,
|
||||
backPressed: backTitle != nil ? { [weak self] in
|
||||
backPressed: displayBackButton ? { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
|
@ -2876,7 +2874,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
chatListTitle: secondaryContext.chatListTitle,
|
||||
leftButton: secondaryContext.leftButton,
|
||||
rightButtons: secondaryContext.rightButtons,
|
||||
backTitle: nil,
|
||||
backPressed: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
|
|
@ -4638,9 +4635,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
searchContentNode = navigationBarView.searchContentNode
|
||||
}
|
||||
|
||||
if let searchContentNode {
|
||||
self.activateSearch(filter: filter, query: query, skipScrolling: false, searchContentNode: searchContentNode)
|
||||
}
|
||||
self.activateSearch(filter: filter, query: query, skipScrolling: false, searchContentNode: searchContentNode)
|
||||
}
|
||||
|
||||
public func activateSearch(query: String? = nil) {
|
||||
|
|
@ -4654,45 +4649,37 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
}
|
||||
|
||||
private var previousSearchToggleTimestamp: Double?
|
||||
func activateSearch(filter: ChatListSearchFilter = .chats, query: String? = nil, skipScrolling: Bool = false, searchContentNode: NavigationBarSearchContentNode) {
|
||||
let currentTimestamp = CACurrentMediaTime()
|
||||
if let previousSearchActivationTimestamp = self.previousSearchToggleTimestamp, currentTimestamp < previousSearchActivationTimestamp + 0.6 {
|
||||
return
|
||||
}
|
||||
self.previousSearchToggleTimestamp = currentTimestamp
|
||||
|
||||
if let storyTooltip = self.storyTooltip {
|
||||
storyTooltip.dismiss()
|
||||
}
|
||||
|
||||
var filter = filter
|
||||
if case .forum = self.chatListDisplayNode.effectiveContainerNode.location {
|
||||
filter = .topics
|
||||
}
|
||||
|
||||
if self.chatListDisplayNode.searchDisplayController == nil {
|
||||
/*if !skipScrolling, let searchContentNode = self.searchContentNode, searchContentNode.expansionProgress != 1.0 {
|
||||
self.scrollToTop?()
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: { [weak self] in
|
||||
self?.activateSearch(filter: filter, query: query, skipScrolling: true)
|
||||
})
|
||||
func activateSearch(filter: ChatListSearchFilter = .chats, query: String? = nil, skipScrolling: Bool = false, searchContentNode: NavigationBarSearchContentNode?) {
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}*/
|
||||
//TODO:scroll to top?
|
||||
}
|
||||
|
||||
let currentTimestamp = CACurrentMediaTime()
|
||||
if let previousSearchActivationTimestamp = self.previousSearchToggleTimestamp, currentTimestamp < previousSearchActivationTimestamp + 0.6 {
|
||||
return
|
||||
}
|
||||
self.previousSearchToggleTimestamp = currentTimestamp
|
||||
|
||||
let _ = (combineLatest(self.chatListDisplayNode.mainContainerNode.currentItemNode.contentsReady |> take(1), self.context.account.postbox.tailChatListView(groupId: .root, count: 16, summaryComponents: ChatListEntrySummaryComponents(components: [:])) |> take(1))
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] _, chatListView in
|
||||
Task { @MainActor in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if let storyTooltip = self.storyTooltip {
|
||||
storyTooltip.dismiss()
|
||||
}
|
||||
|
||||
var filter = filter
|
||||
if case .forum = self.chatListDisplayNode.effectiveContainerNode.location {
|
||||
filter = .topics
|
||||
}
|
||||
|
||||
if self.chatListDisplayNode.searchDisplayController == nil {
|
||||
let (_, _) = await combineLatest(self.chatListDisplayNode.mainContainerNode.currentItemNode.contentsReady |> take(1), self.context.account.postbox.tailChatListView(groupId: .root, count: 16, summaryComponents: ChatListEntrySummaryComponents(components: [:])) |> take(1)).get()
|
||||
|
||||
do {
|
||||
/*if let scrollToTop = strongSelf.scrollToTop {
|
||||
scrollToTop()
|
||||
}*/
|
||||
scrollToTop()
|
||||
}*/
|
||||
|
||||
let tabsIsEmpty: Bool
|
||||
if let (resolvedItems, displayTabsAtBottom, _) = strongSelf.tabContainerData {
|
||||
if let (resolvedItems, displayTabsAtBottom, _) = self.tabContainerData {
|
||||
tabsIsEmpty = resolvedItems.count <= 1 || displayTabsAtBottom
|
||||
} else {
|
||||
tabsIsEmpty = true
|
||||
|
|
@ -4702,40 +4689,43 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
|
||||
let displaySearchFilters = true
|
||||
|
||||
if let filterContainerNodeAndActivate = await strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, displaySearchFilters: displaySearchFilters, hasDownloads: strongSelf.hasDownloads, initialFilter: filter, navigationController: strongSelf.navigationController as? NavigationController) {
|
||||
if let filterContainerNodeAndActivate = await self.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode?.placeholderNode, displaySearchFilters: displaySearchFilters, hasDownloads: self.hasDownloads, initialFilter: filter, navigationController: self.navigationController as? NavigationController, searchBarIsExternal: true) {
|
||||
let (filterContainerNode, activate) = filterContainerNodeAndActivate
|
||||
if displaySearchFilters {
|
||||
let searchTabsNode = SparseNode()
|
||||
strongSelf.searchTabsNode = searchTabsNode
|
||||
self.searchTabsNode = searchTabsNode
|
||||
searchTabsNode.addSubnode(filterContainerNode)
|
||||
}
|
||||
|
||||
activate(filter != .downloads)
|
||||
|
||||
if let searchContentNode = strongSelf.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode {
|
||||
if let searchContentNode = self.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode {
|
||||
searchContentNode.search(filter: filter, query: query)
|
||||
}
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
|
||||
strongSelf.setDisplayNavigationBar(false, transition: transition)
|
||||
|
||||
(strongSelf.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
})
|
||||
|
||||
self.isSearchActive = true
|
||||
if let navigationController = self.navigationController as? NavigationController {
|
||||
for controller in navigationController.globalOverlayControllers {
|
||||
if let controller = controller as? VoiceChatOverlayController {
|
||||
controller.updateVisibility()
|
||||
break
|
||||
self.setDisplayNavigationBar(false, transition: transition)
|
||||
self.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: true), transition: transition)
|
||||
if let searchBarNode = self.currentTabBarSearchNode?() as? SearchBarNode {
|
||||
self.chatListDisplayNode.searchDisplayController?.setSearchBar(searchBarNode)
|
||||
searchBarNode.activate()
|
||||
}
|
||||
|
||||
self.isSearchActive = true
|
||||
if let navigationController = self.navigationController as? NavigationController {
|
||||
for controller in navigationController.globalOverlayControllers {
|
||||
if let controller = controller as? VoiceChatOverlayController {
|
||||
controller.updateVisibility()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if self.isSearchActive {
|
||||
if let searchContentNode = self.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode {
|
||||
searchContentNode.search(filter: filter, query: query)
|
||||
} else if self.isSearchActive {
|
||||
if let searchContentNode = self.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode {
|
||||
searchContentNode.search(filter: filter, query: query)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4766,6 +4756,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
}
|
||||
completion = self.chatListDisplayNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated)
|
||||
searchContentNode.placeholderNode.frame = previousFrame
|
||||
} else {
|
||||
completion = self.chatListDisplayNode.deactivateSearch(placeholderNode: nil, animated: animated)
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.tempAllowAvatarExpansion = true
|
||||
|
|
@ -4780,7 +4772,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
|
||||
completion?()
|
||||
|
||||
(self.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.4, curve: .spring))
|
||||
self.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: false), transition: transition)
|
||||
|
||||
self.isSearchActive = false
|
||||
if let navigationController = self.navigationController as? NavigationController {
|
||||
|
|
@ -6236,6 +6228,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
|
||||
})
|
||||
}
|
||||
|
||||
override public func tabBarActivateSearch() {
|
||||
self.activateSearch()
|
||||
}
|
||||
|
||||
override public func tabBarDeactivateSearch() {
|
||||
self.deactivateSearch(animated: true)
|
||||
}
|
||||
|
||||
private var playedSignUpCompletedAnimation = false
|
||||
public func playSignUpCompletedAnimation() {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import ChatListHeaderComponent
|
|||
import StoryPeerListComponent
|
||||
import TelegramNotices
|
||||
import EdgeEffect
|
||||
import ChatListFilterTabContainerNode
|
||||
|
||||
public enum ChatListContainerNodeFilter: Equatable {
|
||||
case all
|
||||
|
|
@ -1382,8 +1383,8 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
|||
strings: self.presentationData.strings,
|
||||
statusBarHeight: layout.statusBarHeight ?? 0.0,
|
||||
sideInset: layout.safeInsets.left,
|
||||
search: nil, //ChatListNavigationBar.Search(isEnabled: true),
|
||||
isSearchActive: self.isSearchDisplayControllerActive,
|
||||
isSearchEnabled: true,
|
||||
primaryContent: headerContent?.primaryContent,
|
||||
secondaryContent: headerContent?.secondaryContent,
|
||||
secondaryTransition: self.inlineStackContainerTransitionFraction,
|
||||
|
|
@ -1540,7 +1541,8 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
|||
var storiesInset = storiesInset
|
||||
|
||||
let navigationBarLayout = self.updateNavigationBar(layout: layout, deferScrollApplication: true, transition: ComponentTransition(transition))
|
||||
self.mainContainerNode.initialScrollingOffset = ChatListNavigationBar.searchScrollHeight + navigationBarLayout.storiesInset
|
||||
//self.mainContainerNode.initialScrollingOffset = ChatListNavigationBar.searchScrollHeight + navigationBarLayout.storiesInset
|
||||
self.mainContainerNode.initialScrollingOffset = navigationBarLayout.storiesInset
|
||||
|
||||
navigationBarHeight = navigationBarLayout.navigationHeight
|
||||
visualNavigationHeight = navigationBarLayout.navigationHeight
|
||||
|
|
@ -1666,7 +1668,7 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
|||
}
|
||||
|
||||
@MainActor
|
||||
func activateSearch(placeholderNode: SearchBarPlaceholderNode, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter, navigationController: NavigationController?) async -> (ASDisplayNode, (Bool) -> Void)? {
|
||||
func activateSearch(placeholderNode: SearchBarPlaceholderNode?, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter, navigationController: NavigationController?, searchBarIsExternal: Bool) async -> (ASDisplayNode, (Bool) -> Void)? {
|
||||
guard let (containerLayout, _, _, cleanNavigationBarHeight, _) = self.containerLayout, self.searchDisplayController == nil else {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1712,7 +1714,7 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
|||
if let requestDeactivateSearch = self?.requestDeactivateSearch {
|
||||
requestDeactivateSearch()
|
||||
}
|
||||
})
|
||||
}, searchBarIsExternal: searchBarIsExternal)
|
||||
self.mainContainerNode.accessibilityElementsHidden = true
|
||||
self.inlineStackContainerNode?.accessibilityElementsHidden = true
|
||||
|
||||
|
|
@ -1742,7 +1744,7 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
|||
})
|
||||
}
|
||||
|
||||
func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) -> (() -> Void)? {
|
||||
func deactivateSearch(placeholderNode: SearchBarPlaceholderNode?, animated: Bool) -> (() -> Void)? {
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
self.isSearchDisplayControllerActive = false
|
||||
self.searchDisplayController = nil
|
||||
|
|
@ -1750,7 +1752,8 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
|||
self.inlineStackContainerNode?.accessibilityElementsHidden = false
|
||||
|
||||
return { [weak self, weak placeholderNode] in
|
||||
if let strongSelf = self, let placeholderNode, let (layout, _, _, cleanNavigationBarHeight, _) = strongSelf.containerLayout {
|
||||
if let strongSelf = self, let (layout, _, _, cleanNavigationBarHeight, _) = strongSelf.containerLayout {
|
||||
let placeholderNode = placeholderNode
|
||||
searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated)
|
||||
|
||||
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: .animated(duration: 0.4, curve: .spring))
|
||||
|
|
|
|||
|
|
@ -2103,7 +2103,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
let effectiveBackgroundColor: UIColor
|
||||
if item.isPinned {
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .pinned, voiceChat: self.onlineIsVoiceChat)
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.pinnedItemBackgroundColor
|
||||
//TODO:localize
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.itemBackgroundColor
|
||||
//effectiveBackgroundColor = item.presentationData.theme.chatList.pinnedItemBackgroundColor
|
||||
} else {
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .regular, voiceChat: self.onlineIsVoiceChat)
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.itemBackgroundColor
|
||||
|
|
@ -2117,7 +2119,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
if let avatarLiveBadge = self.avatarLiveBadge {
|
||||
let effectiveBackgroundColor: UIColor
|
||||
if item.isPinned {
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.pinnedItemBackgroundColor
|
||||
//TODO:localize
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.itemBackgroundColor
|
||||
//effectiveBackgroundColor = item.presentationData.theme.chatList.pinnedItemBackgroundColor
|
||||
} else {
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.itemBackgroundColor
|
||||
}
|
||||
|
|
@ -4127,7 +4131,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
|
||||
let effectiveBackgroundColor: UIColor
|
||||
if item.isPinned {
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.pinnedItemBackgroundColor
|
||||
//TODO:localize
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.itemBackgroundColor
|
||||
//effectiveBackgroundColor = item.presentationData.theme.chatList.pinnedItemBackgroundColor
|
||||
} else {
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.itemBackgroundColor
|
||||
}
|
||||
|
|
@ -4148,7 +4154,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
effectiveBackgroundColor = item.presentationData.theme.chatList.itemHighlightedBackgroundColor
|
||||
} else if case let .chatList(index) = item.index, index.pinningIndex != nil {
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .pinned, voiceChat: onlineIsVoiceChat)
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.pinnedItemBackgroundColor
|
||||
//TODO:localize
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.itemBackgroundColor
|
||||
//effectiveBackgroundColor = item.presentationData.theme.chatList.pinnedItemBackgroundColor
|
||||
} else {
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .regular, voiceChat: onlineIsVoiceChat)
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.itemBackgroundColor
|
||||
|
|
@ -5060,7 +5068,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
backgroundColor = theme.itemBackgroundColor
|
||||
highlightedBackgroundColor = theme.itemHighlightedBackgroundColor
|
||||
} else {
|
||||
backgroundColor = theme.pinnedItemBackgroundColor
|
||||
//TODO:localize
|
||||
backgroundColor = item.presentationData.theme.chatList.itemBackgroundColor
|
||||
//backgroundColor = theme.pinnedItemBackgroundColor
|
||||
highlightedBackgroundColor = theme.pinnedItemHighlightedBackgroundColor
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1335,6 +1335,41 @@ public struct ComponentTransition {
|
|||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
public func setBlur(layer: CALayer, radius: CGFloat, completion: ((Bool) -> Void)? = nil) {
|
||||
var currentRadius: CGFloat = 0.0
|
||||
if let currentFilters = layer.filters {
|
||||
for filter in currentFilters {
|
||||
if let filter = filter as? NSObject, filter.description.contains("gaussianBlur") {
|
||||
currentRadius = filter.value(forKey: "inputRadius") as? CGFloat ?? 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if currentRadius == radius {
|
||||
completion?(true)
|
||||
return
|
||||
}
|
||||
|
||||
if let blurFilter = CALayer.blur() {
|
||||
blurFilter.setValue(radius as NSNumber, forKey: "inputRadius")
|
||||
layer.filters = [blurFilter]
|
||||
switch self.animation {
|
||||
case .none:
|
||||
completion?(true)
|
||||
case let .curve(duration, curve):
|
||||
layer.animate(from: currentRadius as NSNumber, to: radius as NSNumber, keyPath: "filters.gaussianBlur.inputRadius", duration: duration, delay: 0.0, curve: curve, removeOnCompletion: true, additive: false,completion: { [weak layer] flag in
|
||||
if let layer {
|
||||
if radius <= 0.0 {
|
||||
layer.filters = nil
|
||||
}
|
||||
}
|
||||
|
||||
completion?(flag)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func animateBlur(layer: CALayer, fromRadius: CGFloat, toRadius: CGFloat, delay: Double = 0.0, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
let duration: Double
|
||||
|
|
|
|||
|
|
@ -226,6 +226,8 @@ public class ContactsController: ViewController {
|
|||
}
|
||||
|
||||
self.sortButton.addTarget(self, action: #selector(self.sortPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: false), transition: .immediate)
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
|
|
|||
|
|
@ -350,7 +350,6 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
|||
chatListTitle: NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil),
|
||||
leftButton: leftButton,
|
||||
rightButtons: rightButtons,
|
||||
backTitle: nil,
|
||||
backPressed: nil
|
||||
)
|
||||
|
||||
|
|
@ -362,8 +361,8 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
|||
strings: self.presentationData.strings,
|
||||
statusBarHeight: layout.statusBarHeight ?? 0.0,
|
||||
sideInset: layout.safeInsets.left,
|
||||
search: ChatListNavigationBar.Search(isEnabled: true),
|
||||
isSearchActive: self.isSearchDisplayControllerActive,
|
||||
isSearchEnabled: true,
|
||||
primaryContent: primaryContent,
|
||||
secondaryContent: nil,
|
||||
secondaryTransition: 0.0,
|
||||
|
|
|
|||
|
|
@ -217,6 +217,18 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject {
|
|||
open var navigationBarRequiresEntireLayoutUpdate: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
public struct TabBarSearchState: Equatable {
|
||||
public var isActive: Bool
|
||||
|
||||
public init(isActive: Bool) {
|
||||
self.isActive = isActive
|
||||
}
|
||||
}
|
||||
|
||||
public private(set) var tabBarSearchState: TabBarSearchState?
|
||||
public var tabBarSearchStateUpdated: ((ContainedViewLayoutTransition) -> Void)?
|
||||
public var currentTabBarSearchNode: (() -> ASDisplayNode?)?
|
||||
|
||||
private weak var activeInputViewCandidate: UIResponder?
|
||||
private weak var activeInputView: UIResponder?
|
||||
|
|
@ -705,9 +717,22 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject {
|
|||
|
||||
open func tabBarDisabledAction() {
|
||||
}
|
||||
|
||||
open func tabBarActivateSearch() {
|
||||
}
|
||||
|
||||
open func tabBarDeactivateSearch() {
|
||||
}
|
||||
|
||||
open func tabBarItemSwipeAction(direction: TabBarItemSwipeDirection) {
|
||||
}
|
||||
|
||||
public func updateTabBarSearchState(_ tabBarSearchState: TabBarSearchState?, transition: ContainedViewLayoutTransition) {
|
||||
if self.tabBarSearchState != tabBarSearchState {
|
||||
self.tabBarSearchState = tabBarSearchState
|
||||
self.tabBarSearchStateUpdated?(transition)
|
||||
}
|
||||
}
|
||||
|
||||
open func updatePossibleControllerDropContent(content: NavigationControllerDropContent?) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -369,6 +369,7 @@ private class SearchBarTextField: UITextField, UIScrollViewDelegate {
|
|||
}
|
||||
|
||||
var theme: SearchBarNodeTheme
|
||||
let style: SearchBarStyle
|
||||
|
||||
fileprivate func layoutTokens(transition: ContainedViewLayoutTransition = .immediate) {
|
||||
var hasSelected = false
|
||||
|
|
@ -519,8 +520,9 @@ private class SearchBarTextField: UITextField, UIScrollViewDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
init(theme: SearchBarNodeTheme) {
|
||||
init(theme: SearchBarNodeTheme, style: SearchBarStyle) {
|
||||
self.theme = theme
|
||||
self.style = style
|
||||
|
||||
self.placeholderLabel = ImmediateTextNode()
|
||||
self.placeholderLabel.isUserInteractionEnabled = false
|
||||
|
|
@ -679,6 +681,10 @@ private class SearchBarTextField: UITextField, UIScrollViewDelegate {
|
|||
placeholderYOffset = 1.0
|
||||
} else {
|
||||
}
|
||||
|
||||
if case .inlineNavigation = self.style {
|
||||
placeholderYOffset += 6.0
|
||||
}
|
||||
|
||||
let textRect = self.textRect(forBounds: bounds)
|
||||
let labelSize = self.placeholderLabel.updateLayout(textRect.size)
|
||||
|
|
@ -813,40 +819,45 @@ public final class SearchBarNodeTheme: Equatable {
|
|||
public enum SearchBarStyle {
|
||||
case modern
|
||||
case legacy
|
||||
case inlineNavigation
|
||||
|
||||
var font: UIFont {
|
||||
switch self {
|
||||
case .modern:
|
||||
return Font.regular(17.0)
|
||||
case .legacy:
|
||||
return Font.regular(14.0)
|
||||
case .modern, .inlineNavigation:
|
||||
return Font.regular(17.0)
|
||||
case .legacy:
|
||||
return Font.regular(14.0)
|
||||
}
|
||||
}
|
||||
|
||||
var cornerDiameter: CGFloat {
|
||||
switch self {
|
||||
case .modern:
|
||||
return 21.0
|
||||
case .legacy:
|
||||
return 14.0
|
||||
case .modern, .inlineNavigation:
|
||||
return 21.0
|
||||
case .legacy:
|
||||
return 14.0
|
||||
}
|
||||
}
|
||||
|
||||
var height: CGFloat {
|
||||
switch self {
|
||||
case .modern:
|
||||
return 36.0
|
||||
case .legacy:
|
||||
return 28.0
|
||||
case .inlineNavigation:
|
||||
return 48.0
|
||||
case .modern:
|
||||
return 36.0
|
||||
case .legacy:
|
||||
return 28.0
|
||||
}
|
||||
}
|
||||
|
||||
var padding: CGFloat {
|
||||
switch self {
|
||||
case .modern:
|
||||
return 10.0
|
||||
case .legacy:
|
||||
return 8.0
|
||||
case .inlineNavigation:
|
||||
return 0.0
|
||||
case .modern:
|
||||
return 10.0
|
||||
case .legacy:
|
||||
return 8.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -994,7 +1005,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
|
|||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
|
||||
self.textField = SearchBarTextField(theme: theme)
|
||||
self.textField = SearchBarTextField(theme: theme, style: fieldStyle)
|
||||
self.textField.accessibilityTraits = .searchField
|
||||
self.textField.autocorrectionType = .no
|
||||
self.textField.returnKeyType = .search
|
||||
|
|
@ -1011,14 +1022,21 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
|
|||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.separatorNode)
|
||||
|
||||
self.addSubnode(self.textBackgroundNode)
|
||||
if case .inlineNavigation = self.fieldStyle {
|
||||
} else {
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.textBackgroundNode)
|
||||
}
|
||||
self.view.addSubview(self.textField)
|
||||
self.addSubnode(self.iconNode)
|
||||
|
||||
if case .inlineNavigation = self.fieldStyle {
|
||||
} else {
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.cancelButton)
|
||||
}
|
||||
|
||||
self.addSubnode(self.clearButton)
|
||||
self.addSubnode(self.cancelButton)
|
||||
|
||||
self.textField.delegate = self
|
||||
self.textField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged)
|
||||
|
|
@ -1093,19 +1111,34 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
|
|||
self.backgroundNode.update(size: self.backgroundNode.bounds.size, transition: .immediate)
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.bounds.size.height), size: CGSize(width: self.bounds.size.width, height: UIScreenPixel)))
|
||||
|
||||
let verticalOffset: CGFloat = boundingSize.height - 82.0
|
||||
|
||||
let contentFrame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: boundingSize.width - leftInset - rightInset, height: boundingSize.height))
|
||||
|
||||
let textBackgroundHeight = self.fieldStyle.height
|
||||
let textBackgroundHeight: CGFloat
|
||||
if case .inlineNavigation = self.fieldStyle {
|
||||
textBackgroundHeight = boundingSize.height
|
||||
} else {
|
||||
textBackgroundHeight = self.fieldStyle.height
|
||||
}
|
||||
let verticalOffset: CGFloat
|
||||
if case .inlineNavigation = self.fieldStyle {
|
||||
verticalOffset = -textBackgroundHeight
|
||||
} else {
|
||||
verticalOffset = boundingSize.height - 82.0
|
||||
}
|
||||
let cancelButtonSize = self.cancelButton.measure(CGSize(width: 100.0, height: CGFloat.infinity))
|
||||
transition.updateFrame(node: self.cancelButton, frame: CGRect(origin: CGPoint(x: contentFrame.maxX - 10.0 - cancelButtonSize.width, y: verticalOffset + textBackgroundHeight + floorToScreenPixels((textBackgroundHeight - cancelButtonSize.height) / 2.0)), size: cancelButtonSize))
|
||||
|
||||
let padding = self.fieldStyle.padding
|
||||
let textBackgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX + padding, y: verticalOffset + textBackgroundHeight), size: CGSize(width: contentFrame.width - padding * 2.0 - (self.hasCancelButton ? cancelButtonSize.width + 11.0 : 0.0), height: textBackgroundHeight))
|
||||
var textBackgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX + padding, y: verticalOffset + textBackgroundHeight), size: CGSize(width: contentFrame.width - padding * 2.0 - (self.hasCancelButton ? cancelButtonSize.width + 11.0 : 0.0), height: textBackgroundHeight))
|
||||
transition.updateFrame(node: self.textBackgroundNode, frame: textBackgroundFrame)
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 24.0, y: textBackgroundFrame.minY), size: CGSize(width: max(1.0, textBackgroundFrame.size.width - 24.0 - 27.0), height: textBackgroundFrame.size.height))
|
||||
var textFrame = CGRect(origin: CGPoint(x: 0.0, y: textBackgroundFrame.minY), size: CGSize(width: max(1.0, textBackgroundFrame.size.width - 24.0 - 27.0), height: textBackgroundFrame.size.height))
|
||||
if case .inlineNavigation = self.fieldStyle {
|
||||
textFrame.size.width = boundingSize.width - 27.0
|
||||
textBackgroundFrame.size.width = boundingSize.width
|
||||
} else {
|
||||
textFrame.origin.x = textBackgroundFrame.minX + 24.0
|
||||
}
|
||||
|
||||
if let iconImage = self.iconNode.image {
|
||||
let iconSize = iconImage.size
|
||||
|
|
|
|||
|
|
@ -25,12 +25,14 @@ public final class SearchDisplayController {
|
|||
}
|
||||
}
|
||||
|
||||
private let searchBar: SearchBarNode
|
||||
private var searchBar: SearchBarNode?
|
||||
private let searchBarIsExternal: Bool
|
||||
private let mode: SearchDisplayControllerMode
|
||||
private let backgroundNode: BackgroundNode
|
||||
public let contentNode: SearchDisplayControllerContentNode
|
||||
private var hasSeparator: Bool
|
||||
private let inline: Bool
|
||||
private let cancel: () -> Void
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
|
|
@ -38,71 +40,75 @@ public final class SearchDisplayController {
|
|||
|
||||
private var isSearchingDisposable: Disposable?
|
||||
|
||||
public init(presentationData: PresentationData, mode: SearchDisplayControllerMode = .navigation, placeholder: String? = nil, hasBackground: Bool = false, hasSeparator: Bool = false, contentNode: SearchDisplayControllerContentNode, inline: Bool = false, cancel: @escaping () -> Void) {
|
||||
public init(
|
||||
presentationData: PresentationData,
|
||||
mode: SearchDisplayControllerMode = .navigation,
|
||||
placeholder: String? = nil,
|
||||
hasBackground: Bool = false,
|
||||
hasSeparator: Bool = false,
|
||||
contentNode: SearchDisplayControllerContentNode,
|
||||
inline: Bool = false,
|
||||
cancel: @escaping () -> Void,
|
||||
fieldStyle: SearchBarStyle = .modern,
|
||||
searchBarIsExternal: Bool = false
|
||||
) {
|
||||
self.inline = inline
|
||||
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasBackground: hasBackground, hasSeparator: hasSeparator, inline: inline), strings: presentationData.strings, fieldStyle: .modern, forceSeparator: hasSeparator, displayBackground: hasBackground)
|
||||
self.cancel = cancel
|
||||
self.searchBarIsExternal = searchBarIsExternal
|
||||
|
||||
if !searchBarIsExternal {
|
||||
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasBackground: hasBackground, hasSeparator: hasSeparator, inline: inline), strings: presentationData.strings, fieldStyle: fieldStyle, forceSeparator: hasSeparator, displayBackground: hasBackground)
|
||||
}
|
||||
self.backgroundNode = BackgroundNode()
|
||||
self.backgroundNode.allowsGroupOpacity = true
|
||||
|
||||
self.mode = mode
|
||||
self.contentNode = contentNode
|
||||
self.hasSeparator = hasSeparator
|
||||
|
||||
if let searchBar = self.searchBar {
|
||||
self.setSearchBar(searchBar)
|
||||
}
|
||||
|
||||
self.searchBar.textUpdated = { [weak contentNode] text, _ in
|
||||
contentNode?.searchTextUpdated(text: text)
|
||||
}
|
||||
self.searchBar.tokensUpdated = { [weak contentNode] tokens in
|
||||
contentNode?.searchTokensUpdated(tokens: tokens)
|
||||
}
|
||||
self.searchBar.cancel = { [weak self] in
|
||||
self?.isDeactivating = true
|
||||
cancel()
|
||||
}
|
||||
self.searchBar.clearPrefix = { [weak contentNode] in
|
||||
contentNode?.searchTextClearPrefix()
|
||||
}
|
||||
self.searchBar.clearTokens = { [weak contentNode] in
|
||||
contentNode?.searchTextClearTokens()
|
||||
}
|
||||
self.contentNode.cancel = { [weak self] in
|
||||
self?.isDeactivating = true
|
||||
cancel()
|
||||
}
|
||||
self.contentNode.dismissInput = { [weak self] in
|
||||
self?.searchBar.deactivate(clear: false)
|
||||
self?.searchBar?.deactivate(clear: false)
|
||||
}
|
||||
|
||||
var isFirstTime = true
|
||||
self.contentNode.setQuery = { [weak self] prefix, tokens, query in
|
||||
if let strongSelf = self {
|
||||
strongSelf.searchBar.prefixString = prefix
|
||||
let previousTokens = strongSelf.searchBar.tokens
|
||||
strongSelf.searchBar.tokens = tokens
|
||||
strongSelf.searchBar.text = query
|
||||
if let strongSelf = self, let searchBar = strongSelf.searchBar {
|
||||
searchBar.prefixString = prefix
|
||||
let previousTokens = searchBar.tokens
|
||||
searchBar.tokens = tokens
|
||||
searchBar.text = query
|
||||
if previousTokens.count < tokens.count && !isFirstTime {
|
||||
if let lastToken = tokens.last, !lastToken.permanent {
|
||||
strongSelf.searchBar.selectLastToken()
|
||||
searchBar.selectLastToken()
|
||||
}
|
||||
}
|
||||
isFirstTime = false
|
||||
}
|
||||
}
|
||||
if let placeholder = placeholder {
|
||||
self.searchBar.placeholderString = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: presentationData.theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
|
||||
self.searchBar?.placeholderString = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: presentationData.theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
|
||||
}
|
||||
self.contentNode.setPlaceholder = { [weak self] string in
|
||||
guard string != self?.searchBar.placeholderString?.string else {
|
||||
guard string != self?.searchBar?.placeholderString?.string else {
|
||||
return
|
||||
}
|
||||
if let mutableAttributedString = self?.searchBar.placeholderString?.mutableCopy() as? NSMutableAttributedString {
|
||||
if let mutableAttributedString = self?.searchBar?.placeholderString?.mutableCopy() as? NSMutableAttributedString {
|
||||
mutableAttributedString.mutableString.setString(string)
|
||||
self?.searchBar.placeholderString = mutableAttributedString
|
||||
self?.searchBar?.placeholderString = mutableAttributedString
|
||||
}
|
||||
}
|
||||
|
||||
self.isSearchingDisposable = (contentNode.isSearching
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
self?.searchBar.activity = value
|
||||
self?.searchBar?.activity = value
|
||||
})
|
||||
|
||||
if self.contentNode.hasDim {
|
||||
|
|
@ -113,9 +119,32 @@ public final class SearchDisplayController {
|
|||
self.backgroundNode.isTransparent = false
|
||||
}
|
||||
}
|
||||
|
||||
public func setSearchBar(_ searchBar: SearchBarNode) {
|
||||
self.searchBar = searchBar
|
||||
|
||||
searchBar.textUpdated = { [weak contentNode] text, _ in
|
||||
contentNode?.searchTextUpdated(text: text)
|
||||
}
|
||||
searchBar.tokensUpdated = { [weak contentNode] tokens in
|
||||
contentNode?.searchTokensUpdated(tokens: tokens)
|
||||
}
|
||||
searchBar.cancel = { [weak self] in
|
||||
self?.isDeactivating = true
|
||||
self?.cancel()
|
||||
}
|
||||
searchBar.clearPrefix = { [weak contentNode] in
|
||||
contentNode?.searchTextClearPrefix()
|
||||
}
|
||||
searchBar.clearTokens = { [weak contentNode] in
|
||||
contentNode?.searchTextClearTokens()
|
||||
}
|
||||
}
|
||||
|
||||
public func updatePresentationData(_ presentationData: PresentationData) {
|
||||
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: self.hasSeparator, inline: self.inline), strings: presentationData.strings)
|
||||
if !self.searchBarIsExternal {
|
||||
self.searchBar?.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: self.hasSeparator, inline: self.inline), strings: presentationData.strings)
|
||||
}
|
||||
self.contentNode.updatePresentationData(presentationData)
|
||||
|
||||
if self.contentNode.hasDim {
|
||||
|
|
@ -137,21 +166,27 @@ public final class SearchDisplayController {
|
|||
navigationBarOffset = 0.0
|
||||
}
|
||||
var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarOffset), size: CGSize(width: layout.size.width, height: searchBarHeight))
|
||||
if self.searchBarIsExternal {
|
||||
navigationBarFrame.size.height -= 10.0
|
||||
}
|
||||
if layout.statusBarHeight == nil {
|
||||
navigationBarFrame.size.height = 64.0
|
||||
}
|
||||
navigationBarFrame.size.height += 10.0
|
||||
let navigationBarHeight = navigationBarFrame.maxY
|
||||
|
||||
let searchBarFrame: CGRect
|
||||
if case .navigation = self.mode {
|
||||
searchBarFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 54.0)
|
||||
} else {
|
||||
searchBarFrame = navigationBarFrame
|
||||
if !self.searchBarIsExternal, let searchBar = self.searchBar {
|
||||
let searchBarFrame: CGRect
|
||||
if case .navigation = self.mode {
|
||||
searchBarFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 54.0)
|
||||
} else {
|
||||
searchBarFrame = navigationBarFrame
|
||||
}
|
||||
transition.updateFrame(node: searchBar, frame: searchBarFrame)
|
||||
searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition)
|
||||
}
|
||||
transition.updateFrame(node: self.searchBar, frame: searchBarFrame)
|
||||
self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition)
|
||||
|
||||
self.containerLayout = (layout, navigationBarFrame.maxY)
|
||||
self.containerLayout = (layout, navigationBarHeight)
|
||||
|
||||
let bounds = CGRect(origin: CGPoint(), size: layout.size)
|
||||
transition.updateFrame(node: self.backgroundNode, frame: bounds.insetBy(dx: -20.0, dy: -20.0))
|
||||
|
|
@ -205,18 +240,20 @@ public final class SearchDisplayController {
|
|||
self.backgroundNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
if !self.contentNode.hasDim {
|
||||
if let placeholder = placeholder {
|
||||
self.searchBar.placeholderString = placeholder.placeholderString
|
||||
}
|
||||
} else {
|
||||
if let placeholder = placeholder {
|
||||
let initialTextBackgroundFrame = placeholder.convert(placeholder.backgroundNode.frame, to: nil)
|
||||
let contentNodePosition = self.backgroundNode.layer.position
|
||||
if contentNode.animateBackgroundAppearance {
|
||||
self.backgroundNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - contentNavigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
if !self.searchBarIsExternal {
|
||||
if !self.contentNode.hasDim {
|
||||
if let placeholder = placeholder {
|
||||
self.searchBar?.placeholderString = placeholder.placeholderString
|
||||
}
|
||||
} else {
|
||||
if let placeholder = placeholder {
|
||||
let initialTextBackgroundFrame = placeholder.convert(placeholder.backgroundNode.frame, to: nil)
|
||||
let contentNodePosition = self.backgroundNode.layer.position
|
||||
if contentNode.animateBackgroundAppearance {
|
||||
self.backgroundNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - contentNavigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
self.searchBar?.placeholderString = placeholder.placeholderString
|
||||
}
|
||||
self.searchBar.placeholderString = placeholder.placeholderString
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -240,39 +277,49 @@ public final class SearchDisplayController {
|
|||
navigationBarFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 54.0)
|
||||
}
|
||||
|
||||
self.searchBar.frame = navigationBarFrame
|
||||
insertSubnode(self.searchBar, true)
|
||||
self.searchBar.layout()
|
||||
|
||||
if focus {
|
||||
self.searchBar.activate()
|
||||
if !self.searchBarIsExternal, let searchBar = self.searchBar {
|
||||
searchBar.frame = navigationBarFrame
|
||||
insertSubnode(searchBar, true)
|
||||
searchBar.layout()
|
||||
|
||||
if focus {
|
||||
searchBar.activate()
|
||||
}
|
||||
}
|
||||
|
||||
if let placeholder = placeholder {
|
||||
self.searchBar.animateIn(from: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
if !self.searchBarIsExternal, let searchBar = self.searchBar {
|
||||
searchBar.animateIn(from: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
if self.contentNode.hasDim {
|
||||
self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
}
|
||||
} else {
|
||||
self.searchBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
if !self.searchBarIsExternal, let searchBar = self.searchBar {
|
||||
searchBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
}
|
||||
self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public func deactivate(placeholder: SearchBarPlaceholderNode?, animated: Bool = true) {
|
||||
self.searchBar.deactivate(clear: false)
|
||||
if let searchBar = self.searchBar {
|
||||
searchBar.deactivate(clear: false)
|
||||
}
|
||||
|
||||
let searchBar = self.searchBar
|
||||
if let placeholder = placeholder {
|
||||
searchBar.transitionOut(to: placeholder, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate, completion: {
|
||||
[weak searchBar] in
|
||||
searchBar?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
searchBar.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak searchBar] finished in
|
||||
if finished {
|
||||
if !self.searchBarIsExternal, let searchBar = self.searchBar {
|
||||
if let placeholder = placeholder {
|
||||
searchBar.transitionOut(to: placeholder, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate, completion: {
|
||||
[weak searchBar] in
|
||||
searchBar?.removeFromSupernode()
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
searchBar.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak searchBar] finished in
|
||||
if finished {
|
||||
searchBar?.removeFromSupernode()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let backgroundNode = self.backgroundNode
|
||||
|
|
|
|||
|
|
@ -18,15 +18,18 @@ final class TabBarControllerNode: ASDisplayNode {
|
|||
let layout: ContainerViewLayout
|
||||
let toolbar: Toolbar?
|
||||
let isTabBarHidden: Bool
|
||||
let currentControllerSearchState: ViewController.TabBarSearchState?
|
||||
|
||||
init(
|
||||
layout: ContainerViewLayout,
|
||||
toolbar: Toolbar?,
|
||||
isTabBarHidden: Bool
|
||||
isTabBarHidden: Bool,
|
||||
currentControllerSearchState: ViewController.TabBarSearchState?
|
||||
) {
|
||||
self.layout = layout
|
||||
self.toolbar = toolbar
|
||||
self.isTabBarHidden = isTabBarHidden
|
||||
self.currentControllerSearchState = currentControllerSearchState
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,6 +54,7 @@ final class TabBarControllerNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
private var theme: PresentationTheme
|
||||
private var strings: PresentationStrings
|
||||
private let itemSelected: (Int, Bool, [ASDisplayNode]) -> Void
|
||||
private let contextAction: (Int, ContextExtractedContentContainingView, ContextGesture) -> Void
|
||||
|
||||
|
|
@ -60,24 +64,26 @@ final class TabBarControllerNode: ASDisplayNode {
|
|||
private var toolbarNode: ToolbarNode?
|
||||
private let toolbarActionSelected: (ToolbarActionOption) -> Void
|
||||
private let disabledPressed: () -> Void
|
||||
private let activateSearch: () -> Void
|
||||
private let deactivateSearch: () -> Void
|
||||
|
||||
private(set) var tabBarItems: [TabBarNodeItem] = []
|
||||
private(set) var selectedIndex: Int = 0
|
||||
|
||||
private(set) var currentControllerNode: ASDisplayNode?
|
||||
private weak var currentController: ViewController?
|
||||
|
||||
private var layoutResult: LayoutResult?
|
||||
private var isUpdateRequested: Bool = false
|
||||
private var isChangingSelectedIndex: Bool = false
|
||||
|
||||
func setCurrentControllerNode(_ node: ASDisplayNode?) -> () -> Void {
|
||||
guard node !== self.currentControllerNode else {
|
||||
func setCurrentController(_ controller: ViewController?) -> () -> Void {
|
||||
guard controller !== self.currentController else {
|
||||
return {}
|
||||
}
|
||||
|
||||
let previousNode = self.currentControllerNode
|
||||
self.currentControllerNode = node
|
||||
if let currentControllerNode = self.currentControllerNode {
|
||||
let previousNode = self.currentController?.displayNode
|
||||
self.currentController = controller
|
||||
if let currentControllerNode = self.currentController?.displayNode {
|
||||
if let previousNode {
|
||||
self.insertSubnode(currentControllerNode, aboveSubnode: previousNode)
|
||||
} else {
|
||||
|
|
@ -89,14 +95,22 @@ final class TabBarControllerNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
return { [weak self, weak previousNode] in
|
||||
if previousNode !== self?.currentControllerNode {
|
||||
if previousNode !== self?.currentController?.displayNode {
|
||||
previousNode?.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var currentSearchNode: ASDisplayNode? {
|
||||
if let tabBarComponentView = self.tabBarView.view as? TabBarComponent.View {
|
||||
return tabBarComponentView.currentSearchNode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
init(theme: PresentationTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingView, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void, disabledPressed: @escaping () -> Void) {
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingView, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void, disabledPressed: @escaping () -> Void, activateSearch: @escaping () -> Void, deactivateSearch: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.itemSelected = itemSelected
|
||||
self.contextAction = contextAction
|
||||
self.disabledOverlayNode = ASDisplayNode()
|
||||
|
|
@ -104,7 +118,9 @@ final class TabBarControllerNode: ASDisplayNode {
|
|||
self.disabledOverlayNode.alpha = 0.0
|
||||
self.toolbarActionSelected = toolbarActionSelected
|
||||
self.disabledPressed = disabledPressed
|
||||
|
||||
self.activateSearch = activateSearch
|
||||
self.deactivateSearch = deactivateSearch
|
||||
|
||||
super.init()
|
||||
|
||||
self.setViewBlock({
|
||||
|
|
@ -163,7 +179,7 @@ final class TabBarControllerNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, toolbar: Toolbar?, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let params = Params(layout: layout, toolbar: toolbar, isTabBarHidden: self.tabBarHidden)
|
||||
let params = Params(layout: layout, toolbar: toolbar, isTabBarHidden: self.tabBarHidden, currentControllerSearchState: self.currentController?.tabBarSearchState)
|
||||
if let layoutResult = self.layoutResult, layoutResult.params == params {
|
||||
return layoutResult.bottomInset
|
||||
} else {
|
||||
|
|
@ -179,18 +195,27 @@ final class TabBarControllerNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
private func updateImpl(params: Params, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
var options: ContainerViewLayoutInsetOptions = []
|
||||
if params.layout.metrics.widthClass == .regular {
|
||||
options.insert(.input)
|
||||
var panelsBottomInset: CGFloat = params.layout.insets(options: []).bottom
|
||||
if params.layout.metrics.widthClass == .regular, let inputHeight = params.layout.inputHeight, inputHeight != 0.0 {
|
||||
panelsBottomInset = inputHeight + 8.0
|
||||
}
|
||||
|
||||
var bottomInset: CGFloat = params.layout.insets(options: options).bottom
|
||||
if bottomInset == 0.0 {
|
||||
bottomInset = 8.0
|
||||
if panelsBottomInset == 0.0 {
|
||||
panelsBottomInset = 8.0
|
||||
} else {
|
||||
bottomInset = max(bottomInset, 8.0)
|
||||
panelsBottomInset = max(panelsBottomInset, 8.0)
|
||||
}
|
||||
|
||||
var tabBarBottomInset: CGFloat = panelsBottomInset
|
||||
if let currentController = self.currentController {
|
||||
if let tabBarSearchState = currentController.tabBarSearchState, tabBarSearchState.isActive, let inputHeight = params.layout.inputHeight, inputHeight != 0.0 {
|
||||
tabBarBottomInset = max(tabBarBottomInset, inputHeight + 8.0)
|
||||
}
|
||||
}
|
||||
|
||||
var sideInset: CGFloat = 12.0
|
||||
if tabBarBottomInset <= 28.0 {
|
||||
sideInset = 20.0
|
||||
}
|
||||
let sideInset: CGFloat = 20.0
|
||||
|
||||
var selectedId: AnyHashable?
|
||||
if self.selectedIndex < self.tabBarItems.count {
|
||||
|
|
@ -208,6 +233,7 @@ final class TabBarControllerNode: ASDisplayNode {
|
|||
transition: tabBarTransition,
|
||||
component: AnyComponent(TabBarComponent(
|
||||
theme: self.theme,
|
||||
strings: self.strings,
|
||||
items: self.tabBarItems.map { item in
|
||||
let itemId = AnyHashable(ObjectIdentifier(item.item))
|
||||
return TabBarComponent.Item(
|
||||
|
|
@ -230,13 +256,30 @@ final class TabBarControllerNode: ASDisplayNode {
|
|||
}
|
||||
)
|
||||
},
|
||||
search: self.currentController?.tabBarSearchState.flatMap { tabBarSearchState in
|
||||
return TabBarComponent.Search(
|
||||
isActive: tabBarSearchState.isActive,
|
||||
activate: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.activateSearch()
|
||||
},
|
||||
deactivate: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.deactivateSearch()
|
||||
}
|
||||
)
|
||||
},
|
||||
selectedId: selectedId,
|
||||
isTablet: params.layout.metrics.isTablet
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: params.layout.size.width - sideInset * 2.0, height: 100.0)
|
||||
)
|
||||
let tabBarFrame = CGRect(origin: CGPoint(x: floor((params.layout.size.width - tabBarSize.width) * 0.5), y: params.layout.size.height - (self.tabBarHidden ? 0.0 : (tabBarSize.height + bottomInset))), size: tabBarSize)
|
||||
let tabBarFrame = CGRect(origin: CGPoint(x: floor((params.layout.size.width - tabBarSize.width) * 0.5), y: params.layout.size.height - (self.tabBarHidden ? 0.0 : (tabBarSize.height + tabBarBottomInset))), size: tabBarSize)
|
||||
|
||||
if let tabBarComponentView = self.tabBarView.view {
|
||||
if tabBarComponentView.superview == nil {
|
||||
|
|
@ -248,13 +291,13 @@ final class TabBarControllerNode: ASDisplayNode {
|
|||
|
||||
transition.updateFrame(node: self.disabledOverlayNode, frame: tabBarFrame)
|
||||
|
||||
let toolbarHeight = 50.0 + params.layout.insets(options: options).bottom
|
||||
let toolbarHeight = 50.0 + panelsBottomInset
|
||||
let toolbarFrame = CGRect(origin: CGPoint(x: 0.0, y: params.layout.size.height - toolbarHeight), size: CGSize(width: params.layout.size.width, height: toolbarHeight))
|
||||
|
||||
if let toolbar = params.toolbar {
|
||||
if let toolbarNode = self.toolbarNode {
|
||||
transition.updateFrame(node: toolbarNode, frame: toolbarFrame)
|
||||
toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: params.layout.safeInsets.left, rightInset: params.layout.safeInsets.right, additionalSideInsets: params.layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: transition)
|
||||
toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: params.layout.safeInsets.left, rightInset: params.layout.safeInsets.right, additionalSideInsets: params.layout.additionalInsets, bottomInset: panelsBottomInset, toolbar: toolbar, transition: transition)
|
||||
} else {
|
||||
let toolbarNode = ToolbarNode(theme: ToolbarTheme(theme: self.theme), displaySeparator: true, left: { [weak self] in
|
||||
self?.toolbarActionSelected(.left)
|
||||
|
|
@ -264,7 +307,7 @@ final class TabBarControllerNode: ASDisplayNode {
|
|||
self?.toolbarActionSelected(.middle)
|
||||
})
|
||||
toolbarNode.frame = toolbarFrame
|
||||
toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: params.layout.safeInsets.left, rightInset: params.layout.safeInsets.right, additionalSideInsets: params.layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: .immediate)
|
||||
toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: params.layout.safeInsets.left, rightInset: params.layout.safeInsets.right, additionalSideInsets: params.layout.additionalInsets, bottomInset: panelsBottomInset, toolbar: toolbar, transition: .immediate)
|
||||
self.addSubnode(toolbarNode)
|
||||
self.toolbarNode = toolbarNode
|
||||
if transition.isAnimated {
|
||||
|
|
|
|||
|
|
@ -93,9 +93,11 @@ open class TabBarControllerImpl: ViewController, TabBarController {
|
|||
private let pendingControllerDisposable = MetaDisposable()
|
||||
|
||||
private var theme: PresentationTheme
|
||||
private var strings: PresentationStrings
|
||||
|
||||
public init(theme: PresentationTheme) {
|
||||
public init(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
|
|
@ -152,7 +154,7 @@ open class TabBarControllerImpl: ViewController, TabBarController {
|
|||
}
|
||||
|
||||
override open func loadDisplayNode() {
|
||||
self.displayNode = TabBarControllerNode(theme: self.theme, itemSelected: { [weak self] index, longTap, itemNodes in
|
||||
self.displayNode = TabBarControllerNode(theme: self.theme, strings: self.strings, itemSelected: { [weak self] index, longTap, itemNodes in
|
||||
if let strongSelf = self {
|
||||
if longTap, let controller = strongSelf.controllers[index] as? TabBarContainedController {
|
||||
controller.presentTabBarPreviewingController(sourceNodes: itemNodes)
|
||||
|
|
@ -235,6 +237,16 @@ open class TabBarControllerImpl: ViewController, TabBarController {
|
|||
self?.currentController?.toolbarActionSelected(action: action)
|
||||
}, disabledPressed: { [weak self] in
|
||||
self?.currentController?.tabBarDisabledAction()
|
||||
}, activateSearch: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.currentController?.tabBarActivateSearch()
|
||||
}, deactivateSearch: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.currentController?.tabBarDeactivateSearch()
|
||||
})
|
||||
|
||||
self.updateSelectedIndex()
|
||||
|
|
@ -263,7 +275,8 @@ open class TabBarControllerImpl: ViewController, TabBarController {
|
|||
}
|
||||
if let currentController = self.currentController {
|
||||
currentController.willMove(toParent: nil)
|
||||
//self.tabBarControllerNode.currentControllerNode = nil
|
||||
currentController.tabBarSearchStateUpdated = nil
|
||||
currentController.currentTabBarSearchNode = nil
|
||||
|
||||
if animated {
|
||||
currentController.view.layer.animateScale(from: 1.0, to: transitionScale, duration: 0.12, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { completed in
|
||||
|
|
@ -286,7 +299,7 @@ open class TabBarControllerImpl: ViewController, TabBarController {
|
|||
currentController.willMove(toParent: self)
|
||||
self.addChild(currentController)
|
||||
|
||||
let commit = self.tabBarControllerNode.setCurrentControllerNode(currentController.displayNode)
|
||||
let commit = self.tabBarControllerNode.setCurrentController(currentController)
|
||||
if animated {
|
||||
currentController.view.layer.animateScale(from: transitionScale, to: 1.0, duration: 0.15, delay: 0.1, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
currentController.view.layer.allowsGroupOpacity = true
|
||||
|
|
@ -303,6 +316,22 @@ open class TabBarControllerImpl: ViewController, TabBarController {
|
|||
|
||||
currentController.displayNode.recursivelyEnsureDisplaySynchronously(true)
|
||||
self.statusBar.statusBarStyle = currentController.statusBar.statusBarStyle
|
||||
|
||||
currentController.tabBarSearchStateUpdated = { [weak self] transition in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let layout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
currentController.currentTabBarSearchNode = { [weak self] in
|
||||
guard let self else {
|
||||
return nil
|
||||
}
|
||||
return self.tabBarControllerNode.currentSearchNode
|
||||
}
|
||||
}
|
||||
|
||||
if let layout = self.validLayout {
|
||||
|
|
|
|||
|
|
@ -1174,6 +1174,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||
self.touchDownGestureRecognizer = recognizer
|
||||
|
||||
textInputNode.textView.accessibilityHint = self.textPlaceholderNode.attributedText?.string
|
||||
|
||||
self.isAccessibilityContainer = true
|
||||
self.accessibilityElements = [textInputNode.textView]
|
||||
}
|
||||
|
||||
private func textFieldMaxHeight(_ maxHeight: CGFloat, metrics: LayoutMetrics, bottomInset: CGFloat) -> CGFloat {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "ChatListFilterTabContainerNode",
|
||||
module_name = "ChatListFilterTabContainerNode",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
"//submodules/TelegramUI/Components/LiquidLens",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -210,7 +210,7 @@ private final class ItemNode: ASDisplayNode {
|
|||
if self.theme !== presentationData.theme {
|
||||
self.theme = presentationData.theme
|
||||
|
||||
self.badgeBackgroundActiveNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.chatList.unreadBadgeActiveBackgroundColor)
|
||||
self.badgeBackgroundActiveNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.chat.inputPanel.panelControlColor)
|
||||
self.badgeBackgroundInactiveNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.chatList.unreadBadgeInactiveBackgroundColor)
|
||||
|
||||
themeUpdated = true
|
||||
|
|
@ -299,10 +299,10 @@ private final class ItemNode: ASDisplayNode {
|
|||
|
||||
if themeUpdated || titleUpdated {
|
||||
self.titleNode.attributedText = title.attributedString(font: Font.medium(14.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
||||
self.titleActiveNode.attributedText = title.attributedString(font: Font.medium(14.0), textColor: presentationData.theme.list.itemAccentColor)
|
||||
self.titleActiveNode.attributedText = title.attributedString(font: Font.medium(14.0), textColor: presentationData.theme.chat.inputPanel.panelControlColor)
|
||||
|
||||
self.shortTitleNode.attributedText = shortTitle.attributedString(font: Font.medium(14.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
||||
self.shortTitleActiveNode.attributedText = shortTitle.attributedString(font: Font.medium(14.0), textColor: presentationData.theme.list.itemAccentColor)
|
||||
self.shortTitleActiveNode.attributedText = shortTitle.attributedString(font: Font.medium(14.0), textColor: presentationData.theme.chat.inputPanel.panelControlColor)
|
||||
}
|
||||
|
||||
if unreadCount != 0 {
|
||||
|
|
@ -438,8 +438,8 @@ public enum ChatListFilterTabEntryId: Hashable {
|
|||
}
|
||||
|
||||
public struct ChatListFilterTabEntryUnreadCount: Equatable {
|
||||
let value: Int
|
||||
let hasUnmuted: Bool
|
||||
public let value: Int
|
||||
public let hasUnmuted: Bool
|
||||
|
||||
public init(value: Int, hasUnmuted: Bool) {
|
||||
self.value = value
|
||||
|
|
@ -482,14 +482,14 @@ public enum ChatListFilterTabEntry: Equatable {
|
|||
public final class ChatListFilterTabContainerNode: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
private let scrollNode: ASScrollNode
|
||||
private let selectedLineNode: ASImageNode
|
||||
private let selectedBackgroundNode: ASImageNode
|
||||
private var itemNodes: [ChatListFilterTabEntryId: ItemNode] = [:]
|
||||
|
||||
public var tabSelected: ((ChatListFilterTabEntryId, Bool) -> Void)?
|
||||
var tabRequestedDeletion: ((ChatListFilterTabEntryId) -> Void)?
|
||||
var addFilter: (() -> Void)?
|
||||
var contextGesture: ((Int32?, ContextExtractedContentContainingNode, ContextGesture, Bool) -> Void)?
|
||||
var presentPremiumTip: (() -> Void)?
|
||||
public var tabRequestedDeletion: ((ChatListFilterTabEntryId) -> Void)?
|
||||
public var addFilter: (() -> Void)?
|
||||
public var contextGesture: ((Int32?, ContextExtractedContentContainingNode, ContextGesture, Bool) -> Void)?
|
||||
public var presentPremiumTip: (() -> Void)?
|
||||
|
||||
private var reorderingGesture: ReorderingGestureRecognizer?
|
||||
private var reorderingItem: ChatListFilterTabEntryId?
|
||||
|
|
@ -501,7 +501,7 @@ public final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||
|
||||
private var currentParams: (size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, isReordering: Bool, isEditing: Bool, canReorderAllChats: Bool, filtersLimit: Int32?, transitionFraction: CGFloat, presentationData: PresentationData)?
|
||||
|
||||
var reorderedFilterIds: [Int32]? {
|
||||
public var reorderedFilterIds: [Int32]? {
|
||||
return self.reorderedItemIds.flatMap {
|
||||
$0.compactMap {
|
||||
switch $0 {
|
||||
|
|
@ -533,9 +533,9 @@ public final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||
self.context = context
|
||||
self.scrollNode = ASScrollNode()
|
||||
|
||||
self.selectedLineNode = ASImageNode()
|
||||
self.selectedLineNode.displaysAsynchronously = false
|
||||
self.selectedLineNode.displayWithoutProcessing = true
|
||||
self.selectedBackgroundNode = ASImageNode()
|
||||
self.selectedBackgroundNode.displaysAsynchronously = false
|
||||
self.selectedBackgroundNode.displayWithoutProcessing = true
|
||||
|
||||
super.init()
|
||||
|
||||
|
|
@ -549,7 +549,7 @@ public final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
self.addSubnode(self.scrollNode)
|
||||
self.scrollNode.addSubnode(self.selectedLineNode)
|
||||
self.scrollNode.addSubnode(self.selectedBackgroundNode)
|
||||
|
||||
let reorderingGesture = ReorderingGestureRecognizer(shouldBegin: { [weak self] point in
|
||||
guard let strongSelf = self else {
|
||||
|
|
@ -677,7 +677,7 @@ public final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||
private var previousSelectedFrame: CGRect?
|
||||
|
||||
public func cancelAnimations() {
|
||||
self.selectedLineNode.layer.removeAllAnimations()
|
||||
self.selectedBackgroundNode.layer.removeAllAnimations()
|
||||
self.scrollNode.layer.removeAllAnimations()
|
||||
}
|
||||
|
||||
|
|
@ -695,14 +695,7 @@ public final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||
let previousContentWidth = self.scrollNode.view.contentSize.width
|
||||
|
||||
if self.currentParams?.presentationData.theme !== presentationData.theme {
|
||||
self.selectedLineNode.image = generateImage(CGSize(width: 5.0, height: 3.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: 4.0, height: 4.0)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - 4.0, y: 0.0), size: CGSize(width: 4.0, height: 4.0)))
|
||||
context.fill(CGRect(x: 2.0, y: 0.0, width: size.width - 4.0, height: 4.0))
|
||||
context.fill(CGRect(x: 0.0, y: 2.0, width: size.width, height: 2.0))
|
||||
})?.resizableImage(withCapInsets: UIEdgeInsets(top: 3.0, left: 3.0, bottom: 0.0, right: 3.0), resizingMode: .stretch)
|
||||
self.selectedBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 36.0, color: presentationData.theme.chatList.itemHighlightedBackgroundColor)
|
||||
}
|
||||
|
||||
if isReordering {
|
||||
|
|
@ -864,7 +857,7 @@ public final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||
|
||||
let minSpacing: CGFloat = 26.0
|
||||
|
||||
let resolvedSideInset: CGFloat = 16.0 + sideInset
|
||||
let resolvedSideInset: CGFloat = 25.0 + sideInset
|
||||
var leftOffset: CGFloat = resolvedSideInset
|
||||
|
||||
var longTitlesWidth: CGFloat = resolvedSideInset
|
||||
|
|
@ -937,13 +930,13 @@ public final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
if let selectedFrame = selectedFrame {
|
||||
let wasAdded = self.selectedLineNode.isHidden
|
||||
self.selectedLineNode.isHidden = false
|
||||
let lineFrame = CGRect(origin: CGPoint(x: selectedFrame.minX, y: size.height - 3.0), size: CGSize(width: selectedFrame.width, height: 3.0))
|
||||
let wasAdded = self.selectedBackgroundNode.isHidden
|
||||
self.selectedBackgroundNode.isHidden = false
|
||||
let selectedBackgroundFrame = CGRect(origin: CGPoint(x: selectedFrame.minX - 10.0, y: selectedFrame.minY - floor((36.0 - selectedFrame.height) * 0.5)), size: CGSize(width: selectedFrame.width + 10.0 * 2.0, height: 36.0))
|
||||
if wasAdded {
|
||||
self.selectedLineNode.frame = lineFrame
|
||||
self.selectedBackgroundNode.frame = selectedBackgroundFrame
|
||||
} else {
|
||||
transition.updateFrame(node: self.selectedLineNode, frame: lineFrame)
|
||||
transition.updateFrame(node: self.selectedBackgroundNode, frame: selectedBackgroundFrame)
|
||||
}
|
||||
|
||||
if let previousSelectedFrame = self.previousSelectedFrame {
|
||||
|
|
@ -972,7 +965,7 @@ public final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||
self.previousSelectedAbsFrame = selectedFrame.offsetBy(dx: -self.scrollNode.bounds.minX, dy: 0.0)
|
||||
self.previousSelectedFrame = selectedFrame
|
||||
} else {
|
||||
self.selectedLineNode.isHidden = true
|
||||
self.selectedBackgroundNode.isHidden = true
|
||||
self.previousSelectedAbsFrame = nil
|
||||
self.previousSelectedFrame = nil
|
||||
}
|
||||
|
|
@ -24,6 +24,8 @@ swift_library(
|
|||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/SearchUI",
|
||||
"//submodules/TelegramUI/Components/MoreHeaderButton",
|
||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import AppBundle
|
|||
import StoryPeerListComponent
|
||||
import TelegramCore
|
||||
import MoreHeaderButton
|
||||
import GlassBackgroundComponent
|
||||
|
||||
public final class HeaderNetworkStatusComponent: Component {
|
||||
public enum Content: Equatable {
|
||||
|
|
@ -79,7 +80,6 @@ public final class ChatListHeaderComponent: Component {
|
|||
public let chatListTitle: NetworkStatusTitle?
|
||||
public let leftButton: AnyComponentWithIdentity<NavigationButtonComponentEnvironment>?
|
||||
public let rightButtons: [AnyComponentWithIdentity<NavigationButtonComponentEnvironment>]
|
||||
public let backTitle: String?
|
||||
public let backPressed: (() -> Void)?
|
||||
|
||||
public init(
|
||||
|
|
@ -89,7 +89,6 @@ public final class ChatListHeaderComponent: Component {
|
|||
chatListTitle: NetworkStatusTitle?,
|
||||
leftButton: AnyComponentWithIdentity<NavigationButtonComponentEnvironment>?,
|
||||
rightButtons: [AnyComponentWithIdentity<NavigationButtonComponentEnvironment>],
|
||||
backTitle: String?,
|
||||
backPressed: (() -> Void)?
|
||||
) {
|
||||
self.title = title
|
||||
|
|
@ -98,7 +97,6 @@ public final class ChatListHeaderComponent: Component {
|
|||
self.chatListTitle = chatListTitle
|
||||
self.leftButton = leftButton
|
||||
self.rightButtons = rightButtons
|
||||
self.backTitle = backTitle
|
||||
self.backPressed = backPressed
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +119,7 @@ public final class ChatListHeaderComponent: Component {
|
|||
if lhs.rightButtons != rhs.rightButtons {
|
||||
return false
|
||||
}
|
||||
if lhs.backTitle != rhs.backTitle {
|
||||
if (lhs.backPressed == nil) != (rhs.backPressed == nil) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
@ -226,8 +224,6 @@ public final class ChatListHeaderComponent: Component {
|
|||
private let onPressed: () -> Void
|
||||
|
||||
let arrowView: UIImageView
|
||||
let titleOffsetContainer: UIView
|
||||
let titleView: ImmediateTextView
|
||||
|
||||
private var currentColor: UIColor?
|
||||
|
||||
|
|
@ -235,16 +231,11 @@ public final class ChatListHeaderComponent: Component {
|
|||
self.onPressed = onPressed
|
||||
|
||||
self.arrowView = UIImageView()
|
||||
self.titleOffsetContainer = UIView()
|
||||
self.titleView = ImmediateTextView()
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.addSubview(self.arrowView)
|
||||
|
||||
self.addSubview(self.titleOffsetContainer)
|
||||
self.titleOffsetContainer.addSubview(self.titleView)
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
guard let self else {
|
||||
return
|
||||
|
|
@ -266,37 +257,37 @@ public final class ChatListHeaderComponent: Component {
|
|||
self.onPressed()
|
||||
}
|
||||
|
||||
func update(title: String, theme: PresentationTheme, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
|
||||
let titleText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor)
|
||||
let titleTextUpdated = self.titleView.attributedText != titleText
|
||||
self.titleView.attributedText = titleText
|
||||
let titleSize = self.titleView.updateLayout(CGSize(width: 100.0, height: 44.0))
|
||||
|
||||
self.accessibilityLabel = title
|
||||
func update(theme: PresentationTheme, strings: PresentationStrings, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
|
||||
self.accessibilityLabel = strings.Common_Back
|
||||
self.accessibilityTraits = [.button]
|
||||
|
||||
if self.currentColor != theme.rootController.navigationBar.accentTextColor {
|
||||
self.currentColor = theme.rootController.navigationBar.accentTextColor
|
||||
self.arrowView.image = NavigationBarTheme.generateBackArrowImage(color: theme.rootController.navigationBar.accentTextColor)
|
||||
if self.currentColor != theme.chat.inputPanel.panelControlColor {
|
||||
self.currentColor = theme.chat.inputPanel.panelControlColor
|
||||
let imageSize = CGSize(width: 44.0, height: 44.0)
|
||||
let topRightPoint = CGPoint(x: 24.6, y: 14.0)
|
||||
let centerPoint = CGPoint(x: 17.0, y: imageSize.height * 0.5)
|
||||
self.arrowView.image = generateImage(imageSize, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setStrokeColor(UIColor.white.cgColor)
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineCap(.round)
|
||||
context.setLineJoin(.round)
|
||||
context.move(to: topRightPoint)
|
||||
context.addLine(to: centerPoint)
|
||||
context.addLine(to: CGPoint(x: topRightPoint.x, y: size.height - topRightPoint.y))
|
||||
context.strokePath()
|
||||
})?.withRenderingMode(.alwaysTemplate)
|
||||
self.arrowView.tintColor = theme.chat.inputPanel.panelControlColor
|
||||
}
|
||||
|
||||
let iconSpacing: CGFloat = 8.0
|
||||
let iconOffset: CGFloat = -7.0
|
||||
|
||||
let size = CGSize(width: 44.0, height: availableSize.height)
|
||||
let arrowSize = self.arrowView.image?.size ?? CGSize(width: 13.0, height: 22.0)
|
||||
|
||||
let arrowFrame = CGRect(origin: CGPoint(x: iconOffset - 1.0, y: floor((availableSize.height - arrowSize.height) / 2.0)), size: arrowSize)
|
||||
let arrowFrame = arrowSize.centered(in: CGRect(origin: CGPoint(), size: size))
|
||||
transition.setPosition(view: self.arrowView, position: arrowFrame.center)
|
||||
transition.setBounds(view: self.arrowView, bounds: CGRect(origin: CGPoint(), size: arrowFrame.size))
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: iconOffset - 3.0 + arrowSize.width + iconSpacing, y: floor((availableSize.height - titleSize.height) / 2.0)), size: titleSize)
|
||||
if titleTextUpdated {
|
||||
self.titleView.frame = titleFrame
|
||||
} else {
|
||||
transition.setFrame(view: self.titleView, frame: titleFrame)
|
||||
}
|
||||
|
||||
return CGSize(width: iconOffset + arrowSize.width + iconSpacing + titleSize.width, height: availableSize.height)
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -305,9 +296,9 @@ public final class ChatListHeaderComponent: Component {
|
|||
let openStatusSetup: (UIView) -> Void
|
||||
let toggleIsLocked: () -> Void
|
||||
|
||||
let leftButtonOffsetContainer: UIView
|
||||
let leftButtonsContainer: UIView
|
||||
var leftButtonViews: [AnyHashable: ComponentView<NavigationButtonComponentEnvironment>] = [:]
|
||||
let rightButtonOffsetContainer: UIView
|
||||
let rightButtonsContainer: UIView
|
||||
var rightButtonViews: [AnyHashable: ComponentView<NavigationButtonComponentEnvironment>] = [:]
|
||||
var backButtonView: BackButtonView?
|
||||
|
||||
|
|
@ -324,6 +315,9 @@ public final class ChatListHeaderComponent: Component {
|
|||
|
||||
private(set) var centerContentOffsetX: CGFloat = 0.0
|
||||
private(set) var centerContentOrigin: CGFloat = 0.0
|
||||
|
||||
private(set) var leftButtonsWidth: CGFloat = 0.0
|
||||
private(set) var rightButtonsWidth: CGFloat = 0.0
|
||||
|
||||
init(
|
||||
backPressed: @escaping () -> Void,
|
||||
|
|
@ -334,8 +328,9 @@ public final class ChatListHeaderComponent: Component {
|
|||
self.openStatusSetup = openStatusSetup
|
||||
self.toggleIsLocked = toggleIsLocked
|
||||
|
||||
self.leftButtonOffsetContainer = UIView()
|
||||
self.rightButtonOffsetContainer = UIView()
|
||||
self.leftButtonsContainer = UIView()
|
||||
self.rightButtonsContainer = UIView()
|
||||
|
||||
self.titleOffsetContainer = UIView()
|
||||
self.titleScaleContainer = UIView()
|
||||
|
||||
|
|
@ -345,8 +340,6 @@ public final class ChatListHeaderComponent: Component {
|
|||
|
||||
self.addSubview(self.titleOffsetContainer)
|
||||
self.titleOffsetContainer.addSubview(self.titleScaleContainer)
|
||||
self.addSubview(self.leftButtonOffsetContainer)
|
||||
self.addSubview(self.rightButtonOffsetContainer)
|
||||
|
||||
self.titleScaleContainer.addSubview(self.titleTextView)
|
||||
}
|
||||
|
|
@ -393,12 +386,12 @@ public final class ChatListHeaderComponent: Component {
|
|||
transition.setSublayerTransform(view: self.titleOffsetContainer, transform: transform)
|
||||
}
|
||||
|
||||
func updateNavigationTransitionAsPrevious(nextView: ContentView, fraction: CGFloat, transition: ComponentTransition, completion: @escaping () -> Void) {
|
||||
transition.setBounds(view: self.leftButtonOffsetContainer, bounds: CGRect(origin: CGPoint(x: fraction * self.bounds.width * 0.5, y: 0.0), size: self.leftButtonOffsetContainer.bounds.size), completion: { _ in
|
||||
completion()
|
||||
})
|
||||
transition.setAlpha(view: self.leftButtonOffsetContainer, alpha: pow(1.0 - fraction, 2.0))
|
||||
transition.setAlpha(view: self.rightButtonOffsetContainer, alpha: pow(1.0 - fraction, 2.0))
|
||||
func updateNavigationTransitionAsPrevious(nextView: ContentView, width: CGFloat, fraction: CGFloat, transition: ComponentTransition, completion: @escaping () -> Void) {
|
||||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3)
|
||||
alphaTransition.setAlpha(view: self.leftButtonsContainer, alpha: pow(1.0 - fraction, 2.0))
|
||||
alphaTransition.setBlur(layer: self.leftButtonsContainer.layer, radius: fraction * 10.0)
|
||||
alphaTransition.setAlpha(view: self.rightButtonsContainer, alpha: pow(1.0 - fraction, 2.0))
|
||||
alphaTransition.setBlur(layer: self.rightButtonsContainer.layer, radius: fraction * 10.0)
|
||||
|
||||
if let backButtonView = self.backButtonView {
|
||||
transition.setBounds(view: backButtonView, bounds: CGRect(origin: CGPoint(x: fraction * self.bounds.width * 0.5, y: 0.0), size: backButtonView.bounds.size), completion: { _ in
|
||||
|
|
@ -406,80 +399,53 @@ public final class ChatListHeaderComponent: Component {
|
|||
})
|
||||
}
|
||||
|
||||
if let chatListTitleView = self.chatListTitleView, let nextBackButtonView = nextView.backButtonView {
|
||||
let titleFrame = chatListTitleView.titleNode.view.convert(chatListTitleView.titleNode.bounds, to: self.titleOffsetContainer)
|
||||
let backButtonTitleFrame = nextBackButtonView.convert(nextBackButtonView.titleView.frame, to: nextView)
|
||||
|
||||
let totalOffset = titleFrame.minX - backButtonTitleFrame.minX
|
||||
|
||||
transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: totalOffset * fraction, y: 0.0), size: self.titleOffsetContainer.bounds.size))
|
||||
transition.setAlpha(view: self.titleOffsetContainer, alpha: pow(1.0 - fraction, 2.0))
|
||||
}
|
||||
let totalOffset = -width * fraction
|
||||
|
||||
transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: totalOffset * fraction, y: 0.0), size: self.titleOffsetContainer.bounds.size))
|
||||
transition.setAlpha(view: self.titleOffsetContainer, alpha: pow(1.0 - fraction, 2.0))
|
||||
}
|
||||
|
||||
func updateNavigationTransitionAsNext(previousView: ContentView, storyPeerListView: StoryPeerListComponent.View?, fraction: CGFloat, transition: ComponentTransition, completion: @escaping () -> Void) {
|
||||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3)
|
||||
|
||||
transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: -(1.0 - fraction) * self.bounds.width, y: 0.0), size: self.titleOffsetContainer.bounds.size), completion: { _ in
|
||||
completion()
|
||||
})
|
||||
transition.setAlpha(view: self.rightButtonOffsetContainer, alpha: pow(fraction, 2.0))
|
||||
transition.setBounds(view: self.rightButtonOffsetContainer, bounds: CGRect(origin: CGPoint(x: -(1.0 - fraction) * self.bounds.width, y: 0.0), size: self.rightButtonOffsetContainer.bounds.size))
|
||||
if let backButtonView = self.backButtonView {
|
||||
transition.setScale(view: backButtonView.arrowView, scale: pow(max(0.001, fraction), 2.0))
|
||||
transition.setAlpha(view: backButtonView.arrowView, alpha: pow(fraction, 2.0))
|
||||
|
||||
if let storyPeerListView {
|
||||
let previousTitleFrame = storyPeerListView.titleFrame()
|
||||
let backButtonTitleFrame = backButtonView.convert(backButtonView.titleView.frame, to: self)
|
||||
|
||||
let totalOffset = previousTitleFrame.minX - backButtonTitleFrame.minX
|
||||
|
||||
transition.setBounds(view: backButtonView.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: -totalOffset * (1.0 - fraction), y: 0.0), size: backButtonView.titleOffsetContainer.bounds.size))
|
||||
transition.setAlpha(view: backButtonView.titleOffsetContainer, alpha: pow(fraction, 2.0))
|
||||
} else if let previousChatListTitleView = previousView.chatListTitleView {
|
||||
let previousTitleFrame = previousChatListTitleView.titleNode.view.convert(previousChatListTitleView.titleNode.bounds, to: previousView.titleOffsetContainer)
|
||||
let backButtonTitleFrame = backButtonView.convert(backButtonView.titleView.frame, to: self)
|
||||
|
||||
let totalOffset = previousTitleFrame.minX - backButtonTitleFrame.minX
|
||||
|
||||
transition.setBounds(view: backButtonView.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: -totalOffset * (1.0 - fraction), y: 0.0), size: backButtonView.titleOffsetContainer.bounds.size))
|
||||
transition.setAlpha(view: backButtonView.titleOffsetContainer, alpha: pow(fraction, 2.0))
|
||||
}
|
||||
}
|
||||
alphaTransition.setAlpha(view: self.rightButtonsContainer, alpha: pow(fraction, 2.0))
|
||||
|
||||
alphaTransition.setBlur(layer: self.leftButtonsContainer.layer, radius: (1.0 - fraction) * 10.0)
|
||||
alphaTransition.setBlur(layer: self.rightButtonsContainer.layer, radius: (1.0 - fraction) * 10.0)
|
||||
}
|
||||
|
||||
func updateNavigationTransitionAsPreviousInplace(nextView: ContentView, fraction: CGFloat, transition: ComponentTransition, completion: @escaping () -> Void) {
|
||||
transition.setBounds(view: self.leftButtonOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: self.leftButtonOffsetContainer.bounds.size), completion: { _ in
|
||||
})
|
||||
transition.setAlpha(view: self.leftButtonOffsetContainer, alpha: pow(1.0 - fraction, 2.0))
|
||||
transition.setAlpha(view: self.rightButtonOffsetContainer, alpha: pow(1.0 - fraction, 2.0), completion: { _ in
|
||||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3)
|
||||
|
||||
alphaTransition.setAlpha(view: self.leftButtonsContainer, alpha: pow(1.0 - fraction, 2.0))
|
||||
alphaTransition.setBlur(layer: self.leftButtonsContainer.layer, radius: fraction * 10.0)
|
||||
alphaTransition.setAlpha(view: self.rightButtonsContainer, alpha: pow(1.0 - fraction, 2.0), completion: { _ in
|
||||
completion()
|
||||
})
|
||||
|
||||
if let backButtonView = self.backButtonView {
|
||||
transition.setBounds(view: backButtonView, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backButtonView.bounds.size), completion: { _ in
|
||||
})
|
||||
}
|
||||
alphaTransition.setBlur(layer: self.rightButtonsContainer.layer, radius: fraction * 10.0)
|
||||
|
||||
transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: self.titleOffsetContainer.bounds.size))
|
||||
transition.setAlpha(view: self.titleOffsetContainer, alpha: pow(1.0 - fraction, 2.0))
|
||||
}
|
||||
|
||||
func updateNavigationTransitionAsNextInplace(previousView: ContentView, fraction: CGFloat, transition: ComponentTransition, completion: @escaping () -> Void) {
|
||||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3)
|
||||
|
||||
transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: self.titleOffsetContainer.bounds.size), completion: { _ in
|
||||
completion()
|
||||
})
|
||||
transition.setAlpha(view: self.rightButtonOffsetContainer, alpha: pow(fraction, 2.0))
|
||||
transition.setBounds(view: self.rightButtonOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: self.rightButtonOffsetContainer.bounds.size))
|
||||
if let backButtonView = self.backButtonView {
|
||||
transition.setScale(view: backButtonView.arrowView, scale: pow(max(0.001, fraction), 2.0))
|
||||
transition.setAlpha(view: backButtonView.arrowView, alpha: pow(fraction, 2.0))
|
||||
|
||||
transition.setBounds(view: backButtonView.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backButtonView.titleOffsetContainer.bounds.size))
|
||||
transition.setAlpha(view: backButtonView.titleOffsetContainer, alpha: pow(fraction, 2.0))
|
||||
}
|
||||
alphaTransition.setBlur(layer: self.leftButtonsContainer.layer, radius: (1.0 - fraction) * 10.0)
|
||||
alphaTransition.setAlpha(view: self.leftButtonsContainer, alpha: pow(fraction, 2.0))
|
||||
alphaTransition.setBlur(layer: self.rightButtonsContainer.layer, radius: (1.0 - fraction) * 10.0)
|
||||
alphaTransition.setAlpha(view: self.rightButtonsContainer, alpha: pow(fraction, 2.0))
|
||||
}
|
||||
|
||||
func update(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, content: Content, backTitle: String?, sideInset: CGFloat, sideContentWidth: CGFloat, sideContentFraction: CGFloat, size: CGSize, transition: ComponentTransition) {
|
||||
func update(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, content: Content, displayBackButton: Bool, sideInset: CGFloat, sideContentWidth: CGFloat, sideContentFraction: CGFloat, size: CGSize, transition: ComponentTransition) {
|
||||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3)
|
||||
|
||||
transition.setPosition(view: self.titleOffsetContainer, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5))
|
||||
transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: self.titleOffsetContainer.bounds.origin, size: size))
|
||||
|
||||
|
|
@ -490,11 +456,10 @@ public final class ChatListHeaderComponent: Component {
|
|||
let titleTextUpdated = self.titleTextView.attributedText != titleText
|
||||
self.titleTextView.attributedText = titleText
|
||||
|
||||
let buttonSpacing: CGFloat = 8.0
|
||||
let buttonSpacing: CGFloat = 0.0
|
||||
var nextLeftButtonX: CGFloat = 0.0
|
||||
|
||||
var leftOffset = sideInset
|
||||
|
||||
if let backTitle = backTitle {
|
||||
if displayBackButton {
|
||||
var backButtonTransition = transition
|
||||
let backButtonView: BackButtonView
|
||||
if let current = self.backButtonView {
|
||||
|
|
@ -508,11 +473,14 @@ public final class ChatListHeaderComponent: Component {
|
|||
self.backPressed()
|
||||
})
|
||||
self.backButtonView = backButtonView
|
||||
self.addSubview(backButtonView)
|
||||
self.leftButtonsContainer.addSubview(backButtonView)
|
||||
}
|
||||
let backButtonSize = backButtonView.update(title: backTitle, theme: theme, availableSize: CGSize(width: 100.0, height: size.height), transition: backButtonTransition)
|
||||
backButtonTransition.setFrame(view: backButtonView, frame: CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - backButtonSize.height) / 2.0)), size: backButtonSize))
|
||||
leftOffset += backButtonSize.width + buttonSpacing
|
||||
let backButtonSize = backButtonView.update(theme: theme, strings: strings, availableSize: CGSize(width: 100.0, height: size.height), transition: backButtonTransition)
|
||||
backButtonTransition.setFrame(view: backButtonView, frame: CGRect(origin: CGPoint(x: nextLeftButtonX, y: floor((size.height - backButtonSize.height) / 2.0)), size: backButtonSize))
|
||||
if nextLeftButtonX != 0.0 {
|
||||
nextLeftButtonX += buttonSpacing
|
||||
}
|
||||
nextLeftButtonX += backButtonSize.width
|
||||
} else if let backButtonView = self.backButtonView {
|
||||
self.backButtonView = nil
|
||||
backButtonView.removeFromSuperview()
|
||||
|
|
@ -521,6 +489,10 @@ public final class ChatListHeaderComponent: Component {
|
|||
var validLeftButtons = Set<AnyHashable>()
|
||||
if let leftButton = content.leftButton {
|
||||
validLeftButtons.insert(leftButton.id)
|
||||
|
||||
if nextLeftButtonX != 0.0 {
|
||||
nextLeftButtonX += buttonSpacing
|
||||
}
|
||||
|
||||
var buttonTransition = transition
|
||||
var animateButtonIn = false
|
||||
|
|
@ -541,23 +513,25 @@ public final class ChatListHeaderComponent: Component {
|
|||
},
|
||||
containerSize: CGSize(width: 100.0, height: size.height)
|
||||
)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: nextLeftButtonX, y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize)
|
||||
if let buttonComponentView = buttonView.view {
|
||||
if buttonComponentView.superview == nil {
|
||||
self.leftButtonOffsetContainer.addSubview(buttonComponentView)
|
||||
self.leftButtonsContainer.addSubview(buttonComponentView)
|
||||
}
|
||||
buttonTransition.setFrame(view: buttonComponentView, frame: buttonFrame)
|
||||
if animateButtonIn {
|
||||
transition.animateAlpha(view: buttonComponentView, from: 0.0, to: 1.0)
|
||||
alphaTransition.animateBlur(layer: buttonComponentView.layer, fromRadius: 10.0, toRadius: 0.0)
|
||||
alphaTransition.animateAlpha(view: buttonComponentView, from: 0.0, to: 1.0)
|
||||
}
|
||||
}
|
||||
leftOffset = buttonFrame.maxX + buttonSpacing
|
||||
nextLeftButtonX += buttonSize.width
|
||||
}
|
||||
var removeLeftButtons: [AnyHashable] = []
|
||||
for (id, buttonView) in self.leftButtonViews {
|
||||
if !validLeftButtons.contains(id) {
|
||||
if let buttonComponentView = buttonView.view {
|
||||
transition.setAlpha(view: buttonComponentView, alpha: 0.0, completion: { [weak buttonComponentView] _ in
|
||||
alphaTransition.setBlur(layer: buttonComponentView.layer, radius: 10.0)
|
||||
alphaTransition.setAlpha(view: buttonComponentView, alpha: 0.0, completion: { [weak buttonComponentView] _ in
|
||||
buttonComponentView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
|
@ -568,10 +542,14 @@ public final class ChatListHeaderComponent: Component {
|
|||
self.leftButtonViews.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
var rightOffset = size.width - sideInset
|
||||
var nextRightButtonX: CGFloat = 0.0
|
||||
var validRightButtons = Set<AnyHashable>()
|
||||
for rightButton in content.rightButtons {
|
||||
for rightButton in content.rightButtons.reversed() {
|
||||
validRightButtons.insert(rightButton.id)
|
||||
|
||||
if nextRightButtonX != 0.0 {
|
||||
nextRightButtonX += buttonSpacing
|
||||
}
|
||||
|
||||
var buttonTransition = transition
|
||||
var animateButtonIn = false
|
||||
|
|
@ -592,23 +570,25 @@ public final class ChatListHeaderComponent: Component {
|
|||
},
|
||||
containerSize: CGSize(width: 100.0, height: size.height)
|
||||
)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: rightOffset - buttonSize.width, y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: nextRightButtonX, y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize)
|
||||
if let buttonComponentView = buttonView.view {
|
||||
if buttonComponentView.superview == nil {
|
||||
self.rightButtonOffsetContainer.addSubview(buttonComponentView)
|
||||
self.rightButtonsContainer.addSubview(buttonComponentView)
|
||||
}
|
||||
buttonTransition.setFrame(view: buttonComponentView, frame: buttonFrame)
|
||||
if animateButtonIn {
|
||||
transition.animateAlpha(view: buttonComponentView, from: 0.0, to: 1.0)
|
||||
alphaTransition.animateBlur(layer: buttonComponentView.layer, fromRadius: 10.0, toRadius: 0.0)
|
||||
alphaTransition.animateAlpha(view: buttonComponentView, from: 0.0, to: 1.0)
|
||||
}
|
||||
}
|
||||
rightOffset = buttonFrame.minX - buttonSpacing
|
||||
nextRightButtonX += buttonSize.width
|
||||
}
|
||||
var removeRightButtons: [AnyHashable] = []
|
||||
for (id, buttonView) in self.rightButtonViews {
|
||||
if !validRightButtons.contains(id) {
|
||||
if let buttonComponentView = buttonView.view {
|
||||
transition.setAlpha(view: buttonComponentView, alpha: 0.0, completion: { [weak buttonComponentView] _ in
|
||||
alphaTransition.setBlur(layer: buttonComponentView.layer, radius: 10.0)
|
||||
alphaTransition.setAlpha(view: buttonComponentView, alpha: 0.0, completion: { [weak buttonComponentView] _ in
|
||||
buttonComponentView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
|
@ -618,8 +598,11 @@ public final class ChatListHeaderComponent: Component {
|
|||
for id in removeRightButtons {
|
||||
self.rightButtonViews.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
let commonInset: CGFloat = max(leftOffset, size.width - rightOffset)
|
||||
|
||||
self.leftButtonsWidth = nextLeftButtonX
|
||||
self.rightButtonsWidth = nextRightButtonX
|
||||
|
||||
let commonInset: CGFloat = sideInset + max(nextLeftButtonX, nextRightButtonX) + 8.0
|
||||
let remainingWidth = size.width - commonInset * 2.0
|
||||
|
||||
let titleTextSize = self.titleTextView.updateLayout(CGSize(width: remainingWidth, height: size.height))
|
||||
|
|
@ -662,10 +645,10 @@ public final class ChatListHeaderComponent: Component {
|
|||
}
|
||||
|
||||
var centerContentLeftInset: CGFloat = 0.0
|
||||
centerContentLeftInset = leftOffset - 4.0
|
||||
centerContentLeftInset = nextLeftButtonX + 4.0
|
||||
|
||||
var centerContentRightInset: CGFloat = 0.0
|
||||
centerContentRightInset = size.width - rightOffset - 8.0
|
||||
centerContentRightInset = nextRightButtonX + 20.0
|
||||
|
||||
var centerContentWidth: CGFloat = 0.0
|
||||
var centerContentOffsetX: CGFloat = 0.0
|
||||
|
|
@ -690,10 +673,8 @@ public final class ChatListHeaderComponent: Component {
|
|||
let titleContentRect = chatListTitleView.updateLayout(size: chatListTitleContentSize, clearBounds: CGRect(origin: CGPoint(), size: chatListTitleContentSize), transition: transition.containedViewLayoutTransition)
|
||||
centerContentWidth = floor((chatListTitleContentSize.width * 0.5 - titleContentRect.minX) * 2.0)
|
||||
|
||||
//sideWidth + centerWidth + centerOffset = size.width
|
||||
//let centerOffset = -(size.width - (sideContentWidth + centerContentWidth)) * 0.5 + size.width * 0.5
|
||||
let centerOffset = sideContentWidth * 0.5
|
||||
centerContentOffsetX = -max(0.0, centerOffset + titleContentRect.maxX - 2.0 - rightOffset)
|
||||
centerContentOffsetX = -max(0.0, centerOffset + titleContentRect.maxX - 2.0 - (size.width - sideInset - nextRightButtonX))
|
||||
|
||||
chatListTitleView.openStatusSetup = { [weak self] sourceView in
|
||||
guard let self else {
|
||||
|
|
@ -741,6 +722,11 @@ public final class ChatListHeaderComponent: Component {
|
|||
private var primaryContentView: ContentView?
|
||||
private var secondaryContentView: ContentView?
|
||||
private var storyOffsetFraction: CGFloat = 0.0
|
||||
|
||||
private let leftButtonsContainer: UIView
|
||||
private let rightButtonsContainer: UIView
|
||||
private var leftButtonsBackgroundContainer: GlassBackgroundView?
|
||||
private var rightButtonsBackgroundContainer: GlassBackgroundView?
|
||||
|
||||
private let storyPeerListExternalState = StoryPeerListComponent.ExternalState()
|
||||
private var storyPeerList: ComponentView<Empty>?
|
||||
|
|
@ -753,6 +739,10 @@ public final class ChatListHeaderComponent: Component {
|
|||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.leftButtonsContainer = UIView()
|
||||
self.rightButtonsContainer = UIView()
|
||||
self.rightButtonsContainer.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0)
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.storyOffsetFraction = 1.0
|
||||
|
|
@ -766,10 +756,6 @@ public final class ChatListHeaderComponent: Component {
|
|||
return self.effectiveContentView?.backButtonView?.arrowView
|
||||
}
|
||||
|
||||
public var backButtonTitleView: UIView? {
|
||||
return self.effectiveContentView?.backButtonView?.titleView
|
||||
}
|
||||
|
||||
public var rightButtonView: UIView? {
|
||||
return self.effectiveContentView?.rightButtonViews.first?.value.view
|
||||
}
|
||||
|
|
@ -795,18 +781,6 @@ public final class ChatListHeaderComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
public func makeTransitionBackButtonView(accentColor: UIColor) -> UIView? {
|
||||
if let backButtonTitleView = self.backButtonTitleView as? ImmediateTextView {
|
||||
let view = ImmediateTextView()
|
||||
view.attributedText = NSAttributedString(string: backButtonTitleView.attributedText?.string ?? "", font: Font.regular(17.0), textColor: accentColor)
|
||||
let _ = view.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||
view.frame = backButtonTitleView.convert(backButtonTitleView.bounds, to: self)
|
||||
return view
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func storyPeerListView() -> StoryPeerListComponent.View? {
|
||||
return self.storyPeerList?.view as? StoryPeerListComponent.View
|
||||
}
|
||||
|
|
@ -848,8 +822,8 @@ public final class ChatListHeaderComponent: Component {
|
|||
let previousComponent = self.component
|
||||
self.component = component
|
||||
|
||||
var primaryContentTransition = transition
|
||||
if var primaryContent = component.primaryContent {
|
||||
var primaryContentTransition = transition
|
||||
let primaryContentView: ContentView
|
||||
if let current = self.primaryContentView {
|
||||
primaryContentView = current
|
||||
|
|
@ -877,6 +851,8 @@ public final class ChatListHeaderComponent: Component {
|
|||
)
|
||||
self.primaryContentView = primaryContentView
|
||||
self.addSubview(primaryContentView)
|
||||
self.leftButtonsContainer.addSubview(primaryContentView.leftButtonsContainer)
|
||||
self.rightButtonsContainer.addSubview(primaryContentView.rightButtonsContainer)
|
||||
}
|
||||
|
||||
let sideContentWidth: CGFloat = 0.0
|
||||
|
|
@ -889,18 +865,19 @@ public final class ChatListHeaderComponent: Component {
|
|||
chatListTitle: nil,
|
||||
leftButton: primaryContent.leftButton,
|
||||
rightButtons: primaryContent.rightButtons,
|
||||
backTitle: primaryContent.backTitle,
|
||||
backPressed: primaryContent.backPressed
|
||||
)
|
||||
}
|
||||
|
||||
primaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: primaryContent, backTitle: primaryContent.backTitle, sideInset: component.sideInset, sideContentWidth: sideContentWidth, sideContentFraction: (1.0 - component.storiesFraction), size: availableSize, transition: primaryContentTransition)
|
||||
primaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: primaryContent, displayBackButton: primaryContent.backPressed != nil, sideInset: component.sideInset, sideContentWidth: sideContentWidth, sideContentFraction: (1.0 - component.storiesFraction), size: availableSize, transition: primaryContentTransition)
|
||||
primaryContentTransition.setFrame(view: primaryContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
primaryContentView.updateContentOffsetFraction(contentOffsetFraction: 1.0 - self.storyOffsetFraction, transition: primaryContentTransition)
|
||||
} else if let primaryContentView = self.primaryContentView {
|
||||
self.primaryContentView = nil
|
||||
primaryContentView.removeFromSuperview()
|
||||
primaryContentView.leftButtonsContainer.removeFromSuperview()
|
||||
primaryContentView.rightButtonsContainer.removeFromSuperview()
|
||||
}
|
||||
|
||||
var storyListTransition = transition
|
||||
|
|
@ -991,13 +968,16 @@ public final class ChatListHeaderComponent: Component {
|
|||
)
|
||||
}
|
||||
|
||||
var secondaryContentTransition = transition
|
||||
var secondaryContentIsAnimatingIn = false
|
||||
var removedSecondaryContentView: ContentView?
|
||||
if let secondaryContent = component.secondaryContent {
|
||||
var secondaryContentTransition = transition
|
||||
let secondaryContentView: ContentView
|
||||
if let current = self.secondaryContentView {
|
||||
secondaryContentView = current
|
||||
} else {
|
||||
secondaryContentTransition = .immediate
|
||||
secondaryContentIsAnimatingIn = true
|
||||
secondaryContentView = ContentView(
|
||||
backPressed: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
|
|
@ -1019,9 +999,15 @@ public final class ChatListHeaderComponent: Component {
|
|||
}
|
||||
)
|
||||
self.secondaryContentView = secondaryContentView
|
||||
self.addSubview(secondaryContentView)
|
||||
if let primaryContentView = self.primaryContentView {
|
||||
self.insertSubview(secondaryContentView, aboveSubview: primaryContentView)
|
||||
} else {
|
||||
self.addSubview(secondaryContentView)
|
||||
}
|
||||
self.leftButtonsContainer.addSubview(secondaryContentView.leftButtonsContainer)
|
||||
self.rightButtonsContainer.addSubview(secondaryContentView.rightButtonsContainer)
|
||||
}
|
||||
secondaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: secondaryContent, backTitle: component.primaryContent?.navigationBackTitle ?? component.primaryContent?.title, sideInset: component.sideInset, sideContentWidth: 0.0, sideContentFraction: 0.0, size: availableSize, transition: secondaryContentTransition)
|
||||
secondaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: secondaryContent, displayBackButton: true, sideInset: component.sideInset, sideContentWidth: 0.0, sideContentFraction: 0.0, size: availableSize, transition: secondaryContentTransition)
|
||||
secondaryContentTransition.setFrame(view: secondaryContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
secondaryContentView.updateContentOffsetFraction(contentOffsetFraction: 1.0 - self.storyOffsetFraction, transition: secondaryContentTransition)
|
||||
|
|
@ -1032,7 +1018,7 @@ public final class ChatListHeaderComponent: Component {
|
|||
primaryContentView.updateNavigationTransitionAsPreviousInplace(nextView: secondaryContentView, fraction: 0.0, transition: .immediate, completion: {})
|
||||
secondaryContentView.updateNavigationTransitionAsNextInplace(previousView: primaryContentView, fraction: 0.0, transition: .immediate, completion: {})
|
||||
} else {
|
||||
primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, fraction: 0.0, transition: .immediate, completion: {})
|
||||
primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, width: availableSize.width, fraction: 0.0, transition: .immediate, completion: {})
|
||||
secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, storyPeerListView: self.storyPeerListView(), fraction: 0.0, transition: .immediate, completion: {})
|
||||
}
|
||||
}
|
||||
|
|
@ -1041,26 +1027,33 @@ public final class ChatListHeaderComponent: Component {
|
|||
primaryContentView.updateNavigationTransitionAsPreviousInplace(nextView: secondaryContentView, fraction: component.secondaryTransition, transition: transition, completion: {})
|
||||
secondaryContentView.updateNavigationTransitionAsNextInplace(previousView: primaryContentView, fraction: component.secondaryTransition, transition: transition, completion: {})
|
||||
} else {
|
||||
primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, fraction: component.secondaryTransition, transition: transition, completion: {})
|
||||
primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, width: availableSize.width, fraction: component.secondaryTransition, transition: transition, completion: {})
|
||||
secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, storyPeerListView: self.storyPeerListView(), fraction: component.secondaryTransition, transition: transition, completion: {})
|
||||
}
|
||||
}
|
||||
} else if let secondaryContentView = self.secondaryContentView {
|
||||
self.secondaryContentView = nil
|
||||
removedSecondaryContentView = secondaryContentView
|
||||
|
||||
if let primaryContentView = self.primaryContentView {
|
||||
if self.storyOffsetFraction < 0.8 {
|
||||
primaryContentView.updateNavigationTransitionAsPreviousInplace(nextView: secondaryContentView, fraction: 0.0, transition: transition, completion: {})
|
||||
secondaryContentView.updateNavigationTransitionAsNextInplace(previousView: primaryContentView, fraction: 0.0, transition: transition, completion: { [weak secondaryContentView] in
|
||||
secondaryContentView?.leftButtonsContainer.removeFromSuperview()
|
||||
secondaryContentView?.rightButtonsContainer.removeFromSuperview()
|
||||
secondaryContentView?.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, fraction: 0.0, transition: transition, completion: {})
|
||||
primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, width: availableSize.width, fraction: 0.0, transition: transition, completion: {})
|
||||
secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, storyPeerListView: self.storyPeerListView(), fraction: 0.0, transition: transition, completion: { [weak secondaryContentView] in
|
||||
secondaryContentView?.leftButtonsContainer.removeFromSuperview()
|
||||
secondaryContentView?.rightButtonsContainer.removeFromSuperview()
|
||||
secondaryContentView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
secondaryContentView.leftButtonsContainer.removeFromSuperview()
|
||||
secondaryContentView.rightButtonsContainer.removeFromSuperview()
|
||||
secondaryContentView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
|
@ -1070,18 +1063,10 @@ public final class ChatListHeaderComponent: Component {
|
|||
self.addSubview(storyPeerListComponentView)
|
||||
}
|
||||
|
||||
//let storyPeerListMinOffset: CGFloat = -7.0
|
||||
let storyPeerListMaxOffset: CGFloat = availableSize.height + 2.0
|
||||
|
||||
//let storyPeerListPosition: CGFloat = storyPeerListMinOffset * (1.0 - component.storiesFraction) + storyPeerListMaxOffset * component.storiesFraction
|
||||
|
||||
var storiesX: CGFloat = 0.0
|
||||
if let nextBackButtonView = self.secondaryContentView?.backButtonView {
|
||||
let backButtonTitleFrame = nextBackButtonView.convert(nextBackButtonView.titleView.frame, to: self)
|
||||
let storyListTitleFrame = storyPeerListComponentView.titleFrame()
|
||||
|
||||
storiesX += (backButtonTitleFrame.minX - storyListTitleFrame.minX) * component.secondaryTransition
|
||||
}
|
||||
storiesX -= availableSize.width * component.secondaryTransition
|
||||
|
||||
storyListTransition.setFrame(view: storyPeerListComponentView, frame: CGRect(origin: CGPoint(x: storiesX, y: storyPeerListMaxOffset), size: CGSize(width: availableSize.width, height: 79.0)))
|
||||
|
||||
|
|
@ -1090,6 +1075,84 @@ public final class ChatListHeaderComponent: Component {
|
|||
let storyListAlpha: CGFloat = (1.0 - component.secondaryTransition) * storyListNormalAlpha
|
||||
storyListTransition.setAlpha(view: storyPeerListComponentView, alpha: storyListAlpha)
|
||||
}
|
||||
|
||||
var leftButtonsEffectiveWidth: CGFloat = 0.0
|
||||
var rightButtonsEffectiveWidth: CGFloat = 0.0
|
||||
if let primaryContentView = self.primaryContentView, let secondaryContentView = self.secondaryContentView {
|
||||
|
||||
leftButtonsEffectiveWidth = primaryContentView.leftButtonsWidth * (1.0 - component.secondaryTransition) + secondaryContentView.leftButtonsWidth * component.secondaryTransition
|
||||
rightButtonsEffectiveWidth = primaryContentView.rightButtonsWidth * (1.0 - component.secondaryTransition) + secondaryContentView.rightButtonsWidth * component.secondaryTransition
|
||||
|
||||
primaryContentTransition.setFrame(view: primaryContentView.leftButtonsContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: max(44.0, primaryContentView.leftButtonsWidth), height: 44.0)))
|
||||
secondaryContentTransition.setFrame(view: secondaryContentView.leftButtonsContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: max(44.0, secondaryContentView.leftButtonsWidth), height: 44.0)))
|
||||
|
||||
primaryContentTransition.setFrame(view: primaryContentView.rightButtonsContainer, frame: CGRect(origin: CGPoint(x: rightButtonsEffectiveWidth - primaryContentView.rightButtonsWidth, y: 0.0), size: CGSize(width: max(44.0, primaryContentView.rightButtonsWidth), height: 44.0)))
|
||||
|
||||
if secondaryContentIsAnimatingIn {
|
||||
secondaryContentView.rightButtonsContainer.frame = CGRect(origin: CGPoint(x: self.rightButtonsContainer.bounds.width - secondaryContentView.rightButtonsWidth, y: 0.0), size: CGSize(width: max(44.0, secondaryContentView.rightButtonsWidth), height: 44.0))
|
||||
}
|
||||
transition.setFrame(view: secondaryContentView.rightButtonsContainer, frame: CGRect(origin: CGPoint(x: rightButtonsEffectiveWidth - secondaryContentView.rightButtonsWidth, y: 0.0), size: CGSize(width: max(44.0, secondaryContentView.rightButtonsWidth), height: 44.0)))
|
||||
} else if let primaryContentView = self.primaryContentView {
|
||||
leftButtonsEffectiveWidth = primaryContentView.leftButtonsWidth
|
||||
rightButtonsEffectiveWidth = primaryContentView.rightButtonsWidth
|
||||
|
||||
primaryContentTransition.setFrame(view: primaryContentView.leftButtonsContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: max(44.0, primaryContentView.leftButtonsWidth), height: 44.0)))
|
||||
primaryContentTransition.setFrame(view: primaryContentView.rightButtonsContainer, frame: CGRect(origin: CGPoint(x: rightButtonsEffectiveWidth - primaryContentView.rightButtonsWidth, y: 0.0), size: CGSize(width: max(44.0, primaryContentView.rightButtonsWidth), height: 44.0)))
|
||||
|
||||
if let removedSecondaryContentView {
|
||||
transition.setFrame(view: removedSecondaryContentView.rightButtonsContainer, frame: CGRect(origin: CGPoint(x: rightButtonsEffectiveWidth - removedSecondaryContentView.rightButtonsWidth, y: 0.0), size: CGSize(width: max(44.0, removedSecondaryContentView.rightButtonsWidth), height: 44.0)))
|
||||
}
|
||||
}
|
||||
|
||||
if leftButtonsEffectiveWidth != 0.0 {
|
||||
let leftButtonsBackgroundContainer: GlassBackgroundView
|
||||
var leftButtonsBackgroundContainerTransition = transition
|
||||
if let current = self.leftButtonsBackgroundContainer {
|
||||
leftButtonsBackgroundContainer = current
|
||||
} else {
|
||||
leftButtonsBackgroundContainerTransition = leftButtonsBackgroundContainerTransition.withAnimation(.none)
|
||||
leftButtonsBackgroundContainer = GlassBackgroundView()
|
||||
self.leftButtonsBackgroundContainer = leftButtonsBackgroundContainer
|
||||
self.addSubview(leftButtonsBackgroundContainer)
|
||||
leftButtonsBackgroundContainer.contentView.addSubview(self.leftButtonsContainer)
|
||||
}
|
||||
let leftButtonsContainerFrame = CGRect(origin: CGPoint(x: component.sideInset, y: 0.0), size: CGSize(width: max(44.0, leftButtonsEffectiveWidth), height: 44.0))
|
||||
leftButtonsBackgroundContainerTransition.setFrame(view: leftButtonsBackgroundContainer, frame: leftButtonsContainerFrame)
|
||||
leftButtonsBackgroundContainer.update(size: leftButtonsContainerFrame.size, cornerRadius: leftButtonsContainerFrame.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: leftButtonsBackgroundContainerTransition)
|
||||
leftButtonsBackgroundContainerTransition.setFrame(view: self.leftButtonsContainer, frame: CGRect(origin: CGPoint(), size: leftButtonsContainerFrame.size))
|
||||
} else {
|
||||
if let leftButtonsBackgroundContainer = self.leftButtonsBackgroundContainer {
|
||||
self.leftButtonsBackgroundContainer = nil
|
||||
transition.setAlpha(view: leftButtonsBackgroundContainer, alpha: 0.0, completion: { [weak leftButtonsBackgroundContainer] _ in
|
||||
leftButtonsBackgroundContainer?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if rightButtonsEffectiveWidth != 0.0 {
|
||||
let rightButtonsBackgroundContainer: GlassBackgroundView
|
||||
var rightButtonsBackgroundContainerTransition = transition
|
||||
if let current = self.rightButtonsBackgroundContainer {
|
||||
rightButtonsBackgroundContainer = current
|
||||
} else {
|
||||
rightButtonsBackgroundContainerTransition = rightButtonsBackgroundContainerTransition.withAnimation(.none)
|
||||
rightButtonsBackgroundContainer = GlassBackgroundView()
|
||||
self.rightButtonsBackgroundContainer = rightButtonsBackgroundContainer
|
||||
self.addSubview(rightButtonsBackgroundContainer)
|
||||
rightButtonsBackgroundContainer.contentView.addSubview(self.rightButtonsContainer)
|
||||
}
|
||||
let rightButtonsContainerFrame = CGRect(origin: CGPoint(x: availableSize.width - component.sideInset - max(44.0, rightButtonsEffectiveWidth), y: 0.0), size: CGSize(width: max(44.0, rightButtonsEffectiveWidth), height: 44.0))
|
||||
rightButtonsBackgroundContainerTransition.setFrame(view: rightButtonsBackgroundContainer, frame: rightButtonsContainerFrame)
|
||||
rightButtonsBackgroundContainer.update(size: rightButtonsContainerFrame.size, cornerRadius: rightButtonsContainerFrame.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: rightButtonsBackgroundContainerTransition)
|
||||
rightButtonsBackgroundContainerTransition.setFrame(view: self.rightButtonsContainer, frame: CGRect(origin: CGPoint(), size: rightButtonsContainerFrame.size))
|
||||
} else {
|
||||
if let rightButtonsBackgroundContainer = self.rightButtonsBackgroundContainer {
|
||||
self.rightButtonsBackgroundContainer = nil
|
||||
transition.setAlpha(view: rightButtonsBackgroundContainer, alpha: 0.0, completion: { [weak rightButtonsBackgroundContainer] _ in
|
||||
rightButtonsBackgroundContainer?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
|
|
@ -1119,251 +1182,3 @@ public final class ChatListHeaderComponent: Component {
|
|||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public final class NavigationButtonComponentEnvironment: Equatable {
|
||||
public let theme: PresentationTheme
|
||||
|
||||
public init(theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
}
|
||||
|
||||
public static func ==(lhs: NavigationButtonComponentEnvironment, rhs: NavigationButtonComponentEnvironment) -> Bool {
|
||||
if lhs.theme != rhs.theme {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public final class NavigationButtonComponent: Component {
|
||||
public typealias EnvironmentType = NavigationButtonComponentEnvironment
|
||||
|
||||
public enum Content: Equatable {
|
||||
case text(title: String, isBold: Bool)
|
||||
case more
|
||||
case icon(imageName: String)
|
||||
case proxy(status: ChatTitleProxyStatus)
|
||||
}
|
||||
|
||||
public let content: Content
|
||||
public let pressed: (UIView) -> Void
|
||||
public let contextAction: ((UIView, ContextGesture?) -> Void)?
|
||||
|
||||
public init(
|
||||
content: Content,
|
||||
pressed: @escaping (UIView) -> Void,
|
||||
contextAction: ((UIView, ContextGesture?) -> Void)? = nil
|
||||
) {
|
||||
self.content = content
|
||||
self.pressed = pressed
|
||||
self.contextAction = contextAction
|
||||
}
|
||||
|
||||
public static func ==(lhs: NavigationButtonComponent, rhs: NavigationButtonComponent) -> Bool {
|
||||
if lhs.content != rhs.content {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: HighlightTrackingButton {
|
||||
private var textView: ImmediateTextView?
|
||||
|
||||
private var iconView: UIImageView?
|
||||
private var iconImageName: String?
|
||||
|
||||
private var proxyNode: ChatTitleProxyNode?
|
||||
|
||||
private var moreButton: MoreHeaderButton?
|
||||
|
||||
private var component: NavigationButtonComponent?
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if highlighted {
|
||||
self.textView?.alpha = 0.6
|
||||
self.proxyNode?.alpha = 0.6
|
||||
self.iconView?.alpha = 0.6
|
||||
} else {
|
||||
self.textView?.alpha = 1.0
|
||||
self.textView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
|
||||
|
||||
self.proxyNode?.alpha = 1.0
|
||||
self.proxyNode?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
|
||||
|
||||
self.iconView?.alpha = 1.0
|
||||
self.iconView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
self.component?.pressed(self)
|
||||
}
|
||||
|
||||
func update(component: NavigationButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<NavigationButtonComponentEnvironment>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let theme = environment[NavigationButtonComponentEnvironment.self].value.theme
|
||||
var themeUpdated = false
|
||||
if self.theme !== theme {
|
||||
self.theme = theme
|
||||
themeUpdated = true
|
||||
}
|
||||
|
||||
let iconOffset: CGFloat = 4.0
|
||||
|
||||
var textString: NSAttributedString?
|
||||
var imageName: String?
|
||||
var proxyStatus: ChatTitleProxyStatus?
|
||||
var isMore: Bool = false
|
||||
|
||||
switch component.content {
|
||||
case let .text(title, isBold):
|
||||
textString = NSAttributedString(string: title, font: isBold ? Font.bold(17.0) : Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor)
|
||||
case .more:
|
||||
isMore = true
|
||||
case let .icon(imageNameValue):
|
||||
imageName = imageNameValue
|
||||
case let .proxy(status):
|
||||
proxyStatus = status
|
||||
}
|
||||
|
||||
var size = CGSize(width: 0.0, height: availableSize.height)
|
||||
|
||||
if let textString = textString {
|
||||
let textView: ImmediateTextView
|
||||
if let current = self.textView {
|
||||
textView = current
|
||||
} else {
|
||||
textView = ImmediateTextView()
|
||||
textView.isUserInteractionEnabled = false
|
||||
self.textView = textView
|
||||
self.addSubview(textView)
|
||||
}
|
||||
|
||||
textView.attributedText = textString
|
||||
let textSize = textView.updateLayout(availableSize)
|
||||
size.width = textSize.width
|
||||
|
||||
textView.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((availableSize.height - textSize.height) / 2.0)), size: textSize)
|
||||
} else if let textView = self.textView {
|
||||
self.textView = nil
|
||||
textView.removeFromSuperview()
|
||||
}
|
||||
|
||||
if let imageName = imageName {
|
||||
let iconView: UIImageView
|
||||
if let current = self.iconView {
|
||||
iconView = current
|
||||
} else {
|
||||
iconView = UIImageView()
|
||||
iconView.isUserInteractionEnabled = false
|
||||
self.iconView = iconView
|
||||
self.addSubview(iconView)
|
||||
}
|
||||
if self.iconImageName != imageName || themeUpdated {
|
||||
self.iconImageName = imageName
|
||||
iconView.image = generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.rootController.navigationBar.accentTextColor)
|
||||
}
|
||||
|
||||
if let iconSize = iconView.image?.size {
|
||||
size.width = iconSize.width
|
||||
|
||||
iconView.frame = CGRect(origin: CGPoint(x: iconOffset, y: floor((availableSize.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
}
|
||||
} else if let iconView = self.iconView {
|
||||
self.iconView = nil
|
||||
iconView.removeFromSuperview()
|
||||
self.iconImageName = nil
|
||||
}
|
||||
|
||||
if let proxyStatus = proxyStatus {
|
||||
let proxyNode: ChatTitleProxyNode
|
||||
if let current = self.proxyNode {
|
||||
proxyNode = current
|
||||
} else {
|
||||
proxyNode = ChatTitleProxyNode(theme: theme)
|
||||
proxyNode.isUserInteractionEnabled = false
|
||||
self.proxyNode = proxyNode
|
||||
self.addSubnode(proxyNode)
|
||||
}
|
||||
|
||||
let proxySize = CGSize(width: 30.0, height: 30.0)
|
||||
size.width = proxySize.width
|
||||
|
||||
proxyNode.theme = theme
|
||||
proxyNode.status = proxyStatus
|
||||
|
||||
proxyNode.frame = CGRect(origin: CGPoint(x: iconOffset, y: floor((availableSize.height - proxySize.height) / 2.0)), size: proxySize)
|
||||
} else if let proxyNode = self.proxyNode {
|
||||
self.proxyNode = nil
|
||||
proxyNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if isMore {
|
||||
let moreButton: MoreHeaderButton
|
||||
if let current = self.moreButton, !themeUpdated {
|
||||
moreButton = current
|
||||
} else {
|
||||
if let moreButton = self.moreButton {
|
||||
moreButton.removeFromSupernode()
|
||||
self.moreButton = nil
|
||||
}
|
||||
|
||||
moreButton = MoreHeaderButton(color: theme.rootController.navigationBar.buttonColor)
|
||||
moreButton.isUserInteractionEnabled = true
|
||||
moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.rootController.navigationBar.buttonColor)))
|
||||
moreButton.onPressed = { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
self.moreButton?.play()
|
||||
component.pressed(self)
|
||||
}
|
||||
moreButton.contextAction = { [weak self] sourceNode, gesture in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
self.moreButton?.play()
|
||||
component.contextAction?(self, gesture)
|
||||
}
|
||||
self.moreButton = moreButton
|
||||
self.addSubnode(moreButton)
|
||||
}
|
||||
|
||||
let buttonSize = CGSize(width: 26.0, height: 44.0)
|
||||
size.width = buttonSize.width
|
||||
|
||||
moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.rootController.navigationBar.buttonColor)))
|
||||
|
||||
moreButton.frame = CGRect(origin: CGPoint(x: iconOffset, y: floor((availableSize.height - buttonSize.height) / 2.0)), size: buttonSize)
|
||||
} else if let moreButton = self.moreButton {
|
||||
self.moreButton = nil
|
||||
moreButton.removeFromSupernode()
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<NavigationButtonComponentEnvironment>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import SearchUI
|
|||
import AccountContext
|
||||
import TelegramCore
|
||||
import StoryPeerListComponent
|
||||
import EdgeEffect
|
||||
|
||||
public final class ChatListNavigationBar: Component {
|
||||
public final class AnimationHint {
|
||||
|
|
@ -20,14 +21,22 @@ public final class ChatListNavigationBar: Component {
|
|||
self.crossfadeStoryPeers = crossfadeStoryPeers
|
||||
}
|
||||
}
|
||||
|
||||
public struct Search: Equatable {
|
||||
public var isEnabled: Bool
|
||||
|
||||
public init(isEnabled: Bool) {
|
||||
self.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
public let context: AccountContext
|
||||
public let theme: PresentationTheme
|
||||
public let strings: PresentationStrings
|
||||
public let statusBarHeight: CGFloat
|
||||
public let sideInset: CGFloat
|
||||
public let search: Search?
|
||||
public let isSearchActive: Bool
|
||||
public let isSearchEnabled: Bool
|
||||
public let primaryContent: ChatListHeaderComponent.Content?
|
||||
public let secondaryContent: ChatListHeaderComponent.Content?
|
||||
public let secondaryTransition: CGFloat
|
||||
|
|
@ -48,8 +57,8 @@ public final class ChatListNavigationBar: Component {
|
|||
strings: PresentationStrings,
|
||||
statusBarHeight: CGFloat,
|
||||
sideInset: CGFloat,
|
||||
search: Search?,
|
||||
isSearchActive: Bool,
|
||||
isSearchEnabled: Bool,
|
||||
primaryContent: ChatListHeaderComponent.Content?,
|
||||
secondaryContent: ChatListHeaderComponent.Content?,
|
||||
secondaryTransition: CGFloat,
|
||||
|
|
@ -69,8 +78,8 @@ public final class ChatListNavigationBar: Component {
|
|||
self.strings = strings
|
||||
self.statusBarHeight = statusBarHeight
|
||||
self.sideInset = sideInset
|
||||
self.search = search
|
||||
self.isSearchActive = isSearchActive
|
||||
self.isSearchEnabled = isSearchEnabled
|
||||
self.primaryContent = primaryContent
|
||||
self.secondaryContent = secondaryContent
|
||||
self.secondaryTransition = secondaryTransition
|
||||
|
|
@ -102,10 +111,10 @@ public final class ChatListNavigationBar: Component {
|
|||
if lhs.sideInset != rhs.sideInset {
|
||||
return false
|
||||
}
|
||||
if lhs.isSearchActive != rhs.isSearchActive {
|
||||
if lhs.search != rhs.search {
|
||||
return false
|
||||
}
|
||||
if lhs.isSearchEnabled != rhs.isSearchEnabled {
|
||||
if lhs.isSearchActive != rhs.isSearchActive {
|
||||
return false
|
||||
}
|
||||
if lhs.primaryContent != rhs.primaryContent {
|
||||
|
|
@ -155,8 +164,7 @@ public final class ChatListNavigationBar: Component {
|
|||
}()
|
||||
|
||||
public final class View: UIView {
|
||||
private let backgroundView: BlurredBackgroundView
|
||||
private let separatorLayer: SimpleLayer
|
||||
private let edgeEffectView: EdgeEffectView
|
||||
|
||||
public let headerContent = ComponentView<Empty>()
|
||||
|
||||
|
|
@ -184,17 +192,15 @@ public final class ChatListNavigationBar: Component {
|
|||
private var disappearingTabsViewSearch: Bool = false
|
||||
|
||||
private var currentHeaderComponent: ChatListHeaderComponent?
|
||||
|
||||
private var currentHeight: CGFloat = 0.0
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true)
|
||||
self.backgroundView.layer.anchorPoint = CGPoint(x: 0.0, y: 1.0)
|
||||
self.separatorLayer = SimpleLayer()
|
||||
self.separatorLayer.anchorPoint = CGPoint()
|
||||
self.edgeEffectView = EdgeEffectView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
self.layer.addSublayer(self.separatorLayer)
|
||||
self.addSubview(self.edgeEffectView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
|
@ -202,7 +208,7 @@ public final class ChatListNavigationBar: Component {
|
|||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if !self.backgroundView.frame.contains(point) {
|
||||
if point.y >= self.currentHeight {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -248,7 +254,12 @@ public final class ChatListNavigationBar: Component {
|
|||
|
||||
let searchOffsetDistance: CGFloat = ChatListNavigationBar.searchScrollHeight
|
||||
|
||||
let minContentOffset: CGFloat = ChatListNavigationBar.searchScrollHeight
|
||||
let minContentOffset: CGFloat
|
||||
if component.search != nil {
|
||||
minContentOffset = ChatListNavigationBar.searchScrollHeight
|
||||
} else {
|
||||
minContentOffset = 0.0
|
||||
}
|
||||
|
||||
let clippedScrollOffset = min(minContentOffset, offset)
|
||||
if self.clippedScrollOffset == clippedScrollOffset && !self.hasDeferredScrollOffset && !forceUpdate && !allowAvatarsExpansionUpdated {
|
||||
|
|
@ -259,72 +270,87 @@ public final class ChatListNavigationBar: Component {
|
|||
|
||||
let visibleSize = CGSize(width: currentLayout.size.width, height: max(0.0, currentLayout.size.height - clippedScrollOffset))
|
||||
|
||||
let previousHeight = self.separatorLayer.position.y
|
||||
let previousHeight = self.currentHeight
|
||||
|
||||
self.backgroundView.update(size: CGSize(width: visibleSize.width, height: 1000.0), transition: transition.containedViewLayoutTransition)
|
||||
/*self.backgroundView.update(size: CGSize(width: visibleSize.width, height: 1000.0), transition: transition.containedViewLayoutTransition)
|
||||
|
||||
transition.setBounds(view: self.backgroundView, bounds: CGRect(origin: CGPoint(), size: CGSize(width: visibleSize.width, height: 1000.0)))
|
||||
transition.animatePosition(view: self.backgroundView, from: CGPoint(x: 0.0, y: -visibleSize.height + self.backgroundView.layer.position.y), to: CGPoint(), additive: true)
|
||||
self.backgroundView.layer.position = CGPoint(x: 0.0, y: visibleSize.height)
|
||||
self.backgroundView.layer.position = CGPoint(x: 0.0, y: visibleSize.height)*/
|
||||
|
||||
let edgeEffectHeight: CGFloat = currentLayout.size.height + 20.0
|
||||
var edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: currentLayout.size.width, height: edgeEffectHeight))
|
||||
if component.isSearchActive {
|
||||
edgeEffectFrame.origin.y -= 46.0
|
||||
}
|
||||
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
|
||||
self.edgeEffectView.update(content: component.theme.list.plainBackgroundColor, blur: true, alpha: 0.75, rect: edgeEffectFrame, edge: .top, edgeSize: 40.0, transition: transition)
|
||||
|
||||
transition.setFrameWithAdditivePosition(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: UIScreenPixel)))
|
||||
self.currentHeight = visibleSize.height
|
||||
|
||||
let searchContentNode: NavigationBarSearchContentNode
|
||||
if let current = self.searchContentNode {
|
||||
searchContentNode = current
|
||||
|
||||
if themeUpdated {
|
||||
if let search = component.search {
|
||||
let searchContentNode: NavigationBarSearchContentNode
|
||||
if let current = self.searchContentNode {
|
||||
searchContentNode = current
|
||||
|
||||
if themeUpdated {
|
||||
let placeholder: String
|
||||
let compactPlaceholder: String
|
||||
|
||||
placeholder = component.strings.Common_Search
|
||||
compactPlaceholder = component.strings.Common_Search
|
||||
|
||||
searchContentNode.updateThemeAndPlaceholder(theme: component.theme, placeholder: placeholder, compactPlaceholder: compactPlaceholder)
|
||||
}
|
||||
} else {
|
||||
let placeholder: String
|
||||
let compactPlaceholder: String
|
||||
|
||||
placeholder = component.strings.Common_Search
|
||||
compactPlaceholder = component.strings.Common_Search
|
||||
|
||||
searchContentNode.updateThemeAndPlaceholder(theme: component.theme, placeholder: placeholder, compactPlaceholder: compactPlaceholder)
|
||||
}
|
||||
} else {
|
||||
let placeholder: String
|
||||
let compactPlaceholder: String
|
||||
|
||||
placeholder = component.strings.Common_Search
|
||||
compactPlaceholder = component.strings.Common_Search
|
||||
|
||||
searchContentNode = NavigationBarSearchContentNode(
|
||||
theme: component.theme,
|
||||
placeholder: placeholder,
|
||||
compactPlaceholder: compactPlaceholder,
|
||||
activate: { [weak self] in
|
||||
guard let self, let component = self.component, let searchContentNode = self.searchContentNode else {
|
||||
return
|
||||
searchContentNode = NavigationBarSearchContentNode(
|
||||
theme: component.theme,
|
||||
placeholder: placeholder,
|
||||
compactPlaceholder: compactPlaceholder,
|
||||
activate: { [weak self] in
|
||||
guard let self, let component = self.component, let searchContentNode = self.searchContentNode else {
|
||||
return
|
||||
}
|
||||
component.activateSearch(searchContentNode)
|
||||
}
|
||||
component.activateSearch(searchContentNode)
|
||||
}
|
||||
)
|
||||
searchContentNode.view.layer.anchorPoint = CGPoint()
|
||||
self.searchContentNode = searchContentNode
|
||||
self.addSubview(searchContentNode.view)
|
||||
)
|
||||
searchContentNode.view.layer.anchorPoint = CGPoint()
|
||||
self.searchContentNode = searchContentNode
|
||||
self.addSubview(searchContentNode.view)
|
||||
}
|
||||
|
||||
let searchSize = CGSize(width: currentLayout.size.width - 6.0 * 2.0, height: navigationBarSearchContentHeight)
|
||||
var searchFrame = CGRect(origin: CGPoint(x: 6.0, y: visibleSize.height - searchSize.height), size: searchSize)
|
||||
if component.tabsNode != nil {
|
||||
searchFrame.origin.y -= 40.0
|
||||
}
|
||||
if !component.isSearchActive {
|
||||
searchFrame.origin.y -= component.accessoryPanelContainerHeight
|
||||
}
|
||||
|
||||
let clippedSearchOffset = max(0.0, min(clippedScrollOffset, searchOffsetDistance))
|
||||
let searchOffsetFraction = clippedSearchOffset / searchOffsetDistance
|
||||
searchContentNode.expansionProgress = 1.0 - searchOffsetFraction
|
||||
|
||||
transition.setFrameWithAdditivePosition(view: searchContentNode.view, frame: searchFrame)
|
||||
|
||||
searchContentNode.updateLayout(size: searchSize, leftInset: component.sideInset, rightInset: component.sideInset, transition: transition.containedViewLayoutTransition)
|
||||
|
||||
transition.setAlpha(view: searchContentNode.view, alpha: search.isEnabled ? 1.0 : 0.5)
|
||||
searchContentNode.isUserInteractionEnabled = search.isEnabled
|
||||
} else {
|
||||
if let searchContentNode = self.searchContentNode {
|
||||
self.searchContentNode = nil
|
||||
searchContentNode.view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
let searchSize = CGSize(width: currentLayout.size.width - 6.0 * 2.0, height: navigationBarSearchContentHeight)
|
||||
var searchFrame = CGRect(origin: CGPoint(x: 6.0, y: visibleSize.height - searchSize.height), size: searchSize)
|
||||
if component.tabsNode != nil {
|
||||
searchFrame.origin.y -= 40.0
|
||||
}
|
||||
if !component.isSearchActive {
|
||||
searchFrame.origin.y -= component.accessoryPanelContainerHeight
|
||||
}
|
||||
|
||||
let clippedSearchOffset = max(0.0, min(clippedScrollOffset, searchOffsetDistance))
|
||||
let searchOffsetFraction = clippedSearchOffset / searchOffsetDistance
|
||||
searchContentNode.expansionProgress = 1.0 - searchOffsetFraction
|
||||
|
||||
transition.setFrameWithAdditivePosition(view: searchContentNode.view, frame: searchFrame)
|
||||
|
||||
searchContentNode.updateLayout(size: searchSize, leftInset: component.sideInset, rightInset: component.sideInset, transition: transition.containedViewLayoutTransition)
|
||||
|
||||
transition.setAlpha(view: searchContentNode.view, alpha: component.isSearchEnabled ? 1.0 : 0.5)
|
||||
searchContentNode.isUserInteractionEnabled = component.isSearchEnabled
|
||||
|
||||
let headerTransition = transition
|
||||
|
||||
let storiesOffsetFraction: CGFloat
|
||||
|
|
@ -455,7 +481,8 @@ public final class ChatListNavigationBar: Component {
|
|||
}
|
||||
|
||||
var tabsFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: 46.0))
|
||||
if !component.isSearchActive {
|
||||
if component.isSearchActive {
|
||||
} else {
|
||||
tabsFrame.origin.y -= component.accessoryPanelContainerHeight
|
||||
}
|
||||
if component.tabsNode != nil {
|
||||
|
|
@ -463,7 +490,8 @@ public final class ChatListNavigationBar: Component {
|
|||
}
|
||||
|
||||
var accessoryPanelContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: component.accessoryPanelContainerHeight))
|
||||
if !component.isSearchActive {
|
||||
if component.isSearchActive {
|
||||
} else {
|
||||
accessoryPanelContainerFrame.origin.y -= component.accessoryPanelContainerHeight
|
||||
}
|
||||
|
||||
|
|
@ -528,8 +556,8 @@ public final class ChatListNavigationBar: Component {
|
|||
strings: component.strings,
|
||||
statusBarHeight: component.statusBarHeight,
|
||||
sideInset: component.sideInset,
|
||||
search: component.search,
|
||||
isSearchActive: component.isSearchActive,
|
||||
isSearchEnabled: component.isSearchEnabled,
|
||||
primaryContent: component.primaryContent,
|
||||
secondaryContent: component.secondaryContent,
|
||||
secondaryTransition: component.secondaryTransition,
|
||||
|
|
@ -575,8 +603,6 @@ public final class ChatListNavigationBar: Component {
|
|||
}
|
||||
|
||||
func update(component: ChatListNavigationBar, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let themeUpdated = self.component?.theme !== component.theme
|
||||
|
||||
var uploadProgressUpdated = false
|
||||
var storySubscriptionsUpdated = false
|
||||
if let previousComponent = self.component {
|
||||
|
|
@ -591,23 +617,20 @@ public final class ChatListNavigationBar: Component {
|
|||
self.component = component
|
||||
self.state = state
|
||||
|
||||
if themeUpdated {
|
||||
self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
|
||||
self.separatorLayer.backgroundColor = component.theme.rootController.navigationBar.separatorColor.cgColor
|
||||
}
|
||||
|
||||
var contentHeight = component.statusBarHeight
|
||||
|
||||
if component.statusBarHeight >= 1.0 {
|
||||
contentHeight += 3.0
|
||||
}
|
||||
contentHeight += 44.0
|
||||
contentHeight += 8.0
|
||||
|
||||
if component.isSearchActive {
|
||||
if component.statusBarHeight < 1.0 {
|
||||
contentHeight += 8.0
|
||||
}
|
||||
} else {
|
||||
}
|
||||
if component.search != nil {
|
||||
contentHeight += navigationBarSearchContentHeight
|
||||
}
|
||||
|
||||
|
|
@ -615,8 +638,11 @@ public final class ChatListNavigationBar: Component {
|
|||
contentHeight += 40.0
|
||||
}
|
||||
|
||||
if component.accessoryPanelContainer != nil && !component.isSearchActive {
|
||||
contentHeight += component.accessoryPanelContainerHeight
|
||||
if component.isSearchActive {
|
||||
} else {
|
||||
if component.accessoryPanelContainer != nil {
|
||||
contentHeight += component.accessoryPanelContainerHeight
|
||||
}
|
||||
}
|
||||
|
||||
let size = CGSize(width: availableSize.width, height: contentHeight)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,254 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import ComponentFlow
|
||||
import ChatListTitleView
|
||||
import TelegramPresentationData
|
||||
import Display
|
||||
import MoreHeaderButton
|
||||
|
||||
public final class NavigationButtonComponentEnvironment: Equatable {
|
||||
public let theme: PresentationTheme
|
||||
|
||||
public init(theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
}
|
||||
|
||||
public static func ==(lhs: NavigationButtonComponentEnvironment, rhs: NavigationButtonComponentEnvironment) -> Bool {
|
||||
if lhs.theme != rhs.theme {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public final class NavigationButtonComponent: Component {
|
||||
public typealias EnvironmentType = NavigationButtonComponentEnvironment
|
||||
|
||||
public enum Content: Equatable {
|
||||
case text(title: String, isBold: Bool)
|
||||
case more
|
||||
case icon(imageName: String)
|
||||
case proxy(status: ChatTitleProxyStatus)
|
||||
}
|
||||
|
||||
public let content: Content
|
||||
public let pressed: (UIView) -> Void
|
||||
public let contextAction: ((UIView, ContextGesture?) -> Void)?
|
||||
|
||||
public init(
|
||||
content: Content,
|
||||
pressed: @escaping (UIView) -> Void,
|
||||
contextAction: ((UIView, ContextGesture?) -> Void)? = nil
|
||||
) {
|
||||
self.content = content
|
||||
self.pressed = pressed
|
||||
self.contextAction = contextAction
|
||||
}
|
||||
|
||||
public static func ==(lhs: NavigationButtonComponent, rhs: NavigationButtonComponent) -> Bool {
|
||||
if lhs.content != rhs.content {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: HighlightTrackingButton {
|
||||
private var textView: ImmediateTextView?
|
||||
|
||||
private var iconView: UIImageView?
|
||||
private var iconImageName: String?
|
||||
|
||||
private var proxyNode: ChatTitleProxyNode?
|
||||
|
||||
private var moreButton: MoreHeaderButton?
|
||||
|
||||
private var component: NavigationButtonComponent?
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if highlighted {
|
||||
self.textView?.alpha = 0.6
|
||||
self.proxyNode?.alpha = 0.6
|
||||
self.iconView?.alpha = 0.6
|
||||
} else {
|
||||
self.textView?.alpha = 1.0
|
||||
self.textView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
|
||||
|
||||
self.proxyNode?.alpha = 1.0
|
||||
self.proxyNode?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
|
||||
|
||||
self.iconView?.alpha = 1.0
|
||||
self.iconView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
self.component?.pressed(self)
|
||||
}
|
||||
|
||||
func update(component: NavigationButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<NavigationButtonComponentEnvironment>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let theme = environment[NavigationButtonComponentEnvironment.self].value.theme
|
||||
var themeUpdated = false
|
||||
if self.theme !== theme {
|
||||
self.theme = theme
|
||||
themeUpdated = true
|
||||
}
|
||||
|
||||
var textString: NSAttributedString?
|
||||
var imageName: String?
|
||||
var proxyStatus: ChatTitleProxyStatus?
|
||||
var isMore: Bool = false
|
||||
|
||||
switch component.content {
|
||||
case let .text(title, isBold):
|
||||
textString = NSAttributedString(string: title, font: isBold ? Font.bold(17.0) : Font.regular(17.0), textColor: theme.chat.inputPanel.panelControlColor)
|
||||
case .more:
|
||||
isMore = true
|
||||
case let .icon(imageNameValue):
|
||||
imageName = imageNameValue
|
||||
case let .proxy(status):
|
||||
proxyStatus = status
|
||||
}
|
||||
|
||||
var size = CGSize(width: 0.0, height: availableSize.height)
|
||||
|
||||
if let textString = textString {
|
||||
let textView: ImmediateTextView
|
||||
if let current = self.textView {
|
||||
textView = current
|
||||
} else {
|
||||
textView = ImmediateTextView()
|
||||
textView.isUserInteractionEnabled = false
|
||||
self.textView = textView
|
||||
self.addSubview(textView)
|
||||
}
|
||||
|
||||
textView.attributedText = textString
|
||||
let textSize = textView.updateLayout(availableSize)
|
||||
let textInset: CGFloat = 10.0
|
||||
size.width = max(44.0, textSize.width + textInset * 2.0)
|
||||
|
||||
textView.frame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((availableSize.height - textSize.height) / 2.0)), size: textSize)
|
||||
} else if let textView = self.textView {
|
||||
self.textView = nil
|
||||
textView.removeFromSuperview()
|
||||
}
|
||||
|
||||
if let imageName = imageName {
|
||||
let iconView: UIImageView
|
||||
if let current = self.iconView {
|
||||
iconView = current
|
||||
} else {
|
||||
iconView = UIImageView()
|
||||
iconView.isUserInteractionEnabled = false
|
||||
self.iconView = iconView
|
||||
self.addSubview(iconView)
|
||||
}
|
||||
if self.iconImageName != imageName || themeUpdated {
|
||||
self.iconImageName = imageName
|
||||
iconView.image = generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.chat.inputPanel.panelControlColor)
|
||||
}
|
||||
|
||||
if let iconSize = iconView.image?.size {
|
||||
size.width = 44.0
|
||||
|
||||
iconView.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((availableSize.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
}
|
||||
} else if let iconView = self.iconView {
|
||||
self.iconView = nil
|
||||
iconView.removeFromSuperview()
|
||||
self.iconImageName = nil
|
||||
}
|
||||
|
||||
if let proxyStatus = proxyStatus {
|
||||
let proxyNode: ChatTitleProxyNode
|
||||
if let current = self.proxyNode {
|
||||
proxyNode = current
|
||||
} else {
|
||||
proxyNode = ChatTitleProxyNode(theme: theme)
|
||||
proxyNode.isUserInteractionEnabled = false
|
||||
self.proxyNode = proxyNode
|
||||
self.addSubnode(proxyNode)
|
||||
}
|
||||
|
||||
let proxySize = CGSize(width: 30.0, height: 30.0)
|
||||
size.width = 44.0
|
||||
|
||||
proxyNode.theme = theme
|
||||
proxyNode.status = proxyStatus
|
||||
|
||||
proxyNode.frame = CGRect(origin: CGPoint(x: floor((size.width - proxySize.width) / 2.0), y: floor((availableSize.height - proxySize.height) / 2.0)), size: proxySize)
|
||||
} else if let proxyNode = self.proxyNode {
|
||||
self.proxyNode = nil
|
||||
proxyNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if isMore {
|
||||
let moreButton: MoreHeaderButton
|
||||
if let current = self.moreButton, !themeUpdated {
|
||||
moreButton = current
|
||||
} else {
|
||||
if let moreButton = self.moreButton {
|
||||
moreButton.removeFromSupernode()
|
||||
self.moreButton = nil
|
||||
}
|
||||
|
||||
moreButton = MoreHeaderButton(color: theme.chat.inputPanel.panelControlColor)
|
||||
moreButton.isUserInteractionEnabled = true
|
||||
moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.chat.inputPanel.panelControlColor)))
|
||||
moreButton.onPressed = { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
self.moreButton?.play()
|
||||
component.pressed(self)
|
||||
}
|
||||
moreButton.contextAction = { [weak self] sourceNode, gesture in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
self.moreButton?.play()
|
||||
component.contextAction?(self, gesture)
|
||||
}
|
||||
self.moreButton = moreButton
|
||||
self.addSubnode(moreButton)
|
||||
}
|
||||
|
||||
let buttonSize = CGSize(width: 30.0, height: 44.0)
|
||||
size.width = 44.0
|
||||
|
||||
moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.rootController.navigationBar.buttonColor)))
|
||||
|
||||
moreButton.frame = CGRect(origin: CGPoint(x: floor((size.width - buttonSize.width) / 2.0), y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize)
|
||||
} else if let moreButton = self.moreButton {
|
||||
self.moreButton = nil
|
||||
moreButton.removeFromSupernode()
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<NavigationButtonComponentEnvironment>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import ComponentFlow
|
||||
|
||||
final class NavigationButtonContainerView: UIView {
|
||||
}
|
||||
|
|
@ -44,13 +44,13 @@ public final class ChatTitleProxyNode: ASDisplayNode {
|
|||
if self.theme !== oldValue {
|
||||
switch self.status {
|
||||
case .connecting:
|
||||
self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: false, off: false)
|
||||
self.iconNode.image = generateIcon(color: theme.chat.inputPanel.panelControlColor, connected: false, off: false)
|
||||
case .connected:
|
||||
self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: true, off: false)
|
||||
self.iconNode.image = generateIcon(color: theme.chat.inputPanel.panelControlColor, connected: true, off: false)
|
||||
case .available:
|
||||
self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: false, off: true)
|
||||
self.iconNode.image = generateIcon(color: theme.chat.inputPanel.panelControlColor, connected: false, off: true)
|
||||
}
|
||||
self.activityIndicator.type = .custom(theme.rootController.navigationBar.accentTextColor, 10.0, 1.3333, true)
|
||||
self.activityIndicator.type = .custom(theme.chat.inputPanel.panelControlColor, 10.0, 1.3333, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,13 +61,13 @@ public final class ChatTitleProxyNode: ASDisplayNode {
|
|||
switch self.status {
|
||||
case .connecting:
|
||||
self.activityIndicator.isHidden = false
|
||||
self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: false, off: false)
|
||||
self.iconNode.image = generateIcon(color: theme.chat.inputPanel.panelControlColor, connected: false, off: false)
|
||||
case .connected:
|
||||
self.activityIndicator.isHidden = true
|
||||
self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: true, off: false)
|
||||
self.iconNode.image = generateIcon(color: theme.chat.inputPanel.panelControlColor, connected: true, off: false)
|
||||
case .available:
|
||||
self.activityIndicator.isHidden = true
|
||||
self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: false, off: true)
|
||||
self.iconNode.image = generateIcon(color: theme.chat.inputPanel.panelControlColor, connected: false, off: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -80,9 +80,9 @@ public final class ChatTitleProxyNode: ASDisplayNode {
|
|||
self.iconNode.isLayerBacked = true
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: false, off: true)
|
||||
self.iconNode.image = generateIcon(color: theme.chat.inputPanel.panelControlColor, connected: false, off: true)
|
||||
|
||||
self.activityIndicator = ActivityIndicator(type: .custom(theme.rootController.navigationBar.accentTextColor, 10.0, 1.3333, true), speed: .slow)
|
||||
self.activityIndicator = ActivityIndicator(type: .custom(theme.chat.inputPanel.panelControlColor, 10.0, 1.3333, true), speed: .slow)
|
||||
|
||||
super.init()
|
||||
|
||||
|
|
|
|||
|
|
@ -64,13 +64,15 @@ public final class LiquidLensView: UIView {
|
|||
var selectionWidth: CGFloat
|
||||
var isDark: Bool
|
||||
var isLifted: Bool
|
||||
var isCollapsed: Bool
|
||||
|
||||
init(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, isDark: Bool, isLifted: Bool) {
|
||||
init(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, isDark: Bool, isLifted: Bool, isCollapsed: Bool) {
|
||||
self.size = size
|
||||
self.selectionX = selectionX
|
||||
self.selectionWidth = selectionWidth
|
||||
self.isLifted = isLifted
|
||||
self.isDark = isDark
|
||||
self.isCollapsed = isCollapsed
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,8 +87,8 @@ public final class LiquidLensView: UIView {
|
|||
}
|
||||
|
||||
private let containerView: UIView
|
||||
private let backgroundContainerContainer: UIView
|
||||
private let backgroundContainer: GlassBackgroundContainerView
|
||||
private let backgroundContainer: GlassBackgroundContainerView?
|
||||
private let genericBackgroundContainer: UIView?
|
||||
private let backgroundView: GlassBackgroundView
|
||||
private var lensView: UIView?
|
||||
private let liftedContainerView: UIView
|
||||
|
|
@ -117,11 +119,16 @@ public final class LiquidLensView: UIView {
|
|||
return self.params?.selectionWidth
|
||||
}
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
public init(useBackgroundContainer: Bool = true) {
|
||||
self.containerView = UIView()
|
||||
|
||||
self.backgroundContainerContainer = UIView()
|
||||
self.backgroundContainer = GlassBackgroundContainerView()
|
||||
if useBackgroundContainer {
|
||||
self.backgroundContainer = GlassBackgroundContainerView()
|
||||
self.genericBackgroundContainer = nil
|
||||
} else {
|
||||
self.backgroundContainer = nil
|
||||
self.genericBackgroundContainer = UIView()
|
||||
}
|
||||
|
||||
self.backgroundView = GlassBackgroundView()
|
||||
|
||||
|
|
@ -130,12 +137,16 @@ public final class LiquidLensView: UIView {
|
|||
|
||||
self.restingBackgroundView = RestingBackgroundView()
|
||||
|
||||
super.init(frame: frame)
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.backgroundContainerContainer.addSubview(self.backgroundContainer)
|
||||
self.addSubview(self.backgroundContainerContainer)
|
||||
if let backgroundContainer = self.backgroundContainer {
|
||||
self.addSubview(backgroundContainer)
|
||||
backgroundContainer.contentView.addSubview(self.backgroundView)
|
||||
} else if let genericBackgroundContainer = self.genericBackgroundContainer {
|
||||
self.addSubview(genericBackgroundContainer)
|
||||
genericBackgroundContainer.addSubview(self.backgroundView)
|
||||
}
|
||||
|
||||
self.backgroundContainer.contentView.addSubview(self.backgroundView)
|
||||
self.backgroundView.contentView.addSubview(self.containerView)
|
||||
self.containerView.isUserInteractionEnabled = false
|
||||
|
||||
|
|
@ -150,7 +161,11 @@ public final class LiquidLensView: UIView {
|
|||
}
|
||||
|
||||
if let lensView = self.lensView {
|
||||
self.backgroundContainer.layer.zPosition = 1
|
||||
if let backgroundContainer = self.backgroundContainer {
|
||||
backgroundContainer.layer.zPosition = 1
|
||||
} else if let genericBackgroundContainer = self.genericBackgroundContainer{
|
||||
genericBackgroundContainer.layer.zPosition = 1
|
||||
}
|
||||
lensView.layer.zPosition = 10.0
|
||||
|
||||
self.liftedContainerView.addSubview(self.restingBackgroundView)
|
||||
|
|
@ -159,7 +174,11 @@ public final class LiquidLensView: UIView {
|
|||
self.containerView.addSubview(lensView)
|
||||
self.containerView.addSubview(self.contentView)
|
||||
|
||||
lensView.perform(NSSelectorFromString("setLiftedContainerView:"), with: self.backgroundContainer.contentView)
|
||||
if let backgroundContainer = self.backgroundContainer {
|
||||
lensView.perform(NSSelectorFromString("setLiftedContainerView:"), with: backgroundContainer.contentView)
|
||||
} else if let genericBackgroundContainer = self.genericBackgroundContainer {
|
||||
lensView.perform(NSSelectorFromString("setLiftedContainerView:"), with: genericBackgroundContainer)
|
||||
}
|
||||
lensView.perform(NSSelectorFromString("setLiftedContentView:"), with: self.liftedContainerView)
|
||||
lensView.perform(NSSelectorFromString("setOverridePunchoutView:"), with: self.contentView)
|
||||
|
||||
|
|
@ -223,8 +242,8 @@ public final class LiquidLensView: UIView {
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func update(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, isDark: Bool, isLifted: Bool, transition: ComponentTransition) {
|
||||
let params = Params(size: size, selectionX: selectionX, selectionWidth: selectionWidth, isDark: isDark, isLifted: isLifted)
|
||||
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)
|
||||
if self.params == params {
|
||||
return
|
||||
}
|
||||
|
|
@ -319,16 +338,36 @@ public final class LiquidLensView: UIView {
|
|||
self.params = params
|
||||
|
||||
transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||
transition.setFrame(view: self.backgroundContainerContainer, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||
|
||||
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||
self.backgroundContainer.update(size: params.size, isDark: params.isDark, transition: transition)
|
||||
if let backgroundContainer = self.backgroundContainer {
|
||||
transition.setFrame(view: backgroundContainer, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||
backgroundContainer.update(size: params.size, isDark: params.isDark, transition: transition)
|
||||
} else if let genericBackgroundContainer = self.genericBackgroundContainer {
|
||||
transition.setFrame(view: genericBackgroundContainer, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||
self.backgroundView.update(size: params.size, cornerRadius: params.size.height * 0.5, isDark: params.isDark, tintColor: GlassBackgroundView.TintColor.init(kind: .panel, color: UIColor(white: params.isDark ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition)
|
||||
|
||||
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||
transition.setFrame(view: self.liftedContainerView, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||
if self.contentView.bounds.size != params.size {
|
||||
self.contentView.clipsToBounds = true
|
||||
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: params.size), completion: { [weak self] completed in
|
||||
guard let self, completed else {
|
||||
return
|
||||
}
|
||||
self.contentView.clipsToBounds = false
|
||||
})
|
||||
transition.setCornerRadius(layer: self.contentView.layer, cornerRadius: params.size.height * 0.5)
|
||||
|
||||
self.liftedContainerView.clipsToBounds = true
|
||||
transition.setFrame(view: self.liftedContainerView, frame: CGRect(origin: CGPoint(), size: params.size), completion: { [weak self] completed in
|
||||
guard let self, completed else {
|
||||
return
|
||||
}
|
||||
self.liftedContainerView.clipsToBounds = false
|
||||
})
|
||||
transition.setCornerRadius(layer: self.liftedContainerView.layer, cornerRadius: params.size.height * 0.5)
|
||||
}
|
||||
|
||||
let baseLensFrame = CGRect(origin: CGPoint(x: max(0.0, min(params.selectionX, params.size.width - params.selectionWidth)), 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)
|
||||
|
|
@ -354,7 +393,7 @@ public final class LiquidLensView: UIView {
|
|||
|
||||
transition.setFrame(view: self.restingBackgroundView, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||
self.restingBackgroundView.update(isDark: params.isDark)
|
||||
transition.setAlpha(view: self.restingBackgroundView, alpha: params.isLifted ? 0.0 : 1.0)
|
||||
transition.setAlpha(view: self.restingBackgroundView, alpha: (params.isLifted || params.isCollapsed) ? 0.0 : 1.0)
|
||||
|
||||
if params.isLifted {
|
||||
if self.liftedDisplayLink == nil {
|
||||
|
|
|
|||
|
|
@ -266,8 +266,7 @@ final class MiniAppListScreenComponent: Component {
|
|||
}
|
||||
))) : nil,
|
||||
rightButtons: rightButtons,
|
||||
backTitle: isModal ? nil : strings.Common_Back,
|
||||
backPressed: { [weak self] in
|
||||
backPressed: isModal ? nil : { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
|
@ -286,8 +285,8 @@ final class MiniAppListScreenComponent: Component {
|
|||
strings: strings,
|
||||
statusBarHeight: statusBarHeight,
|
||||
sideInset: insets.left,
|
||||
search: ChatListNavigationBar.Search(isEnabled: true),
|
||||
isSearchActive: self.isSearchDisplayControllerActive,
|
||||
isSearchEnabled: true,
|
||||
primaryContent: headerContent,
|
||||
secondaryContent: nil,
|
||||
secondaryTransition: 0.0,
|
||||
|
|
|
|||
|
|
@ -13923,6 +13923,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
|
|||
}
|
||||
self._readyProxy.set(.single(true))
|
||||
})
|
||||
|
||||
self.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: false), transition: .immediate)
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
|
|
@ -14476,7 +14478,7 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
|
|||
if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar {
|
||||
self.addSubnode(bottomNavigationBar.additionalContentNode)
|
||||
|
||||
if let headerView = bottomNavigationBar.customHeaderContentView as? ChatListHeaderComponent.View {
|
||||
/*if let headerView = bottomNavigationBar.customHeaderContentView as? ChatListHeaderComponent.View {
|
||||
if let previousBackButtonArrow = headerView.makeTransitionBackArrowView(accentColor: self.presentationData.theme.rootController.navigationBar.accentTextColor) {
|
||||
self.previousBackButtonArrow = previousBackButtonArrow
|
||||
self.view.addSubview(previousBackButtonArrow)
|
||||
|
|
@ -14485,7 +14487,7 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
|
|||
self.previousBackButton = previousBackButton
|
||||
self.view.addSubview(previousBackButton)
|
||||
}
|
||||
} else {
|
||||
} else*/ do {
|
||||
if let previousBackButtonArrow = bottomNavigationBar.makeTransitionBackArrowView(accentColor: self.presentationData.theme.rootController.navigationBar.accentTextColor) {
|
||||
self.previousBackButtonArrow = previousBackButtonArrow
|
||||
self.view.addSubview(previousBackButtonArrow)
|
||||
|
|
@ -14594,13 +14596,13 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
|
|||
}
|
||||
|
||||
if let previousBackButton = self.previousBackButton {
|
||||
if let headerView = bottomNavigationBar.customHeaderContentView as? ChatListHeaderComponent.View {
|
||||
/*if let headerView = bottomNavigationBar.customHeaderContentView as? ChatListHeaderComponent.View {
|
||||
if let backButtonTitleView = headerView.backButtonTitleView {
|
||||
let previousBackButtonFrame = backButtonTitleView.convert(backButtonTitleView.bounds, to: bottomNavigationBar.view)
|
||||
previousBackButton.frame = previousBackButtonFrame
|
||||
transition.updateAlpha(layer: previousBackButton.layer, alpha: fraction)
|
||||
}
|
||||
} else {
|
||||
} else*/ do {
|
||||
let previousBackButtonFrame = bottomNavigationBar.backButtonNode.view.convert(bottomNavigationBar.backButtonNode.view.bounds, to: bottomNavigationBar.view)
|
||||
previousBackButton.frame = previousBackButtonFrame
|
||||
transition.updateAlpha(layer: previousBackButton.layer, alpha: fraction)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ swift_library(
|
|||
"//submodules/TextFormat",
|
||||
"//submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode",
|
||||
"//submodules/CounterControllerTitleView",
|
||||
"//submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import AccountContext
|
|||
import SearchUI
|
||||
import ChatListUI
|
||||
import CounterControllerTitleView
|
||||
import ChatListFilterTabContainerNode
|
||||
|
||||
public final class PeerSelectionControllerImpl: ViewController, PeerSelectionController {
|
||||
private let context: AccountContext
|
||||
|
|
|
|||
|
|
@ -798,8 +798,7 @@ final class QuickReplySetupScreenComponent: Component {
|
|||
}
|
||||
))) : nil,
|
||||
rightButtons: rightButtons,
|
||||
backTitle: isModal ? nil : strings.Common_Back,
|
||||
backPressed: { [weak self] in
|
||||
backPressed: isModal ? nil :{ [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
|
@ -818,8 +817,8 @@ final class QuickReplySetupScreenComponent: Component {
|
|||
strings: strings,
|
||||
statusBarHeight: statusBarHeight,
|
||||
sideInset: insets.left,
|
||||
search: ChatListNavigationBar.Search(isEnabled: !self.isEditing),
|
||||
isSearchActive: self.isSearchDisplayControllerActive,
|
||||
isSearchEnabled: !self.isEditing,
|
||||
primaryContent: headerContent,
|
||||
secondaryContent: nil,
|
||||
secondaryTransition: 0.0,
|
||||
|
|
|
|||
|
|
@ -298,8 +298,7 @@ final class PeerSelectionScreenComponent: Component {
|
|||
}
|
||||
))) : nil,
|
||||
rightButtons: rightButtons,
|
||||
backTitle: isModal ? nil : strings.Common_Back,
|
||||
backPressed: { [weak self] in
|
||||
backPressed: isModal ? nil : { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
|
@ -318,8 +317,8 @@ final class PeerSelectionScreenComponent: Component {
|
|||
strings: strings,
|
||||
statusBarHeight: statusBarHeight,
|
||||
sideInset: insets.left,
|
||||
search: ChatListNavigationBar.Search(isEnabled: true),
|
||||
isSearchActive: self.isSearchDisplayControllerActive,
|
||||
isSearchEnabled: true,
|
||||
primaryContent: headerContent,
|
||||
secondaryContent: nil,
|
||||
secondaryTransition: 0.0,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ swift_library(
|
|||
"//submodules/UIKitRuntimeUtils",
|
||||
"//submodules/TelegramUI/Components/LiquidLens",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/SearchBarNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import BundleIconComponent
|
|||
import TextBadgeComponent
|
||||
import LiquidLens
|
||||
import AppBundle
|
||||
import SearchBarNode
|
||||
|
||||
private final class TabSelectionRecognizer: UIGestureRecognizer {
|
||||
private var initialLocation: CGPoint?
|
||||
|
|
@ -69,35 +70,147 @@ private final class TabSelectionRecognizer: UIGestureRecognizer {
|
|||
}
|
||||
}
|
||||
|
||||
public final class TabBarSearchView: UIView {
|
||||
private let backgroundView: GlassBackgroundView
|
||||
private let iconView: GlassBackgroundView.ContentImageView
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
self.backgroundView = GlassBackgroundView()
|
||||
self.iconView = GlassBackgroundView.ContentImageView()
|
||||
public final class NavigationSearchView: UIView {
|
||||
private struct Params: Equatable {
|
||||
let size: CGSize
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let isActive: Bool
|
||||
|
||||
super.init(frame: frame)
|
||||
init(size: CGSize, theme: PresentationTheme, strings: PresentationStrings, isActive: Bool) {
|
||||
self.size = size
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.isActive = isActive
|
||||
}
|
||||
|
||||
static func ==(lhs: Params, rhs: Params) -> Bool {
|
||||
if lhs.size != rhs.size {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.isActive != rhs.isActive {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private let action: () -> Void
|
||||
|
||||
private let backgroundView: GlassBackgroundView
|
||||
private let iconView: UIImageView
|
||||
private(set) var searchBarNode: SearchBarNode?
|
||||
|
||||
private var params: Params?
|
||||
|
||||
public init(action: @escaping () -> Void) {
|
||||
self.action = action
|
||||
|
||||
self.backgroundView = GlassBackgroundView()
|
||||
self.backgroundView.contentView.clipsToBounds = true
|
||||
self.iconView = UIImageView()
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
self.backgroundView.contentView.addSubview(self.iconView)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))))
|
||||
}
|
||||
|
||||
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.action()
|
||||
}
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func update(size: CGSize, isDark: Bool, tintColor: GlassBackgroundView.TintColor, iconColor: UIColor, transition: ComponentTransition) {
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: isDark, tintColor: tintColor, transition: transition)
|
||||
public func update(size: CGSize, theme: PresentationTheme, strings: PresentationStrings, isActive: Bool, transition: ComponentTransition) {
|
||||
let params = Params(size: size, theme: theme, strings: strings, isActive: isActive)
|
||||
if self.params == params {
|
||||
return
|
||||
}
|
||||
self.params = params
|
||||
self.update(params: params, transition: transition)
|
||||
}
|
||||
|
||||
private func update(params: Params, transition: ComponentTransition) {
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.25)
|
||||
|
||||
self.backgroundView.update(size: params.size, cornerRadius: params.size.height * 0.5, isDark: params.theme.overallDarkAppearance, tintColor: GlassBackgroundView.TintColor.init(kind: .panel, color: UIColor(white: params.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition)
|
||||
|
||||
if self.iconView.image == nil {
|
||||
self.iconView.image = UIImage(bundleImageName: "Navigation/Search")?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
self.iconView.tintColor = iconColor
|
||||
transition.setTintColor(view: self.iconView, color: params.isActive ? params.theme.rootController.navigationSearchBar.inputIconColor : params.theme.chat.inputPanel.panelControlColor)
|
||||
|
||||
if let image = self.iconView.image {
|
||||
transition.setFrame(view: self.iconView, frame: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), size: image.size))
|
||||
let imageSize: CGSize
|
||||
let iconFrame: CGRect
|
||||
if params.isActive {
|
||||
let iconFraction: CGFloat = 0.8
|
||||
imageSize = CGSize(width: image.size.width * iconFraction, height: image.size.height * iconFraction)
|
||||
iconFrame = CGRect(origin: CGPoint(x: 12.0, y: floor((params.size.height - imageSize.height) * 0.5)), size: imageSize)
|
||||
} else {
|
||||
iconFrame = CGRect(origin: CGPoint(x: floor((params.size.width - image.size.width) * 0.5), y: floor((params.size.height - image.size.height) * 0.5)), size: image.size)
|
||||
}
|
||||
transition.setPosition(view: self.iconView, position: iconFrame.center)
|
||||
transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
||||
}
|
||||
|
||||
if params.isActive {
|
||||
let searchBarNode: SearchBarNode
|
||||
var searchBarNodeTransition = transition
|
||||
if let current = self.searchBarNode {
|
||||
searchBarNode = current
|
||||
} else {
|
||||
searchBarNodeTransition = searchBarNodeTransition.withAnimation(.none)
|
||||
searchBarNode = SearchBarNode(
|
||||
theme: SearchBarNodeTheme(
|
||||
background: .clear,
|
||||
separator: .clear,
|
||||
inputFill: .clear,
|
||||
primaryText: params.theme.chat.inputPanel.panelControlColor,
|
||||
placeholder: params.theme.chat.inputPanel.inputPlaceholderColor,
|
||||
inputIcon: params.theme.chat.inputPanel.inputControlColor,
|
||||
inputClear: params.theme.chat.inputPanel.inputControlColor,
|
||||
accent: params.theme.chat.inputPanel.panelControlAccentColor,
|
||||
keyboard: params.theme.rootController.keyboardColor
|
||||
),
|
||||
strings: params.strings,
|
||||
fieldStyle: .inlineNavigation,
|
||||
icon: .loupe,
|
||||
forceSeparator: false,
|
||||
displayBackground: false,
|
||||
cancelText: nil
|
||||
)
|
||||
searchBarNode.placeholderString = NSAttributedString(string: params.strings.Common_Search, font: Font.regular(17.0), textColor: params.theme.chat.inputPanel.inputPlaceholderColor)
|
||||
self.searchBarNode = searchBarNode
|
||||
self.backgroundView.contentView.addSubview(searchBarNode.view)
|
||||
searchBarNode.view.alpha = 0.0
|
||||
}
|
||||
let searchBarFrame = CGRect(origin: CGPoint(x: 36.0, y: 0.0), size: CGSize(width: params.size.width - 36.0 - 4.0, height: params.size.height))
|
||||
transition.setFrame(view: searchBarNode.view, frame: searchBarFrame)
|
||||
searchBarNode.updateLayout(boundingSize: searchBarFrame.size, leftInset: 0.0, rightInset: 0.0, transition: transition.containedViewLayoutTransition)
|
||||
alphaTransition.setAlpha(view: searchBarNode.view, alpha: 1.0)
|
||||
} else {
|
||||
if let searchBarNode = self.searchBarNode {
|
||||
self.searchBarNode = nil
|
||||
let searchBarNodeView = searchBarNode.view
|
||||
alphaTransition.setAlpha(view: searchBarNode.view, alpha: 0.0, completion: { [weak searchBarNodeView] _ in
|
||||
searchBarNodeView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -131,20 +244,45 @@ public final class TabBarComponent: Component {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public final class Search: Equatable {
|
||||
public let isActive: Bool
|
||||
public let activate: () -> Void
|
||||
public let deactivate: () -> Void
|
||||
|
||||
public init(isActive: Bool, activate: @escaping () -> Void, deactivate: @escaping () -> Void) {
|
||||
self.isActive = isActive
|
||||
self.activate = activate
|
||||
self.deactivate = deactivate
|
||||
}
|
||||
|
||||
public static func ==(lhs: Search, rhs: Search) -> Bool {
|
||||
if lhs.isActive != rhs.isActive {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public let theme: PresentationTheme
|
||||
public let strings: PresentationStrings
|
||||
public let items: [Item]
|
||||
public let search: Search?
|
||||
public let selectedId: AnyHashable?
|
||||
public let isTablet: Bool
|
||||
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
items: [Item],
|
||||
search: Search?,
|
||||
selectedId: AnyHashable?,
|
||||
isTablet: Bool
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.items = items
|
||||
self.search = search
|
||||
self.selectedId = selectedId
|
||||
self.isTablet = isTablet
|
||||
}
|
||||
|
|
@ -153,9 +291,15 @@ public final class TabBarComponent: Component {
|
|||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
if lhs.search != rhs.search {
|
||||
return false
|
||||
}
|
||||
if lhs.selectedId != rhs.selectedId {
|
||||
return false
|
||||
}
|
||||
|
|
@ -166,11 +310,14 @@ public final class TabBarComponent: Component {
|
|||
}
|
||||
|
||||
public final class View: UIView, UITabBarDelegate, UIGestureRecognizerDelegate {
|
||||
private let backgroundContainer: GlassBackgroundContainerView
|
||||
private let liquidLensView: LiquidLensView
|
||||
private let contextGestureContainerView: ContextControllerSourceView
|
||||
|
||||
private var itemViews: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
private var selectedItemViews: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
|
||||
private var searchView: NavigationSearchView?
|
||||
|
||||
private var tabSelectionRecognizer: TabSelectionRecognizer?
|
||||
private var itemWithActiveContextGesture: AnyHashable?
|
||||
|
|
@ -178,11 +325,16 @@ public final class TabBarComponent: Component {
|
|||
private var component: TabBarComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
private var selectionGestureState: (startX: CGFloat, currentX: CGFloat)?
|
||||
private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemId: AnyHashable)?
|
||||
private var overrideSelectedItemId: AnyHashable?
|
||||
|
||||
public var currentSearchNode: ASDisplayNode? {
|
||||
return self.searchView?.searchBarNode
|
||||
}
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
self.liquidLensView = LiquidLensView()
|
||||
self.backgroundContainer = GlassBackgroundContainerView()
|
||||
self.liquidLensView = LiquidLensView(useBackgroundContainer: false)
|
||||
|
||||
self.contextGestureContainerView = ContextControllerSourceView()
|
||||
self.contextGestureContainerView.isGestureEnabled = true
|
||||
|
|
@ -194,12 +346,13 @@ public final class TabBarComponent: Component {
|
|||
self.traitOverrides.horizontalSizeClass = .compact
|
||||
}
|
||||
|
||||
self.addSubview(self.contextGestureContainerView)
|
||||
self.addSubview(self.backgroundContainer)
|
||||
self.backgroundContainer.contentView.addSubview(self.contextGestureContainerView)
|
||||
|
||||
self.contextGestureContainerView.addSubview(self.liquidLensView)
|
||||
let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:)))
|
||||
self.tabSelectionRecognizer = tabSelectionRecognizer
|
||||
self.addGestureRecognizer(tabSelectionRecognizer)
|
||||
self.contextGestureContainerView.addGestureRecognizer(tabSelectionRecognizer)
|
||||
|
||||
self.contextGestureContainerView.shouldBegin = { [weak self] point in
|
||||
guard let self, let component = self.component else {
|
||||
|
|
@ -281,29 +434,41 @@ public final class TabBarComponent: Component {
|
|||
}
|
||||
|
||||
@objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
if let itemId = self.item(at: recognizer.location(in: self)), let itemView = self.itemViews[itemId]?.view {
|
||||
if let search = component.search, search.isActive {
|
||||
} else if let itemId = self.item(at: recognizer.location(in: self)), let itemView = self.itemViews[itemId]?.view {
|
||||
let startX = itemView.frame.minX - 4.0
|
||||
self.selectionGestureState = (startX, startX)
|
||||
self.selectionGestureState = (startX, startX, itemId)
|
||||
self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
|
||||
}
|
||||
case .changed:
|
||||
if var selectionGestureState = self.selectionGestureState {
|
||||
if let search = component.search, search.isActive {
|
||||
} else if var selectionGestureState = self.selectionGestureState {
|
||||
selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self).x
|
||||
if let itemId = self.item(at: recognizer.location(in: self)) {
|
||||
selectionGestureState.itemId = itemId
|
||||
}
|
||||
self.selectionGestureState = selectionGestureState
|
||||
self.state?.updated(transition: .immediate, isLocal: true)
|
||||
}
|
||||
case .ended, .cancelled:
|
||||
self.selectionGestureState = nil
|
||||
if let component = self.component, let itemId = self.item(at: recognizer.location(in: self)) {
|
||||
guard let item = component.items.first(where: { $0.id == itemId }) else {
|
||||
return
|
||||
if let search = component.search, search.isActive {
|
||||
search.deactivate()
|
||||
} else if let selectionGestureState = self.selectionGestureState {
|
||||
self.selectionGestureState = nil
|
||||
if case .ended = recognizer.state, let component = self.component {
|
||||
guard let item = component.items.first(where: { $0.id == selectionGestureState.itemId }) else {
|
||||
return
|
||||
}
|
||||
self.overrideSelectedItemId = selectionGestureState.itemId
|
||||
item.action(false)
|
||||
}
|
||||
self.overrideSelectedItemId = itemId
|
||||
item.action(false)
|
||||
self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
|
||||
}
|
||||
self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
@ -355,22 +520,28 @@ public final class TabBarComponent: Component {
|
|||
}
|
||||
|
||||
func update(component: TabBarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.25)
|
||||
let _ = alphaTransition
|
||||
|
||||
let innerInset: CGFloat = 4.0
|
||||
let availableSize = CGSize(width: min(500.0, availableSize.width), height: availableSize.height)
|
||||
|
||||
let previousComponent = self.component
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let _ = innerInset
|
||||
let _ = availableSize
|
||||
let _ = previousComponent
|
||||
|
||||
self.overrideUserInterfaceStyle = component.theme.overallDarkAppearance ? .dark : .light
|
||||
|
||||
let itemSize = CGSize(width: floor((availableSize.width - innerInset * 2.0) / CGFloat(component.items.count)), height: 56.0)
|
||||
let barHeight: CGFloat = 56.0 + innerInset * 2.0
|
||||
|
||||
var availableItemsWidth: CGFloat = availableSize.width - innerInset * 2.0
|
||||
if component.search != nil {
|
||||
availableItemsWidth -= barHeight + 8.0
|
||||
}
|
||||
|
||||
let itemSize = CGSize(width: floor(availableItemsWidth / CGFloat(component.items.count)), height: 56.0)
|
||||
let contentWidth: CGFloat = innerInset * 2.0 + CGFloat(component.items.count) * itemSize.width
|
||||
let size = CGSize(width: min(availableSize.width, contentWidth), height: itemSize.height + innerInset * 2.0)
|
||||
let tabsSize = CGSize(width: min(availableSize.width, contentWidth), height: itemSize.height + innerInset * 2.0)
|
||||
|
||||
var validIds: [AnyHashable] = []
|
||||
var selectionFrame: CGRect?
|
||||
|
|
@ -409,6 +580,7 @@ public final class TabBarComponent: Component {
|
|||
component: AnyComponent(ItemComponent(
|
||||
item: item,
|
||||
theme: component.theme,
|
||||
isCompact: component.search?.isActive == true,
|
||||
isSelected: false
|
||||
)),
|
||||
environment: {},
|
||||
|
|
@ -419,14 +591,21 @@ public final class TabBarComponent: Component {
|
|||
component: AnyComponent(ItemComponent(
|
||||
item: item,
|
||||
theme: component.theme,
|
||||
isCompact: component.search?.isActive == true,
|
||||
isSelected: true
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: itemSize
|
||||
)
|
||||
|
||||
let itemFrame = CGRect(origin: CGPoint(x: innerInset + CGFloat(index) * itemSize.width, y: floor((size.height - itemSize.height) * 0.5)), size: itemSize)
|
||||
var itemFrame = CGRect(origin: CGPoint(x: innerInset + CGFloat(index) * itemSize.width, y: floor((tabsSize.height - itemSize.height) * 0.5)), size: itemSize)
|
||||
if isItemSelected {
|
||||
selectionFrame = itemFrame
|
||||
}
|
||||
|
||||
if let itemComponentView = itemView.view as? ItemComponent.View, let selectedItemComponentView = selectedItemView.view as? ItemComponent.View {
|
||||
let itemAlphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.25)
|
||||
|
||||
if itemComponentView.superview == nil {
|
||||
itemComponentView.isUserInteractionEnabled = false
|
||||
selectedItemComponentView.isUserInteractionEnabled = false
|
||||
|
|
@ -434,6 +613,27 @@ public final class TabBarComponent: Component {
|
|||
self.liquidLensView.contentView.addSubview(itemComponentView)
|
||||
self.liquidLensView.selectedContentView.addSubview(selectedItemComponentView)
|
||||
}
|
||||
|
||||
if let search = component.search, search.isActive {
|
||||
if isItemSelected {
|
||||
itemFrame.origin.x = floor((48.0 - itemSize.width) * 0.5)
|
||||
itemTransition.setAlpha(view: itemComponentView, alpha: 1.0)
|
||||
itemAlphaTransition.setBlur(layer: itemComponentView.layer, radius: 0.0)
|
||||
itemTransition.setAlpha(view: selectedItemComponentView, alpha: 1.0)
|
||||
itemAlphaTransition.setBlur(layer: selectedItemComponentView.layer, radius: 0.0)
|
||||
} else {
|
||||
itemTransition.setAlpha(view: itemComponentView, alpha: 0.0)
|
||||
itemAlphaTransition.setBlur(layer: itemComponentView.layer, radius: 10.0)
|
||||
itemTransition.setAlpha(view: selectedItemComponentView, alpha: 0.0)
|
||||
itemAlphaTransition.setBlur(layer: selectedItemComponentView.layer, radius: 10.0)
|
||||
}
|
||||
} else {
|
||||
itemTransition.setAlpha(view: itemComponentView, alpha: 1.0)
|
||||
itemAlphaTransition.setBlur(layer: itemComponentView.layer, radius: 0.0)
|
||||
itemTransition.setAlpha(view: selectedItemComponentView, alpha: 1.0)
|
||||
itemAlphaTransition.setBlur(layer: selectedItemComponentView.layer, radius: 0.0)
|
||||
}
|
||||
|
||||
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
|
||||
itemTransition.setPosition(view: selectedItemComponentView, position: itemFrame.center)
|
||||
itemTransition.setBounds(view: selectedItemComponentView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
||||
|
|
@ -444,9 +644,6 @@ public final class TabBarComponent: Component {
|
|||
selectedItemComponentView.playSelectionAnimation()
|
||||
}
|
||||
}
|
||||
if isItemSelected {
|
||||
selectionFrame = itemFrame
|
||||
}
|
||||
}
|
||||
|
||||
var removeIds: [AnyHashable] = []
|
||||
|
|
@ -461,12 +658,17 @@ public final class TabBarComponent: Component {
|
|||
self.itemViews.removeValue(forKey: id)
|
||||
self.selectedItemViews.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.contextGestureContainerView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
transition.setFrame(view: self.liquidLensView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let lensSelection: (x: CGFloat, width: CGFloat)
|
||||
var tabsFrame = CGRect(origin: CGPoint(), size: tabsSize)
|
||||
if let search = component.search, search.isActive {
|
||||
tabsFrame.size = CGSize(width: 48.0, height: 48.0)
|
||||
tabsFrame.origin.y = tabsSize.height - 48.0
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.contextGestureContainerView, frame: tabsFrame)
|
||||
transition.setFrame(view: self.liquidLensView, frame: CGRect(origin: CGPoint(), size: tabsSize))
|
||||
|
||||
var lensSelection: (x: CGFloat, width: CGFloat)
|
||||
if let selectionGestureState = self.selectionGestureState {
|
||||
lensSelection = (selectionGestureState.currentX, itemSize.width + innerInset * 2.0)
|
||||
} else if let selectionFrame {
|
||||
|
|
@ -475,7 +677,62 @@ public final class TabBarComponent: Component {
|
|||
lensSelection = (0.0, itemSize.width)
|
||||
}
|
||||
|
||||
self.liquidLensView.update(size: size, selectionX: lensSelection.x, selectionWidth: lensSelection.width, isDark: component.theme.overallDarkAppearance, isLifted: self.selectionGestureState != nil, transition: transition)
|
||||
var lensSize: CGSize = tabsSize
|
||||
var isLensCollapsed = false
|
||||
if let search = component.search, search.isActive {
|
||||
isLensCollapsed = true
|
||||
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)
|
||||
|
||||
var size = tabsSize
|
||||
|
||||
if let search = component.search {
|
||||
let searchSize: CGSize
|
||||
let searchFrame: CGRect
|
||||
if search.isActive {
|
||||
size.width = availableSize.width
|
||||
searchSize = CGSize(width: availableSize.width - 48.0 - 8.0, height: 48.0)
|
||||
searchFrame = CGRect(origin: CGPoint(x: 48.0 + 8.0, y: size.height - searchSize.height), size: searchSize)
|
||||
} else {
|
||||
searchSize = CGSize(width: barHeight, height: barHeight)
|
||||
size.width += barHeight + 8.0
|
||||
searchFrame = CGRect(origin: CGPoint(x: availableSize.width - searchSize.width, y: 0.0), size: searchSize)
|
||||
}
|
||||
|
||||
let searchView: NavigationSearchView
|
||||
var searchViewTransition = transition
|
||||
if let current = self.searchView {
|
||||
searchView = current
|
||||
} else {
|
||||
searchViewTransition = searchViewTransition.withAnimation(.none)
|
||||
searchView = NavigationSearchView(action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.search?.activate()
|
||||
})
|
||||
self.searchView = searchView
|
||||
self.backgroundContainer.contentView.addSubview(searchView)
|
||||
searchView.frame = CGRect(origin: CGPoint(x: availableSize.width + 50.0, y: 0.0), size: searchSize)
|
||||
}
|
||||
searchView.update(size: searchSize, theme: component.theme, strings: component.strings, isActive: search.isActive, transition: searchViewTransition)
|
||||
transition.setFrame(view: searchView, frame: searchFrame)
|
||||
} else {
|
||||
if let searchView = self.searchView {
|
||||
self.searchView = nil
|
||||
transition.setFrame(view: searchView, frame: CGRect(origin: CGPoint(x: availableSize.width + 50.0, y: 0.0), size: searchView.bounds.size), completion: { [weak searchView] completed in
|
||||
guard let searchView, completed else {
|
||||
return
|
||||
}
|
||||
searchView.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.backgroundContainer.update(size: size, isDark: component.theme.overallDarkAppearance, transition: transition)
|
||||
|
||||
return size
|
||||
}
|
||||
|
|
@ -493,11 +750,13 @@ public final class TabBarComponent: Component {
|
|||
private final class ItemComponent: Component {
|
||||
let item: TabBarComponent.Item
|
||||
let theme: PresentationTheme
|
||||
let isCompact: Bool
|
||||
let isSelected: Bool
|
||||
|
||||
init(item: TabBarComponent.Item, theme: PresentationTheme, isSelected: Bool) {
|
||||
init(item: TabBarComponent.Item, theme: PresentationTheme, isCompact: Bool, isSelected: Bool) {
|
||||
self.item = item
|
||||
self.theme = theme
|
||||
self.isCompact = isCompact
|
||||
self.isSelected = isSelected
|
||||
}
|
||||
|
||||
|
|
@ -508,6 +767,9 @@ private final class ItemComponent: Component {
|
|||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.isCompact != rhs.isCompact {
|
||||
return false
|
||||
}
|
||||
if lhs.isSelected != rhs.isSelected {
|
||||
return false
|
||||
}
|
||||
|
|
@ -562,6 +824,8 @@ private final class ItemComponent: Component {
|
|||
}
|
||||
|
||||
func update(component: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.25)
|
||||
|
||||
let previousComponent = self.component
|
||||
|
||||
if previousComponent?.item.item !== component.item.item {
|
||||
|
|
@ -619,7 +883,7 @@ private final class ItemComponent: Component {
|
|||
content: LottieComponent.AppBundleContent(
|
||||
name: animationName
|
||||
),
|
||||
color: component.isSelected ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor,
|
||||
color: (component.isSelected && !component.isCompact) ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor,
|
||||
placeholderColor: nil,
|
||||
startingPosition: .end,
|
||||
size: CGSize(width: 48.0, height: 48.0),
|
||||
|
|
@ -692,6 +956,7 @@ private final class ItemComponent: Component {
|
|||
self.contextContainerView.contentView.addSubview(titleView)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
alphaTransition.setAlpha(view: titleView, alpha: component.isCompact ? 0.0 : 1.0)
|
||||
}
|
||||
|
||||
if let badgeText = component.item.item.badgeValue, !badgeText.isEmpty {
|
||||
|
|
@ -723,6 +988,7 @@ private final class ItemComponent: Component {
|
|||
self.contextContainerView.contentView.addSubview(badgeView)
|
||||
}
|
||||
badgeTransition.setFrame(view: badgeView, frame: badgeFrame)
|
||||
alphaTransition.setAlpha(view: badgeView, alpha: component.isCompact ? 0.0 : 1.0)
|
||||
}
|
||||
} else if let badge = self.badge {
|
||||
self.badge = nil
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||
}
|
||||
|
||||
public func addRootControllers(showCallsTab: Bool) {
|
||||
let tabBarController = TabBarControllerImpl(theme: self.presentationData.theme)
|
||||
let tabBarController = TabBarControllerImpl(theme: self.presentationData.theme, strings: self.presentationData.strings)
|
||||
tabBarController.navigationPresentation = .master
|
||||
let chatListController = self.context.sharedContext.makeChatListController(context: self.context, location: .chatList(groupId: .root), controlsHistoryPreload: true, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild)
|
||||
if let sharedContext = self.context.sharedContext as? SharedAccountContextImpl {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue