Search filters improvements

This commit is contained in:
Ilya Laktyushin 2020-09-21 20:54:32 +03:00
parent 7a8d5e27f2
commit d59f695e6f
38 changed files with 4080 additions and 3180 deletions

View File

@ -5743,6 +5743,7 @@ Any member of this group will be able to see messages in the channel.";
"Call.AccountIsLoggedOnCurrentDevice" = "Sorry, you can't call %@ because that account is logged in to Telegram on the device you're using for the call.";
"ChatList.Search.FilterChats" = "Chats";
"ChatList.Search.FilterMedia" = "Media";
"ChatList.Search.FilterLinks" = "Links";
"ChatList.Search.FilterFiles" = "Files";

View File

@ -106,7 +106,11 @@ extension CameraOutput: AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
let codes: [CameraCode] = metadataObjects.filter { $0.type == .qr }.compactMap { object in
if let object = object as? AVMetadataMachineReadableCodeObject, let stringValue = object.stringValue, !stringValue.isEmpty {
#if targetEnvironment(simulator)
return CameraCode(type: .qr, message: stringValue, corners: [CGPoint(), CGPoint(), CGPoint(), CGPoint()])
#else
return CameraCode(type: .qr, message: stringValue, corners: object.corners)
#endif
} else {
return nil
}

View File

@ -109,7 +109,6 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
private var strings: PresentationStrings
private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings)>
private let mode: HorizontalPeerItemMode
private let sectionHeaderNode: ListSectionHeaderNode
private let listView: ListView
private let share: Bool
@ -133,15 +132,11 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
self.peerContextAction = peerContextAction
self.isPeerSelected = isPeerSelected
self.sectionHeaderNode = ListSectionHeaderNode(theme: theme)
self.sectionHeaderNode.title = strings.DialogList_RecentTitlePeople.uppercased()
self.listView = ListView()
self.listView.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
super.init()
self.addSubnode(self.sectionHeaderNode)
self.addSubnode(self.listView)
let peersDisposable = DisposableSet()
@ -249,20 +244,14 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
self.theme = theme
self.strings = strings
self.themeAndStringsPromise.set(.single((self.theme, self.strings)))
self.sectionHeaderNode.title = strings.DialogList_RecentTitlePeople.uppercased()
self.sectionHeaderNode.updateTheme(theme: theme)
}
}
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
return CGSize(width: constrainedSize.width, height: 114.0)
return CGSize(width: constrainedSize.width, height: 86.0)
}
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
self.sectionHeaderNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: 28.0))
self.sectionHeaderNode.updateLayout(size: CGSize(width: size.width, height: 28.0), leftInset: leftInset, rightInset: rightInset)
var insets = UIEdgeInsets()
insets.top += leftInset
insets.bottom += rightInset
@ -277,7 +266,7 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 92.0, height: size.width)
self.listView.position = CGPoint(x: size.width / 2.0, y: 92.0 / 2.0 + 28.0)
self.listView.position = CGPoint(x: size.width / 2.0, y: 92.0 / 2.0)
self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: 92.0, height: size.width), insets: insets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
self.itemCustomWidthValuePromise.set(itemCustomWidth)
}

View File

@ -1683,39 +1683,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
if let searchContentNode = strongSelf.searchContentNode {
var updatedDisplayFiltersPanelImpl: ((Bool) -> Void)?
if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, navigationController: strongSelf.navigationController as? NavigationController, updatedDisplayFiltersPanel: { display in
updatedDisplayFiltersPanelImpl?(display)
}) {
if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, navigationController: strongSelf.navigationController as? NavigationController) {
let (filterContainerNode, activate) = filterContainerNodeAndActivate
strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: false)
if let parentController = strongSelf.parent as? TabBarController {
parentController.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: true)
}
activate()
var currentDisplay = true
updatedDisplayFiltersPanelImpl = { [weak self, weak filterContainerNode] display in
guard let strongSelf = self, let strongFilterContainerNode = filterContainerNode else {
return
}
if currentDisplay != display {
currentDisplay = display
let node = display ? strongFilterContainerNode : nil
strongSelf.navigationBar?.setSecondaryContentNode(node, animated: false)
if let parentController = strongSelf.parent as? TabBarController {
parentController.navigationBar?.setSecondaryContentNode(node, animated: true)
}
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
if let layout = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, transition: transition)
(strongSelf.parent as? TabBarController)?.updateLayout(transition: transition)
}
}
}
}
}

View File

@ -1147,7 +1147,7 @@ final class ChatListControllerNode: ASDisplayNode {
}
}
func activateSearch(placeholderNode: SearchBarPlaceholderNode, navigationController: NavigationController?, updatedDisplayFiltersPanel: ((Bool) -> Void)?) -> (ASDisplayNode, () -> Void)? {
func activateSearch(placeholderNode: SearchBarPlaceholderNode, navigationController: NavigationController?) -> (ASDisplayNode, () -> Void)? {
guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
return nil
}
@ -1169,9 +1169,7 @@ final class ChatListControllerNode: ASDisplayNode {
self?.controller?.present(c, in: .window(.root), with: a)
}, presentInGlobalOverlay: { [weak self] c, a in
self?.controller?.presentInGlobalOverlay(c, with: a)
}, navigationController: navigationController, updatedDisplayFiltersPanel: { display in
updatedDisplayFiltersPanel?(display)
})
}, navigationController: navigationController)
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: contentNode, cancel: { [weak self] in
if let requestDeactivateSearch = self?.requestDeactivateSearch {

View File

@ -837,8 +837,6 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
self.scrollNode.view.contentSize = CGSize(width: leftOffset, height: size.height)
var previousFrame: CGRect?
var nextFrame: CGRect?
var selectedFrame: CGRect?
if let selectedFilter = selectedFilter, let currentIndex = reorderedFilters.firstIndex(where: { $0.id == selectedFilter }) {
func interpolateFrame(from fromValue: CGRect, to toValue: CGRect, t: CGFloat) -> CGRect {

View File

@ -94,7 +94,7 @@ class ChatListRecentPeersListItemNode: ListViewItemNode {
let currentItem = self.item
return { [weak self] item, params, last in
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 124.0), insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0))
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 96.0), insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0))
return (nodeLayout, { [weak self] in
var updatedTheme: PresentationTheme?

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@ import TelegramCore
import TelegramPresentationData
enum ChatListSearchFilter: Equatable {
case chats
case media
case links
case files
@ -18,16 +19,18 @@ enum ChatListSearchFilter: Equatable {
var id: Int32 {
switch self {
case .media:
case .chats:
return 0
case .links:
case .media:
return 1
case .files:
case .links:
return 2
case .music:
case .files:
return 3
case .voice:
case .music:
return 4
case .voice:
return 5
case let .peer(peerId, _, _, _):
return peerId.id
case let .date(date, _):
@ -41,12 +44,10 @@ private final class ItemNode: ASDisplayNode {
private let iconNode: ASImageNode
private let titleNode: ImmediateTextNode
private let titleActiveNode: ImmediateTextNode
private let buttonNode: HighlightTrackingButtonNode
private var selectionFraction: CGFloat = 0.0
private(set) var unreadCount: Int = 0
private var isReordering: Bool = false
private var theme: PresentationTheme?
@ -63,11 +64,17 @@ private final class ItemNode: ASDisplayNode {
self.titleNode.displaysAsynchronously = false
self.titleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
self.titleActiveNode = ImmediateTextNode()
self.titleActiveNode.displaysAsynchronously = false
self.titleActiveNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
self.titleActiveNode.alpha = 0.0
self.buttonNode = HighlightTrackingButtonNode()
super.init()
self.addSubnode(self.titleNode)
self.addSubnode(self.titleActiveNode)
self.addSubnode(self.iconNode)
self.addSubnode(self.buttonNode)
@ -95,27 +102,32 @@ private final class ItemNode: ASDisplayNode {
self.pressed()
}
func update(type: ChatListSearchFilter, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
func update(type: ChatListSearchFilter, presentationData: PresentationData, selectionFraction: CGFloat, transition: ContainedViewLayoutTransition) {
self.selectionFraction = selectionFraction
let title: String
let icon: UIImage?
let color = presentationData.theme.list.itemSecondaryTextColor
switch type {
case .chats:
title = presentationData.strings.ChatList_Search_FilterChats
icon = nil
case .media:
title = presentationData.strings.ChatList_Search_FilterMedia
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Media"), color: color)
icon = nil
case .links:
title = presentationData.strings.ChatList_Search_FilterLinks
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Links"), color: color)
icon = nil
case .files:
title = presentationData.strings.ChatList_Search_FilterFiles
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Files"), color: color)
icon = nil
case .music:
title = presentationData.strings.ChatList_Search_FilterMusic
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Music"), color: color)
icon = nil
case .voice:
title = presentationData.strings.ChatList_Search_FilterVoice
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Voice"), color: color)
icon = nil
case let .peer(peerId, isGroup, displayTitle, _):
title = displayTitle
let image: UIImage?
@ -133,6 +145,12 @@ private final class ItemNode: ASDisplayNode {
}
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: color)
self.titleActiveNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: presentationData.theme.list.itemAccentColor)
let selectionAlpha: CGFloat = selectionFraction * selectionFraction
let deselectionAlpha: CGFloat = 1.0// - selectionFraction
transition.updateAlpha(node: self.titleNode, alpha: deselectionAlpha)
transition.updateAlpha(node: self.titleActiveNode, alpha: selectionAlpha)
if self.theme !== presentationData.theme {
self.theme = presentationData.theme
@ -141,14 +159,17 @@ private final class ItemNode: ASDisplayNode {
}
func updateLayout(height: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let iconInset: CGFloat = 22.0
var iconInset: CGFloat = 0.0
if let image = self.iconNode.image {
iconInset = 22.0
self.iconNode.frame = CGRect(x: 0.0, y: floorToScreenPixels((height - image.size.height) / 2.0), width: image.size.width, height: image.size.height)
}
let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude))
let _ = self.titleActiveNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude))
let titleFrame = CGRect(origin: CGPoint(x: -self.titleNode.insets.left + iconInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
self.titleNode.frame = titleFrame
self.titleActiveNode.frame = titleFrame
return titleSize.width - self.titleNode.insets.left - self.titleNode.insets.right + iconInset
}
@ -177,15 +198,23 @@ enum ChatListSearchFilterEntry: Equatable {
final class ChatListSearchFiltersContainerNode: ASDisplayNode {
private let scrollNode: ASScrollNode
private let selectedLineNode: ASImageNode
private var itemNodes: [ChatListSearchFilterEntryId: ItemNode] = [:]
var filterPressed: ((ChatListSearchFilter) -> Void)?
private var currentParams: (size: CGSize, sideInset: CGFloat, filters: [ChatListSearchFilterEntry], presentationData: PresentationData)?
private var currentParams: (size: CGSize, sideInset: CGFloat, filters: [ChatListSearchFilterEntry], selectedFilter: ChatListSearchFilterEntryId?, transitionFraction: CGFloat, presentationData: PresentationData)?
private var previousSelectedAbsFrame: CGRect?
private var previousSelectedFrame: CGRect?
override init() {
self.scrollNode = ASScrollNode()
self.selectedLineNode = ASImageNode()
self.selectedLineNode.displaysAsynchronously = false
self.selectedLineNode.displayWithoutProcessing = true
super.init()
self.scrollNode.view.showsHorizontalScrollIndicator = false
@ -197,21 +226,31 @@ final class ChatListSearchFiltersContainerNode: ASDisplayNode {
}
self.addSubnode(self.scrollNode)
self.addSubnode(self.selectedLineNode)
}
func cancelAnimations() {
self.scrollNode.layer.removeAllAnimations()
}
func update(size: CGSize, sideInset: CGFloat, filters: [ChatListSearchFilterEntry], presentationData: PresentationData, transition proposedTransition: ContainedViewLayoutTransition) {
func update(size: CGSize, sideInset: CGFloat, filters: [ChatListSearchFilterEntry], selectedFilter: ChatListSearchFilterEntryId?, transitionFraction: CGFloat, presentationData: PresentationData, transition proposedTransition: ContainedViewLayoutTransition) {
let isFirstTime = self.currentParams == nil
let transition: ContainedViewLayoutTransition = isFirstTime ? .immediate : proposedTransition
var focusOnSelectedFilter = self.currentParams?.selectedFilter != selectedFilter
let previousScrollBounds = self.scrollNode.bounds
let previousContentWidth = self.scrollNode.view.contentSize.width
if self.currentParams?.presentationData.theme !== presentationData.theme {
self.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor
self.selectedLineNode.image = generateImage(CGSize(width: 8.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)
}
self.currentParams = (size: size, sideInset: sideInset, filters: filters, presentationData: presentationData)
self.currentParams = (size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, transitionFraction: transitionFraction, presentationData: presentationData)
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size))
@ -229,7 +268,19 @@ final class ChatListSearchFiltersContainerNode: ASDisplayNode {
})
self.itemNodes[filter.id] = itemNode
}
itemNode.update(type: type, presentationData: presentationData, transition: itemNodeTransition)
let selectionFraction: CGFloat
if selectedFilter == filter.id {
selectionFraction = 1.0 - abs(transitionFraction)
} else if i != 0 && selectedFilter == filters[i - 1].id {
selectionFraction = max(0.0, -transitionFraction)
} else if i != filters.count - 1 && selectedFilter == filters[i + 1].id {
selectionFraction = max(0.0, transitionFraction)
} else {
selectionFraction = 0.0
}
itemNode.update(type: type, presentationData: presentationData, selectionFraction: selectionFraction, transition: itemNodeTransition)
}
}
@ -253,6 +304,7 @@ final class ChatListSearchFiltersContainerNode: ASDisplayNode {
var tabSizes: [(ChatListSearchFilterEntryId, CGSize, ItemNode, Bool)] = []
var totalRawTabSize: CGFloat = 0.0
var selectionFrames: [CGRect] = []
for filter in filters {
guard let itemNode = self.itemNodes[filter.id] else {
@ -308,6 +360,8 @@ final class ChatListSearchFiltersContainerNode: ASDisplayNode {
paneNode.updateArea(size: paneFrame.size, sideInset: spacing / 2.0, transition: itemNodeTransition)
paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -spacing / 2.0, bottom: 0.0, right: -spacing / 2.0)
selectionFrames.append(paneFrame)
leftOffset += paneNodeSize.width + spacing
}
leftOffset -= spacing
@ -315,6 +369,66 @@ final class ChatListSearchFiltersContainerNode: ASDisplayNode {
self.scrollNode.view.contentSize = CGSize(width: leftOffset, height: size.height)
var selectedFrame: CGRect?
if let selectedFilter = selectedFilter, let currentIndex = filters.firstIndex(where: { $0.id == selectedFilter }) {
func interpolateFrame(from fromValue: CGRect, to toValue: CGRect, t: CGFloat) -> CGRect {
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)))
}
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 != filters.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
} else {
transition.updateFrame(node: self.selectedLineNode, frame: lineFrame)
}
transition.updateAlpha(node: self.selectedLineNode, alpha: 1.0)
if let previousSelectedFrame = self.previousSelectedFrame {
let previousContentOffsetX = max(0.0, min(previousContentWidth - previousScrollBounds.width, floor(previousSelectedFrame.midX - previousScrollBounds.width / 2.0)))
if abs(previousContentOffsetX - previousScrollBounds.minX) < 1.0 {
focusOnSelectedFilter = true
}
}
if focusOnSelectedFilter {
let updatedBounds: CGRect
if transitionFraction.isZero && selectedFilter == filters.first?.id {
updatedBounds = CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size)
} else if transitionFraction.isZero && selectedFilter == filters.last?.id {
updatedBounds = 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)))
updatedBounds = CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size)
}
self.scrollNode.bounds = updatedBounds
}
transition.animateHorizontalOffsetAdditive(node: self.scrollNode, offset: previousScrollBounds.minX - self.scrollNode.bounds.minX)
self.previousSelectedAbsFrame = selectedFrame.offsetBy(dx: -self.scrollNode.bounds.minX, dy: 0.0)
self.previousSelectedFrame = selectedFrame
} else {
self.selectedLineNode.isHidden = true
self.previousSelectedAbsFrame = nil
self.previousSelectedFrame = nil
}
if updated && self.scrollNode.view.contentOffset.x > 0.0 {
self.scrollNode.view.contentOffset = CGPoint()
}

File diff suppressed because it is too large Load Diff

View File

@ -117,6 +117,7 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
}
func update(layout: ContainerViewLayout, presentationData: PresentationData, transition: ContainedViewLayoutTransition) -> CGFloat {
self.validLayout = layout
if presentationData.theme !== self.theme {
self.theme = presentationData.theme

View File

@ -0,0 +1,539 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import TelegramPresentationData
import Postbox
import SyncCore
import TelegramCore
import AccountContext
import ContextUI
protocol ChatListSearchPaneNode: ASDisplayNode {
var isReady: Signal<Bool, NoError> { get }
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition)
func scrollToTop() -> Bool
func cancelPreviewGestures()
func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
func addToTransitionSurface(view: UIView)
func updateHiddenMedia()
func updateSelectedMessages(animated: Bool)
func previewViewAndActionAtLocation(_ location: CGPoint) -> (UIView, CGRect, Any)?
var searchCurrentMessages: [Message]? { get }
}
final class ChatListSearchPaneWrapper {
let key: ChatListSearchPaneKey
let node: ChatListSearchPaneNode
var isAnimatingOut: Bool = false
private var appliedParams: (CGSize, CGFloat, CGFloat, CGFloat, PresentationData)?
init(key: ChatListSearchPaneKey, node: ChatListSearchPaneNode) {
self.key = key
self.node = node
}
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
if let (currentSize, currentSideInset, currentBottomInset, visibleHeight, currentPresentationData) = self.appliedParams {
if currentSize == size && currentSideInset == sideInset && currentBottomInset == bottomInset && currentPresentationData === presentationData {
return
}
}
self.appliedParams = (size, sideInset, bottomInset, visibleHeight, presentationData)
self.node.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: synchronous, transition: transition)
}
}
enum ChatListSearchPaneKey {
case chats
case media
case links
case files
case music
case voice
}
private let availablePanes: [ChatListSearchPaneKey] = [.chats, .media, .links, .files, .music, .voice]
struct ChatListSearchPaneSpecifier: Equatable {
var key: ChatListSearchPaneKey
var title: String
}
private func interpolateFrame(from fromValue: CGRect, to toValue: CGRect, t: CGFloat) -> CGRect {
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)))
}
private final class ChatListSearchPendingPane {
let pane: ChatListSearchPaneWrapper
private var disposable: Disposable?
var isReady: Bool = false
init(
context: AccountContext,
interaction: ChatListSearchInteraction,
navigationController: NavigationController?,
peersFilter: ChatListNodePeersFilter,
searchQuery: Signal<String?, NoError>,
searchOptions: Signal<ChatListSearchOptions?, NoError>,
key: ChatListSearchPaneKey,
hasBecomeReady: @escaping (ChatListSearchPaneKey) -> Void
) {
let paneNode: ChatListSearchPaneNode
switch key {
case .chats:
paneNode = ChatListSearchListPaneNode(context: context, interaction: interaction, tagMask: nil, peersFilter: peersFilter, searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController)
case .media:
paneNode = ChatListSearchListPaneNode(context: context, interaction: interaction, tagMask: .photoOrVideo, peersFilter: [], searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController)
case .files:
paneNode = ChatListSearchListPaneNode(context: context, interaction: interaction, tagMask: .file, peersFilter: [], searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController)
case .links:
paneNode = ChatListSearchListPaneNode(context: context, interaction: interaction, tagMask: .webPage, peersFilter: [], searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController)
case .voice:
paneNode = ChatListSearchListPaneNode(context: context, interaction: interaction, tagMask: .voiceOrInstantVideo, peersFilter: [], searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController)
case .music:
paneNode = ChatListSearchListPaneNode(context: context, interaction: interaction, tagMask: .music, peersFilter: [], searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController)
}
self.pane = ChatListSearchPaneWrapper(key: key, node: paneNode)
self.disposable = (paneNode.isReady
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in
self?.isReady = true
hasBecomeReady(key)
})
}
deinit {
self.disposable?.dispose()
}
}
final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
private let context: AccountContext
private let peersFilter: ChatListNodePeersFilter
private let searchQuery: Signal<String?, NoError>
private let searchOptions: Signal<ChatListSearchOptions?, NoError>
private let navigationController: NavigationController?
var interaction: ChatListSearchInteraction?
private let coveringBackgroundNode: ASDisplayNode
private let separatorNode: ASDisplayNode
let isReady = Promise<Bool>()
var didSetIsReady = false
private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData)?
private(set) var currentPaneKey: ChatListSearchPaneKey?
var pendingSwitchToPaneKey: ChatListSearchPaneKey?
var currentPane: ChatListSearchPaneWrapper? {
if let currentPaneKey = self.currentPaneKey {
return self.currentPanes[currentPaneKey]
} else {
return nil
}
}
private var currentPanes: [ChatListSearchPaneKey: ChatListSearchPaneWrapper] = [:]
private var pendingPanes: [ChatListSearchPaneKey: ChatListSearchPendingPane] = [:]
private var transitionFraction: CGFloat = 0.0
var openPeerContextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?
var currentPaneUpdated: ((ChatListSearchPaneKey?, CGFloat, ContainedViewLayoutTransition) -> Void)?
var requestExpandTabs: (() -> Bool)?
private var currentAvailablePanes: [ChatListSearchPaneKey]?
init(context: AccountContext, peersFilter: ChatListNodePeersFilter, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
self.context = context
self.peersFilter = peersFilter
self.searchQuery = searchQuery
self.searchOptions = searchOptions
self.navigationController = navigationController
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
self.coveringBackgroundNode = ASDisplayNode()
self.coveringBackgroundNode.isLayerBacked = true
super.init()
self.addSubnode(self.separatorNode)
self.addSubnode(self.coveringBackgroundNode)
}
func requestSelectPane(_ key: ChatListSearchPaneKey) {
if self.currentPaneKey == key {
if let requestExpandTabs = self.requestExpandTabs, requestExpandTabs() {
} else {
let _ = self.currentPane?.node.scrollToTop()
}
return
}
if self.currentPanes[key] != nil {
self.currentPaneKey = key
if let (size, sideInset, bottomInset, visibleHeight, presentationData) = self.currentParams {
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, transition: .animated(duration: 0.4, curve: .spring))
}
} else if self.pendingSwitchToPaneKey != key {
self.pendingSwitchToPaneKey = key
if let (size, sideInset, bottomInset, visibleHeight, presentationData) = self.currentParams {
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, transition: .animated(duration: 0.4, curve: .spring))
}
}
}
override func didLoad() {
super.didLoad()
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
guard let strongSelf = self, let currentPaneKey = strongSelf.currentPaneKey, let index = availablePanes.firstIndex(of: currentPaneKey) else {
return []
}
if index == 0 {
return .left
}
return [.left, .right]
})
panRecognizer.delegate = self
panRecognizer.delaysTouchesBegan = false
panRecognizer.cancelsTouchesInView = true
self.view.addGestureRecognizer(panRecognizer)
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer {
return false
}
if let _ = otherGestureRecognizer as? UIPanGestureRecognizer {
return true
}
return false
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
func cancelContextGestures(view: UIView) {
if let gestureRecognizers = view.gestureRecognizers {
for gesture in gestureRecognizers {
if let gesture = gesture as? ContextGesture {
gesture.cancel()
}
}
}
for subview in view.subviews {
cancelContextGestures(view: subview)
}
}
cancelContextGestures(view: self.view)
case .changed:
if let (size, sideInset, bottomInset, visibleHeight, presentationData) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) {
let translation = recognizer.translation(in: self.view)
var transitionFraction = translation.x / size.width
if currentIndex <= 0 {
transitionFraction = min(0.0, transitionFraction)
}
if currentIndex >= availablePanes.count - 1 {
transitionFraction = max(0.0, transitionFraction)
}
self.transitionFraction = transitionFraction
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, transition: .immediate)
}
case .cancelled, .ended:
if let (size, sideInset, bottomInset, visibleHeight, presentationData) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) {
let translation = recognizer.translation(in: self.view)
let velocity = recognizer.velocity(in: self.view)
var directionIsToRight: Bool?
if abs(velocity.x) > 10.0 {
directionIsToRight = velocity.x < 0.0
} else {
if abs(translation.x) > size.width / 2.0 {
directionIsToRight = translation.x > size.width / 2.0
}
}
var updated = false
if let directionIsToRight = directionIsToRight {
var updatedIndex = currentIndex
if directionIsToRight {
updatedIndex = min(updatedIndex + 1, availablePanes.count - 1)
} else {
updatedIndex = max(updatedIndex - 1, 0)
}
let switchToKey = availablePanes[updatedIndex]
if switchToKey != self.currentPaneKey && self.currentPanes[switchToKey] != nil{
self.currentPaneKey = switchToKey
updated = true
}
}
self.transitionFraction = 0.0
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, transition: .animated(duration: 0.35, curve: .spring))
}
default:
break
}
}
func scrollToTop() -> Bool {
if let currentPane = self.currentPane {
return currentPane.node.scrollToTop()
} else {
return false
}
}
func updateHiddenMedia() {
self.currentPane?.node.updateHiddenMedia()
}
func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
return self.currentPane?.node.transitionNodeForGallery(messageId: messageId, media: media)
}
func updateSelectedMessageIds(_ selectedMessageIds: Set<MessageId>?, animated: Bool) {
for (_, pane) in self.currentPanes {
pane.node.updateSelectedMessages(animated: animated)
}
for (_, pane) in self.pendingPanes {
pane.pane.node.updateSelectedMessages(animated: animated)
}
}
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
let previousAvailablePanes = self.currentAvailablePanes ?? []
self.currentAvailablePanes = availablePanes
if let currentPaneKey = self.currentPaneKey, !availablePanes.contains(currentPaneKey) {
var nextCandidatePaneKey: ChatListSearchPaneKey?
if let index = previousAvailablePanes.firstIndex(of: currentPaneKey), index != 0 {
for i in (0 ... index - 1).reversed() {
if availablePanes.contains(previousAvailablePanes[i]) {
nextCandidatePaneKey = previousAvailablePanes[i]
}
}
}
if nextCandidatePaneKey == nil {
nextCandidatePaneKey = availablePanes.first
}
if let nextCandidatePaneKey = nextCandidatePaneKey {
self.pendingSwitchToPaneKey = nextCandidatePaneKey
} else {
self.currentPaneKey = nil
self.pendingSwitchToPaneKey = nil
}
} else if self.currentPaneKey == nil {
self.pendingSwitchToPaneKey = availablePanes.first
}
let currentIndex: Int?
if let currentPaneKey = self.currentPaneKey {
currentIndex = availablePanes.firstIndex(of: currentPaneKey)
} else {
currentIndex = nil
}
self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData)
transition.updateAlpha(node: self.coveringBackgroundNode, alpha: 0.0)
self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor
self.coveringBackgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor
self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
let tabsHeight: CGFloat = 48.0
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)))
transition.updateFrame(node: self.coveringBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: tabsHeight + UIScreenPixel)))
let paneFrame = CGRect(origin: CGPoint(x: 0.0, y: tabsHeight), size: CGSize(width: size.width, height: size.height - tabsHeight))
var visiblePaneIndices: [Int] = []
var requiredPendingKeys: [ChatListSearchPaneKey] = []
if let currentIndex = currentIndex {
if currentIndex != 0 {
visiblePaneIndices.append(currentIndex - 1)
}
visiblePaneIndices.append(currentIndex)
if currentIndex != availablePanes.count - 1 {
visiblePaneIndices.append(currentIndex + 1)
}
for index in visiblePaneIndices {
let key = availablePanes[index]
if self.currentPanes[key] == nil && self.pendingPanes[key] == nil {
requiredPendingKeys.append(key)
}
}
}
if let pendingSwitchToPaneKey = self.pendingSwitchToPaneKey {
if self.currentPanes[pendingSwitchToPaneKey] == nil && self.pendingPanes[pendingSwitchToPaneKey] == nil {
if !requiredPendingKeys.contains(pendingSwitchToPaneKey) {
requiredPendingKeys.append(pendingSwitchToPaneKey)
}
}
}
for key in requiredPendingKeys {
if self.pendingPanes[key] == nil {
var leftScope = false
let pane = ChatListSearchPendingPane(
context: self.context,
interaction: self.interaction!,
navigationController: self.navigationController,
peersFilter: self.peersFilter,
searchQuery: self.searchQuery,
searchOptions: self.searchOptions,
key: key,
hasBecomeReady: { [weak self] key in
let apply: () -> Void = {
guard let strongSelf = self else {
return
}
if let (size, sideInset, bottomInset, visibleHeight, presentationData) = strongSelf.currentParams {
var transition: ContainedViewLayoutTransition = .immediate
if strongSelf.pendingSwitchToPaneKey == key && strongSelf.currentPaneKey != nil {
transition = .animated(duration: 0.4, curve: .spring)
}
strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, transition: transition)
}
}
if leftScope {
apply()
}
}
)
self.pendingPanes[key] = pane
pane.pane.node.frame = paneFrame
pane.pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .immediate)
leftScope = true
}
}
for (key, pane) in self.pendingPanes {
pane.pane.node.frame = paneFrame
pane.pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: self.currentPaneKey == nil, transition: .immediate)
if pane.isReady {
self.pendingPanes.removeValue(forKey: key)
self.currentPanes[key] = pane.pane
}
}
var paneDefaultTransition = transition
var previousPaneKey: ChatListSearchPaneKey?
var paneSwitchAnimationOffset: CGFloat = 0.0
var updatedCurrentIndex = currentIndex
var animatePaneTransitionOffset: CGFloat?
if let pendingSwitchToPaneKey = self.pendingSwitchToPaneKey, let pane = self.currentPanes[pendingSwitchToPaneKey] {
self.pendingSwitchToPaneKey = nil
previousPaneKey = self.currentPaneKey
self.currentPaneKey = pendingSwitchToPaneKey
updatedCurrentIndex = availablePanes.firstIndex(of: pendingSwitchToPaneKey)
if let previousPaneKey = previousPaneKey, let previousIndex = availablePanes.firstIndex(of: previousPaneKey), let updatedCurrentIndex = updatedCurrentIndex {
if updatedCurrentIndex < previousIndex {
paneSwitchAnimationOffset = -size.width
} else {
paneSwitchAnimationOffset = size.width
}
}
paneDefaultTransition = .immediate
}
for (key, pane) in self.currentPanes {
if let index = availablePanes.firstIndex(of: key), let updatedCurrentIndex = updatedCurrentIndex {
var paneWasAdded = false
if pane.node.supernode == nil {
self.addSubnode(pane.node)
paneWasAdded = true
}
let indexOffset = CGFloat(index - updatedCurrentIndex)
let paneTransition: ContainedViewLayoutTransition = paneWasAdded ? .immediate : paneDefaultTransition
let adjustedFrame = paneFrame.offsetBy(dx: size.width * self.transitionFraction + indexOffset * size.width, dy: 0.0)
let paneCompletion: () -> Void = { [weak self, weak pane] in
guard let strongSelf = self, let pane = pane else {
return
}
pane.isAnimatingOut = false
if let (size, sideInset, bottomInset, visibleHeight, presentationData) = strongSelf.currentParams {
if let currentPaneKey = strongSelf.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey), let paneIndex = availablePanes.firstIndex(of: key), abs(paneIndex - currentIndex) <= 1 {
} else {
if let pane = strongSelf.currentPanes.removeValue(forKey: key) {
pane.node.removeFromSupernode()
}
}
}
}
if let previousPaneKey = previousPaneKey, key == previousPaneKey {
pane.node.frame = adjustedFrame
let isAnimatingOut = pane.isAnimatingOut
pane.isAnimatingOut = true
transition.animateFrame(node: pane.node, from: paneFrame, to: paneFrame.offsetBy(dx: -paneSwitchAnimationOffset, dy: 0.0), completion: isAnimatingOut ? nil : { _ in
paneCompletion()
})
} else if let previousPaneKey = previousPaneKey, key == self.currentPaneKey {
pane.node.frame = adjustedFrame
let isAnimatingOut = pane.isAnimatingOut
pane.isAnimatingOut = true
transition.animatePositionAdditive(node: pane.node, offset: CGPoint(x: paneSwitchAnimationOffset, y: 0.0), completion: isAnimatingOut ? nil : {
paneCompletion()
})
} else {
let isAnimatingOut = pane.isAnimatingOut
pane.isAnimatingOut = true
paneTransition.updateFrame(node: pane.node, frame: adjustedFrame, completion: isAnimatingOut ? nil : { _ in
paneCompletion()
})
}
pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition)
}
}
for (_, pane) in self.pendingPanes {
let paneTransition: ContainedViewLayoutTransition = .immediate
paneTransition.updateFrame(node: pane.pane.node, frame: paneFrame)
pane.pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: paneTransition)
}
if !self.didSetIsReady {
if let currentPaneKey = self.currentPaneKey, let currentPane = self.currentPanes[currentPaneKey] {
self.didSetIsReady = true
self.isReady.set(currentPane.node.isReady)
} else if self.pendingSwitchToPaneKey == nil {
self.didSetIsReady = true
self.isReady.set(.single(true))
}
}
self.currentPaneUpdated?(self.currentPaneKey, self.transitionFraction, transition)
}
func allCurrentMessages() -> [MessageId: Message] {
var allMessages: [MessageId: Message] = [:]
for (_, pane) in self.currentPanes {
if let messages = pane.node.searchCurrentMessages {
for message in messages {
allMessages[message.id] = message
}
}
}
return allMessages
}
}

View File

@ -20,7 +20,7 @@ import LocalizedPeerData
import TextSelectionNode
private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: .white)
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: .white)
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: .white)
private let backwardImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/BackwardButton"), color: .white)
private let forwardImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/ForwardButton"), color: .white)
@ -439,7 +439,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
}
}
func setMessage(_ message: Message) {
func setMessage(_ message: Message, displayInfo: Bool = true) {
self.currentMessage = message
let canDelete: Bool
@ -464,14 +464,18 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
}
var authorNameText: String?
if let author = message.effectiveAuthor {
authorNameText = author.displayTitle(strings: self.strings, displayOrder: self.nameOrder)
} else if let peer = message.peers[message.id.peerId] {
authorNameText = peer.displayTitle(strings: self.strings, displayOrder: self.nameOrder)
}
let dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: message.timestamp)
var dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: message.timestamp)
if !displayInfo {
authorNameText = ""
dateText = ""
}
var messageText = NSAttributedString(string: "")
var hasCaption = false
@ -495,7 +499,6 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
self.actionButton.isHidden = message.containsSecretMedia || Namespaces.Message.allScheduled.contains(message.id.namespace)
if self.currentMessageText != messageText || canDelete != !self.deleteButton.isHidden || canShare != !self.actionButton.isHidden || self.currentAuthorNameText != authorNameText || self.currentDateText != dateText {
self.currentMessageText = messageText

View File

@ -143,12 +143,12 @@ private func galleryMessageCaptionText(_ message: Message) -> String {
return message.text
}
public func galleryItemForEntry(context: AccountContext, presentationData: PresentationData, entry: MessageHistoryEntry, isCentral: Bool = false, streamVideos: Bool, loopVideos: Bool = false, hideControls: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, configuration: GalleryConfiguration? = nil, tempFilePath: String? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void = { _, _ in }, present: @escaping (ViewController, Any?) -> Void) -> GalleryItem? {
public func galleryItemForEntry(context: AccountContext, presentationData: PresentationData, entry: MessageHistoryEntry, isCentral: Bool = false, streamVideos: Bool, loopVideos: Bool = false, hideControls: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, displayInfoOnTop: Bool = false, configuration: GalleryConfiguration? = nil, tempFilePath: String? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void = { _, _ in }, present: @escaping (ViewController, Any?) -> Void) -> GalleryItem? {
let message = entry.message
let location = entry.location
if let (media, mediaImage) = mediaForMessage(message: message) {
if let _ = media as? TelegramMediaImage {
return ChatImageGalleryItem(context: context, presentationData: presentationData, message: message, location: location, performAction: performAction, openActionOptions: openActionOptions, present: present)
return ChatImageGalleryItem(context: context, presentationData: presentationData, message: message, location: location, displayInfoOnTop: displayInfoOnTop, performAction: performAction, openActionOptions: openActionOptions, present: present)
} else if let file = media as? TelegramMediaFile {
if file.isVideo {
let content: UniversalVideoContent
@ -176,7 +176,7 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese
}
let caption = galleryCaptionStringWithAppliedEntities(text, entities: entities)
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: caption, hideControls: hideControls, fromPlayingVideo: fromPlayingVideo, landscape: landscape, timecode: timecode, configuration: configuration, playbackCompleted: playbackCompleted, performAction: performAction, openActionOptions: openActionOptions, storeMediaPlaybackState: storeMediaPlaybackState, present: present)
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: caption, displayInfoOnTop: displayInfoOnTop, hideControls: hideControls, fromPlayingVideo: fromPlayingVideo, landscape: landscape, timecode: timecode, configuration: configuration, playbackCompleted: playbackCompleted, performAction: performAction, openActionOptions: openActionOptions, storeMediaPlaybackState: storeMediaPlaybackState, present: present)
} else {
if let fileName = file.fileName, (fileName as NSString).pathExtension.lowercased() == "json" {
return ChatAnimationGalleryItem(context: context, presentationData: presentationData, message: message, location: location)
@ -187,7 +187,7 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese
pixelsCount = Int(dimensions.width) * Int(dimensions.height)
}
if (file.size == nil || file.size! < 4 * 1024 * 1024) && pixelsCount < 4096 * 4096 {
return ChatImageGalleryItem(context: context, presentationData: presentationData, message: message, location: location, performAction: performAction, openActionOptions: openActionOptions, present: present)
return ChatImageGalleryItem(context: context, presentationData: presentationData, message: message, location: location, displayInfoOnTop: displayInfoOnTop, performAction: performAction, openActionOptions: openActionOptions, present: present)
} else {
return ChatDocumentGalleryItem(context: context, presentationData: presentationData, message: message, location: location)
}
@ -215,7 +215,7 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese
}
}
if let content = content {
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, timecode: timecode, configuration: configuration, performAction: performAction, openActionOptions: openActionOptions, storeMediaPlaybackState: storeMediaPlaybackState, present: present)
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), displayInfoOnTop: displayInfoOnTop, fromPlayingVideo: fromPlayingVideo, landscape: landscape, timecode: timecode, configuration: configuration, performAction: performAction, openActionOptions: openActionOptions, storeMediaPlaybackState: storeMediaPlaybackState, present: present)
} else {
return nil
}
@ -453,7 +453,7 @@ public class GalleryController: ViewController, StandalonePresentableController
var entries: [MessageHistoryEntry] = []
var index = messages.count
for message in messages.reversed() {
entries.append(MessageHistoryEntry(message: message, isRead: false, location: MessageHistoryEntryLocation(index: Int(totalCount) - index, count: Int(totalCount)), monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)))
entries.append(MessageHistoryEntry(message: message, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)))
index -= 1
}
return GalleryMessageHistoryView.entries(entries, hasMore, false)
@ -469,6 +469,11 @@ public class GalleryController: ViewController, StandalonePresentableController
semaphore = nil
}
var displayInfoOnTop = false
if case .custom = source {
displayInfoOnTop = true
}
let syncResult = Atomic<(Bool, (() -> Void)?)>(value: (false, nil))
self.disposable.set(combineLatest(messageView, self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])).start(next: { [weak self] view, preferencesView in
let f: () -> Void = {
@ -524,7 +529,7 @@ public class GalleryController: ViewController, StandalonePresentableController
if entry.message.stableId == strongSelf.centralEntryStableId {
isCentral = true
}
if let item = galleryItemForEntry(context: context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: streamSingleVideo, fromPlayingVideo: isCentral && fromPlayingVideo, landscape: isCentral && landscape, timecode: isCentral ? timecode : nil, configuration: configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
if let item = galleryItemForEntry(context: context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: streamSingleVideo, fromPlayingVideo: isCentral && fromPlayingVideo, landscape: isCentral && landscape, timecode: isCentral ? timecode : nil, displayInfoOnTop: displayInfoOnTop, configuration: configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.presentInGlobalOverlay(c, with: a)
}
@ -961,6 +966,11 @@ public class GalleryController: ViewController, StandalonePresentableController
return baseNavigationController
}
var displayInfoOnTop = false
if case .custom = source {
displayInfoOnTop = true
}
var items: [GalleryItem] = []
var centralItemIndex: Int?
for entry in self.entries {
@ -968,7 +978,7 @@ public class GalleryController: ViewController, StandalonePresentableController
if entry.message.stableId == self.centralEntryStableId {
isCentral = true
}
if let item = galleryItemForEntry(context: self.context, presentationData: self.presentationData, entry: entry, streamVideos: self.streamVideos, fromPlayingVideo: isCentral && self.fromPlayingVideo, landscape: isCentral && self.landscape, timecode: isCentral ? self.timecode : nil, configuration: self.configuration, performAction: self.performAction, openActionOptions: self.openActionOptions, storeMediaPlaybackState: self.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
if let item = galleryItemForEntry(context: self.context, presentationData: self.presentationData, entry: entry, streamVideos: self.streamVideos, fromPlayingVideo: isCentral && self.fromPlayingVideo, landscape: isCentral && self.landscape, timecode: isCentral ? self.timecode : nil, displayInfoOnTop: displayInfoOnTop, configuration: self.configuration, performAction: self.performAction, openActionOptions: self.openActionOptions, storeMediaPlaybackState: self.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.presentInGlobalOverlay(c, with: a)
}
@ -1048,7 +1058,7 @@ public class GalleryController: ViewController, StandalonePresentableController
if entry.message.stableId == strongSelf.centralEntryStableId {
isCentral = true
}
if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, displayInfoOnTop: displayInfoOnTop, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.presentInGlobalOverlay(c, with: a)
}
@ -1078,7 +1088,7 @@ public class GalleryController: ViewController, StandalonePresentableController
var entries: [MessageHistoryEntry] = []
var index = messages.count
for message in messages.reversed() {
entries.append(MessageHistoryEntry(message: message, isRead: false, location: MessageHistoryEntryLocation(index: Int(totalCount) - index, count: Int(totalCount)), monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)))
entries.append(MessageHistoryEntry(message: message, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)))
index -= 1
}
@ -1100,7 +1110,7 @@ public class GalleryController: ViewController, StandalonePresentableController
if entry.message.stableId == strongSelf.centralEntryStableId {
isCentral = true
}
if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, displayInfoOnTop: displayInfoOnTop, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.presentInGlobalOverlay(c, with: a)
}

View File

@ -0,0 +1,62 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import Postbox
import TelegramPresentationData
import TelegramStringFormatting
private let titleFont = Font.medium(15.0)
private let dateFont = Font.regular(14.0)
final class GalleryTitleView: UIView, NavigationBarTitleView {
private let authorNameNode: ASTextNode
private let dateNode: ASTextNode
override init(frame: CGRect) {
self.authorNameNode = ASTextNode()
self.authorNameNode.displaysAsynchronously = false
self.authorNameNode.maximumNumberOfLines = 1
self.dateNode = ASTextNode()
self.dateNode.displaysAsynchronously = false
self.dateNode.maximumNumberOfLines = 1
super.init(frame: frame)
self.addSubnode(self.authorNameNode)
self.addSubnode(self.dateNode)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setMessage(_ message: Message, presentationData: PresentationData, accountPeerId: PeerId) {
let authorNameText = stringForFullAuthorName(message: message, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, accountPeerId: accountPeerId)
let dateText = humanReadableStringForTimestamp(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, timestamp: message.timestamp)
self.authorNameNode.attributedText = NSAttributedString(string: authorNameText, font: titleFont, textColor: .white)
self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: .white)
}
func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) {
let leftInset: CGFloat = 0.0
let rightInset: CGFloat = 0.0
let authorNameSize = self.authorNameNode.measure(CGSize(width: size.width - 44.0 * 2.0 - 8.0 * 2.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude))
let dateSize = self.dateNode.measure(CGSize(width: size.width - 44.0 * 2.0 - 8.0 * 2.0, height: CGFloat.greatestFiniteMagnitude))
if authorNameSize.height.isZero {
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((size.width - dateSize.width) / 2.0), y: floor((size.height - dateSize.height) / 2.0)), size: dateSize)
} else {
let labelsSpacing: CGFloat = 0.0
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)
}
}
func animateLayoutTransition() {
}
}

View File

@ -87,15 +87,17 @@ class ChatImageGalleryItem: GalleryItem {
let presentationData: PresentationData
let message: Message
let location: MessageHistoryEntryLocation?
let displayInfoOnTop: Bool
let performAction: (GalleryControllerInteractionTapAction) -> Void
let openActionOptions: (GalleryControllerInteractionTapAction) -> Void
let present: (ViewController, Any?) -> Void
init(context: AccountContext, presentationData: PresentationData, message: Message, location: MessageHistoryEntryLocation?, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void, present: @escaping (ViewController, Any?) -> Void) {
init(context: AccountContext, presentationData: PresentationData, message: Message, location: MessageHistoryEntryLocation?, displayInfoOnTop: Bool, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.context = context
self.presentationData = presentationData
self.message = message
self.location = location
self.displayInfoOnTop = displayInfoOnTop
self.performAction = performAction
self.openActionOptions = openActionOptions
self.present = present
@ -126,7 +128,10 @@ class ChatImageGalleryItem: GalleryItem {
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(location.index + 1)", "\(location.count)").0))
}
node.setMessage(self.message)
if self.displayInfoOnTop {
node.titleContentView?.setMessage(self.message, presentationData: self.presentationData, accountPeerId: self.context.account.peerId)
}
node.setMessage(self.message, displayInfo: !self.displayInfoOnTop)
return node
}
@ -135,7 +140,10 @@ class ChatImageGalleryItem: GalleryItem {
if let node = node as? ChatImageGalleryItemNode, let location = self.location {
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(location.index + 1)", "\(location.count)").0))
node.setMessage(self.message)
if self.displayInfoOnTop {
node.titleContentView?.setMessage(self.message, presentationData: self.presentationData, accountPeerId: self.context.account.peerId)
}
node.setMessage(self.message, displayInfo: !self.displayInfoOnTop)
}
}
@ -167,10 +175,12 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
private var tilingNode: TilingNode?
fileprivate let _ready = Promise<Void>()
fileprivate let _title = Promise<String>()
fileprivate let _titleView = Promise<UIView?>()
fileprivate let _rightBarButtonItems = Promise<[UIBarButtonItem]?>(nil)
private let statusNodeContainer: HighlightableButtonNode
private let statusNode: RadialStatusNode
private let footerContentNode: ChatItemGalleryFooterContentNode
fileprivate var titleContentView: GalleryTitleView?
private var contextAndMedia: (AccountContext, AnyMediaReference)?
@ -207,6 +217,9 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
self.statusNodeContainer.addTarget(self, action: #selector(self.statusPressed), forControlEvents: .touchUpInside)
self.statusNodeContainer.isUserInteractionEnabled = false
self.titleContentView = GalleryTitleView(frame: CGRect())
self._titleView.set(.single(self.titleContentView))
}
deinit {
@ -227,8 +240,8 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(), size: statusSize))
}
fileprivate func setMessage(_ message: Message) {
self.footerContentNode.setMessage(message)
fileprivate func setMessage(_ message: Message, displayInfo: Bool) {
self.footerContentNode.setMessage(message, displayInfo: displayInfo)
}
fileprivate func setImage(imageReference: ImageMediaReference) {
@ -579,6 +592,10 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
return self._title.get()
}
override func titleView() -> Signal<UIView?, NoError> {
return self._titleView.get()
}
override func rightBarButtonItems() -> Signal<[UIBarButtonItem]?, NoError> {
return self._rightBarButtonItems.get()
}

View File

@ -34,6 +34,7 @@ public class UniversalVideoGalleryItem: GalleryItem {
let contentInfo: UniversalVideoGalleryItemContentInfo?
let caption: NSAttributedString
let credit: NSAttributedString?
let displayInfoOnTop: Bool
let hideControls: Bool
let fromPlayingVideo: Bool
let landscape: Bool
@ -45,7 +46,7 @@ public class UniversalVideoGalleryItem: GalleryItem {
let storeMediaPlaybackState: (MessageId, Double?) -> Void
let present: (ViewController, Any?) -> Void
public init(context: AccountContext, presentationData: PresentationData, content: UniversalVideoContent, originData: GalleryItemOriginData?, indexData: GalleryItemIndexData?, contentInfo: UniversalVideoGalleryItemContentInfo?, caption: NSAttributedString, credit: NSAttributedString? = nil, hideControls: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, configuration: GalleryConfiguration? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void, present: @escaping (ViewController, Any?) -> Void) {
public init(context: AccountContext, presentationData: PresentationData, content: UniversalVideoContent, originData: GalleryItemOriginData?, indexData: GalleryItemIndexData?, contentInfo: UniversalVideoGalleryItemContentInfo?, caption: NSAttributedString, credit: NSAttributedString? = nil, displayInfoOnTop: Bool = false, hideControls: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, configuration: GalleryConfiguration? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.context = context
self.presentationData = presentationData
self.content = content
@ -54,6 +55,7 @@ public class UniversalVideoGalleryItem: GalleryItem {
self.contentInfo = contentInfo
self.caption = caption
self.credit = credit
self.displayInfoOnTop = displayInfoOnTop
self.hideControls = hideControls
self.fromPlayingVideo = fromPlayingVideo
self.landscape = landscape
@ -75,6 +77,10 @@ public class UniversalVideoGalleryItem: GalleryItem {
node.setupItem(self)
if self.displayInfoOnTop, case let .message(message) = self.contentInfo {
node.titleContentView?.setMessage(message, presentationData: self.presentationData, accountPeerId: self.context.account.peerId)
}
return node
}
@ -85,6 +91,10 @@ public class UniversalVideoGalleryItem: GalleryItem {
}
node.setupItem(self)
if self.displayInfoOnTop, case let .message(message) = self.contentInfo {
node.titleContentView?.setMessage(message, presentationData: self.presentationData, accountPeerId: self.context.account.peerId)
}
}
}
@ -249,6 +259,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
fileprivate let _titleView = Promise<UIView?>()
fileprivate let _rightBarButtonItems = Promise<[UIBarButtonItem]?>()
fileprivate var titleContentView: GalleryTitleView?
private let scrubberView: ChatVideoGalleryItemScrubberView
private let footerContentNode: ChatItemGalleryFooterContentNode
private let overlayContentNode: UniversalVideoGalleryItemOverlayNode
@ -309,7 +320,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
self.statusNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 50.0, height: 50.0))
self._title.set(.single(""))
self._titleView.set(.single(nil))
super.init()
@ -412,6 +422,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
strongSelf.pictureInPictureButtonPressed()
return true
}
self.titleContentView = GalleryTitleView(frame: CGRect())
self._titleView.set(.single(self.titleContentView))
}
deinit {
@ -756,7 +769,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if let contentInfo = item.contentInfo {
switch contentInfo {
case let .message(message):
self.footerContentNode.setMessage(message)
self.footerContentNode.setMessage(message, displayInfo: !item.displayInfoOnTop)
case let .webPage(webPage, media, _):
self.footerContentNode.setWebPage(webPage, media: media)
}

View File

@ -88,18 +88,15 @@ public final class HashtagSearchController: TelegramBaseController {
}, openMessageContextMenu: { message, bool, node, rect, gesture in
}, toggleMessagesSelection: { messageId, selected in
}, openUrl: { url, _, _, message in
}, openInstantPage: { message, data in
}, longTap: { action, message in
}, getHiddenMedia: {
return [:]
})
let firstTime = previousEntries == nil
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, searchQuery: "", context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: {
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, searchQuery: "", context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], tagMask: nil, interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: {
}, toggleExpandGlobalResults: {
}, searchPeer: { _ in

View File

@ -14,7 +14,7 @@ import ShareController
import GalleryUI
import AppBundle
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: .white)
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: .white)
private let textFont = Font.regular(16.0)

View File

@ -1120,7 +1120,7 @@
[_playerItemDisposable setDisposable:[[itemSignal deliverOn:[SQueue mainQueue]] startWithNext:^(AVPlayerItem *playerItem)
{
__strong TGMediaPickerGalleryVideoItemView *strongSelf = weakSelf;
if (strongSelf == nil)
if (strongSelf == nil || ![playerItem isKindOfClass:[AVPlayerItem class]])
return;
strongSelf->_player = [AVPlayer playerWithPlayerItem:playerItem];

View File

@ -87,6 +87,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
_textView.autocorrectionType = UITextAutocorrectionTypeNo;
_textView.spellCheckingType = UITextSpellCheckingTypeNo;
_textView.font = [UIFont boldSystemFontOfSize:_baseFontSize];
_textView.typingAttributes = @{NSFontAttributeName: _textView.font};
// _textView.frameWidthInset = floor(_baseFontSize * 0.03);
[self setSwatch:entity.swatch];
@ -175,6 +176,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
{
_font = font;
_textView.font = [UIFont boldSystemFontOfSize:_baseFontSize];
_textView.typingAttributes = @{NSFontAttributeName: _textView.font};
// _textView.frameWidthInset = floor(_baseFontSize * 0.03);
[self sizeToFit];
@ -480,6 +482,9 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
@implementation TGPhotoTextView
{
UIFont *_font;
}
- (instancetype)initWithFrame:(CGRect)frame
{
@ -554,10 +559,29 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
- (void)setFont:(UIFont *)font {
[super setFont:font];
_font = font;
self.layoutManager.textContainers.firstObject.lineFragmentPadding = floor(font.pointSize * 0.3);
}
- (void)insertText:(NSString *)text {
[self fixTypingAttributes];
[super insertText:text];
[self fixTypingAttributes];
}
- (void)paste:(id)sender {
[self fixTypingAttributes];
[super paste:sender];
[self fixTypingAttributes];
}
- (void)fixTypingAttributes {
if (_font != nil) {
self.typingAttributes = @{NSFontAttributeName: _font};
}
}
@end
@ -802,8 +826,6 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
return [_impl attributesAtIndex:location effectiveRange:range];
}
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str {
[self beginEditing];
[_impl replaceCharactersInRange:range withString:str];

View File

@ -89,36 +89,6 @@ private func extensionImage(fileExtension: String?) -> UIImage? {
}
private let extensionFont = Font.with(size: 15.0, design: .round, traits: [.bold])
func fullAuthorString(for item: ListMessageItem) -> String {
var authorString = ""
if let author = item.message.author, [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(item.message.id.peerId.namespace) {
var authorName = ""
if author.id == item.context.account.peerId {
authorName = item.presentationData.strings.DialogList_You
} else {
authorName = author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
}
if let peer = item.message.peers[item.message.id.peerId], author.id != peer.id {
authorString = "\(authorName)\(peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder))"
} else {
authorString = authorName
}
} else if let peer = item.message.peers[item.message.id.peerId] {
if item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
authorString = peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
} else {
if item.message.id.peerId == item.context.account.peerId {
authorString = item.presentationData.strings.DialogList_SavedMessages
} else if item.message.flags.contains(.Incoming) {
authorString = peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
} else {
authorString = "\(item.presentationData.strings.DialogList_You)\(peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder))"
}
}
}
return authorString
}
private struct FetchControls {
let fetch: () -> Void
let cancel: () -> Void
@ -418,7 +388,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
}
if item.isGlobalSearchResult {
let authorString = fullAuthorString(for: item)
let authorString = stringForFullAuthorName(message: item.message, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, accountPeerId: item.context.account.peerId)
if descriptionString.isEmpty {
descriptionString = authorString
} else {
@ -458,7 +428,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
}
if item.isGlobalSearchResult {
authorName = fullAuthorString(for: item)
authorName = stringForFullAuthorName(message: item.message, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, accountPeerId: item.context.account.peerId)
}
titleText = NSAttributedString(string: authorName, font: audioTitleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor)
@ -511,7 +481,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
}
if item.isGlobalSearchResult {
let authorString = fullAuthorString(for: item)
let authorString = stringForFullAuthorName(message: item.message, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, accountPeerId: item.context.account.peerId)
if descriptionString.isEmpty {
descriptionString = authorString
} else {

View File

@ -11,9 +11,9 @@ import AccountContext
import TelegramUIPreferences
public final class ListMessageItemInteraction {
let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool
let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void
let toggleMessagesSelection: ([MessageId], Bool) -> Void
public let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool
public let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void
public let toggleMessagesSelection: ([MessageId], Bool) -> Void
let openUrl: (String, Bool, Bool?, Message?) -> Void
let openInstantPage: (Message, ChatMessageItemAssociatedData?) -> Void
let longTap: (ChatControllerInteractionLongTapAction, Message?) -> Void

View File

@ -479,7 +479,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
var authorString = ""
if item.isGlobalSearchResult {
authorString = fullAuthorString(for: item)
authorString = stringForFullAuthorName(message: item.message, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, accountPeerId: item.context.account.peerId)
}
let authorText = NSAttributedString(string: authorString, font: authorFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)

View File

@ -48,7 +48,7 @@ public enum LocationActionListItemIcon: Equatable {
}
private func generateLocationIcon(theme: PresentationTheme) -> UIImage {
return generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.chat.inputPanel.actionControlFillColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
@ -64,7 +64,7 @@ private func generateLocationIcon(theme: PresentationTheme) -> UIImage {
}
private func generateLiveLocationIcon(theme: PresentationTheme) -> UIImage {
return generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor(rgb: 0x6cc139).cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))

View File

@ -10,7 +10,7 @@ private let panelSize = CGSize(width: 46.0, height: 90.0)
private func generateBackgroundImage(theme: PresentationTheme) -> UIImage? {
let cornerRadius: CGFloat = 9.0
return generateImage(CGSize(width: (cornerRadius + panelInset) * 2.0, height: (cornerRadius + panelInset) * 2.0), contextGenerator: { size, context in
return generateImage(CGSize(width: (cornerRadius + panelInset) * 2.0, height: (cornerRadius + panelInset) * 2.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setShadow(offset: CGSize(), blur: 10.0, color: UIColor(rgb: 0x000000, alpha: 0.2).cgColor)
@ -22,7 +22,7 @@ private func generateBackgroundImage(theme: PresentationTheme) -> UIImage? {
}
private func generateShadowImage(theme: PresentationTheme, highlighted: Bool) -> UIImage? {
return generateImage(CGSize(width: 26.0, height: 14.0), contextGenerator: { size, context in
return generateImage(CGSize(width: 26.0, height: 14.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setShadow(offset: CGSize(), blur: 10.0, color: UIColor(rgb: 0x000000, alpha: 0.2).cgColor)

View File

@ -15,7 +15,7 @@ import GalleryUI
import AppBundle
private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: .white)
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: .white)
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: .white)
private let nameFont = Font.medium(15.0)
private let dateFont = Font.regular(14.0)

View File

@ -134,7 +134,7 @@ public final class SearchDisplayController {
insertSubnode(self.backgroundNode, false)
self.backgroundNode.addSubnode(self.contentNode)
self.contentNode.frame = CGRect(origin: CGPoint(), size: layout.size)
self.contentNode.frame = CGRect(origin: CGPoint(x: 20.0, y: 20.0), size: layout.size)
self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: layout.safeInsets, statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), navigationBarHeight: navigationBarHeight, transition: .immediate)
@ -149,7 +149,7 @@ public final class SearchDisplayController {
let contentNodePosition = self.contentNode.layer.position
// self.contentNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - contentNavigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
self.backgroundNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
self.searchBar.placeholderString = placeholder.placeholderString
}

View File

@ -38,8 +38,8 @@ final class ThemeGridSelectionPanelNode: ASDisplayNode {
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
super.init()
@ -56,8 +56,8 @@ final class ThemeGridSelectionPanelNode: ASDisplayNode {
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
}
}

View File

@ -139,7 +139,7 @@ public final class ThemePreviewController: ViewController {
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
if !isPreview {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: self.previewTheme.rootController.navigationBar.accentTextColor), style: .plain, target: self, action: #selector(self.actionPressed))
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: self.previewTheme.rootController.navigationBar.accentTextColor), style: .plain, target: self, action: #selector(self.actionPressed))
}
self.disposable = (combineLatest(self.theme.get(), self.presentationTheme.get())

View File

@ -227,7 +227,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
let contentSize: CGSize
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let defaultAction = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.rootController.navigationBar.accentTextColor), style: .plain, target: self, action: #selector(self.actionPressed))
let defaultAction = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.rootController.navigationBar.accentTextColor), style: .plain, target: self, action: #selector(self.actionPressed))
let progressAction = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: presentationData.theme.rootController.navigationBar.accentTextColor))
var isBlurrable = true

View File

@ -17,7 +17,7 @@ final class ShareControllerRecentPeersGridItem: GridItem {
let controllerInteraction: ShareControllerInteraction
let section: GridSection? = nil
let fillsRowWithHeight: (CGFloat, Bool)? = (130.0, true)
let fillsRowWithHeight: (CGFloat, Bool)? = (102.0, true)
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ShareControllerInteraction) {
self.context = context

View File

@ -238,7 +238,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
} else {
let lowerBound = state?.main.messages.last.flatMap({ $0.index })
let signal: Signal<Api.messages.Messages, MTRpcError>
if peer.id.namespace == Namespaces.Peer.CloudChannel && tags == nil && minDate == nil && maxDate == nil {
if peer.id.namespace == Namespaces.Peer.CloudChannel && query.isEmpty && tags == nil && minDate == nil && maxDate == nil {
signal = account.network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: lowerBound?.id.id ?? 0, offsetDate: 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0))
} else {
signal = account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: query, fromId: fromInputUser, topMsgId: topMsgId?.id, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetId: lowerBound?.id.id ?? 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0))

View File

@ -44,7 +44,10 @@ public struct PresentationResourcesRootController {
}
public static func navigationShareIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationShareIcon.rawValue, generateShareButtonImage)
return theme.image(PresentationResourceKey.navigationShareIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.rootController.navigationBar.accentTextColor)
})
// return theme.image(PresentationResourceKey.navigationShareIcon.rawValue, generateShareButtonImage)
}
public static func navigationCallIcon(_ theme: PresentationTheme) -> UIImage? {

View File

@ -0,0 +1,35 @@
import Foundation
import Postbox
import TelegramPresentationData
import TelegramUIPreferences
import SyncCore
public func stringForFullAuthorName(message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> String {
var authorString = ""
if let author = message.author, [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(message.id.peerId.namespace) {
var authorName = ""
if author.id == accountPeerId {
authorName = strings.DialogList_You
} else {
authorName = author.compactDisplayTitle
}
if let peer = message.peers[message.id.peerId], author.id != peer.id {
authorString = "\(authorName)\(peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder))"
} else {
authorString = authorName
}
} else if let peer = message.peers[message.id.peerId] {
if message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
authorString = peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
} else {
if message.id.peerId == accountPeerId {
authorString = strings.DialogList_SavedMessages
} else if message.flags.contains(.Incoming) {
authorString = peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
} else {
authorString = "\(strings.DialogList_You)\(peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder))"
}
}
}
return authorString
}

View File

@ -318,7 +318,7 @@ final class PeerInfoPaneTabsContainerNode: ASDisplayNode {
}
var selectedFrame: CGRect?
if let selectedPane = selectedPane, let currentIndex = paneList.index(where: { $0.key == selectedPane }) {
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]
@ -425,7 +425,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
private let coveringBackgroundNode: ASDisplayNode
private let separatorNode: ASDisplayNode
private let tabsContainerNode: PeerInfoPaneTabsContainerNode
private let tapsSeparatorNode: ASDisplayNode
private let tabsSeparatorNode: ASDisplayNode
let isReady = Promise<Bool>()
var didSetIsReady = false
@ -471,15 +471,15 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
self.tabsContainerNode = PeerInfoPaneTabsContainerNode()
self.tapsSeparatorNode = ASDisplayNode()
self.tapsSeparatorNode.isLayerBacked = true
self.tabsSeparatorNode = ASDisplayNode()
self.tabsSeparatorNode.isLayerBacked = true
super.init()
self.addSubnode(self.separatorNode)
self.addSubnode(self.coveringBackgroundNode)
self.addSubnode(self.tabsContainerNode)
self.addSubnode(self.tapsSeparatorNode)
self.addSubnode(self.tabsSeparatorNode)
self.tabsContainerNode.requestSelectPane = { [weak self] key in
guard let strongSelf = self else {
@ -488,7 +488,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
if strongSelf.currentPaneKey == key {
if let requestExpandTabs = strongSelf.requestExpandTabs, requestExpandTabs() {
} else {
strongSelf.currentPane?.node.scrollToTop()
let _ = strongSelf.currentPane?.node.scrollToTop()
}
return
}
@ -563,7 +563,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
cancelContextGestures(view: self.view)
case .changed:
if let (size, sideInset, bottomInset, visibleHeight, expansionFraction, presentationData, data) = self.currentParams, let availablePanes = data?.availablePanes, availablePanes.count > 1, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.index(of: currentPaneKey) {
if let (size, sideInset, bottomInset, visibleHeight, expansionFraction, presentationData, data) = 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)
var transitionFraction = translation.x / size.width
if currentIndex <= 0 {
@ -576,7 +576,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .immediate)
}
case .cancelled, .ended:
if let (size, sideInset, bottomInset, visibleHeight, expansionFraction, presentationData, data) = self.currentParams, let availablePanes = data?.availablePanes, availablePanes.count > 1, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.index(of: currentPaneKey) {
if let (size, sideInset, bottomInset, visibleHeight, expansionFraction, presentationData, data) = 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)
var directionIsToRight: Bool?
@ -648,7 +648,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
if let currentPaneKey = self.currentPaneKey, !availablePanes.contains(currentPaneKey) {
var nextCandidatePaneKey: PeerInfoPaneKey?
if let index = previousAvailablePanes.index(of: currentPaneKey), index != 0 {
if let index = previousAvailablePanes.firstIndex(of: currentPaneKey), index != 0 {
for i in (0 ... index - 1).reversed() {
if availablePanes.contains(previousAvailablePanes[i]) {
nextCandidatePaneKey = previousAvailablePanes[i]
@ -671,7 +671,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
let currentIndex: Int?
if let currentPaneKey = self.currentPaneKey {
currentIndex = availablePanes.index(of: currentPaneKey)
currentIndex = availablePanes.firstIndex(of: currentPaneKey)
} else {
currentIndex = nil
}
@ -683,14 +683,14 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor
self.coveringBackgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor
self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
self.tapsSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
self.tabsSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
let tabsHeight: CGFloat = 48.0
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)))
transition.updateFrame(node: self.coveringBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: tabsHeight + UIScreenPixel)))
transition.updateFrame(node: self.tapsSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: tabsHeight - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)))
transition.updateFrame(node: self.tabsSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: tabsHeight - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)))
let paneFrame = CGRect(origin: CGPoint(x: 0.0, y: tabsHeight), size: CGSize(width: size.width, height: size.height - tabsHeight))
@ -706,7 +706,6 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
}
for index in visiblePaneIndices {
let indexOffset = CGFloat(index - currentIndex)
let key = availablePanes[index]
if self.currentPanes[key] == nil && self.pendingPanes[key] == nil {
requiredPendingKeys.append(key)
@ -781,8 +780,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
self.pendingSwitchToPaneKey = nil
previousPaneKey = self.currentPaneKey
self.currentPaneKey = pendingSwitchToPaneKey
updatedCurrentIndex = availablePanes.index(of: pendingSwitchToPaneKey)
if let previousPaneKey = previousPaneKey, let previousIndex = availablePanes.index(of: previousPaneKey), let updatedCurrentIndex = updatedCurrentIndex {
updatedCurrentIndex = availablePanes.firstIndex(of: pendingSwitchToPaneKey)
if let previousPaneKey = previousPaneKey, let previousIndex = availablePanes.firstIndex(of: previousPaneKey), let updatedCurrentIndex = updatedCurrentIndex {
if updatedCurrentIndex < previousIndex {
paneSwitchAnimationOffset = -size.width
} else {
@ -794,7 +793,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
}
for (key, pane) in self.currentPanes {
if let index = availablePanes.index(of: key), let updatedCurrentIndex = updatedCurrentIndex {
if let index = availablePanes.firstIndex(of: key), let updatedCurrentIndex = updatedCurrentIndex {
var paneWasAdded = false
if pane.node.supernode == nil {
self.addSubnode(pane.node)
@ -811,7 +810,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
}
pane.isAnimatingOut = false
if let (size, sideInset, bottomInset, visibleHeight, expansionFraction, presentationData, data) = strongSelf.currentParams {
if let availablePanes = data?.availablePanes, let currentPaneKey = strongSelf.currentPaneKey, let currentIndex = availablePanes.index(of: currentPaneKey), let paneIndex = availablePanes.index(of: key), abs(paneIndex - currentIndex) <= 1 {
if let availablePanes = data?.availablePanes, let currentPaneKey = strongSelf.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey), let paneIndex = availablePanes.firstIndex(of: key), abs(paneIndex - currentIndex) <= 1 {
} else {
if let pane = strongSelf.currentPanes.removeValue(forKey: key) {
//print("remove \(key)")