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