mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Download list improvements
This commit is contained in:
parent
b1d78aedb2
commit
d771cdaeac
BIN
Telegram/Telegram-iOS/Resources/ClearDownloadList.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/ClearDownloadList.tgs
Normal file
Binary file not shown.
@ -7328,3 +7328,29 @@ Sorry for the inconvenience.";
|
||||
"Attachment.DeselectedItems_0" = "%@ items deselected";
|
||||
|
||||
"PrivacyPhoneNumberSettings.CustomPublicLink" = "Users who have your number saved in their contacts will also see it on Telegram.\n\nThis public link opens a chat with you:\n[https://t.me/%@]()";
|
||||
|
||||
"DownloadList.DownloadingHeader" = "Downloading";
|
||||
"DownloadList.DownloadedHeader" = "Recently Downloaded";
|
||||
"DownloadList.PauseAll" = "Pause All";
|
||||
"DownloadList.ResumeAll" = "Resume All";
|
||||
"DownloadList.Clear" = "Clear";
|
||||
"DownloadList.OptionManageDeviceStorage" = "Manage Device Storage";
|
||||
"DownloadList.ClearDownloadList" = "Clear Download List";
|
||||
"DownloadList.DeleteFromCache" = "Delete from Cache";
|
||||
"DownloadList.RaisePriority" = "Raise Priority";
|
||||
"DownloadList.CancelDownloading" = "Cancel Downloading";
|
||||
|
||||
"DownloadList.RemoveFileAlertTitle_1" = "Remove Document?";
|
||||
"DownloadList.RemoveFileAlertTitle_any" = "Remove %@ Documents?";
|
||||
"DownloadList.RemoveFileAlertText_1" = "Are you sure you want to remove this\ndocument from Downloads?\nIt will be deleted from your disk, but\nwill remain accessible in the cloud.";
|
||||
"DownloadList.RemoveFileAlertText_any" = "Are you sure you want to remove these\n%@ documents from Downloads?\nThey will be deleted from your disk, but\nwill remain accessible in the cloud.";
|
||||
"DownloadList.RemoveFileAlertRemove" = "Remove";
|
||||
|
||||
"DownloadList.ClearAlertTitle" = "Downloaded Files";
|
||||
"DownloadList.ClearAlertText" = "Telegram allows to store all received and sent\ndocuments in the cloud and save storage\nspace on your device.";
|
||||
|
||||
"ChatList.Search.FilterDownloads" = "Downloads";
|
||||
|
||||
"LiveStream.NoViewers" = "No viewers";
|
||||
"LiveStream.ViewerCount_1" = "1 viewer";
|
||||
"LiveStream.ViewerCount_any" = "%@ viewers";
|
||||
|
@ -77,11 +77,9 @@ public enum ChatListSearchItemHeaderType {
|
||||
case .subscribers:
|
||||
return strings.Channel_ChannelSubscribersHeader
|
||||
case .downloading:
|
||||
//TODO:localize
|
||||
return "Downloading"
|
||||
return strings.DownloadList_DownloadingHeader
|
||||
case .recentDownloads:
|
||||
//TODO:localize
|
||||
return "Recently Downloaded"
|
||||
return strings.DownloadList_DownloadedHeader
|
||||
}
|
||||
}
|
||||
|
||||
@ -261,6 +259,10 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
|
||||
self.sectionHeaderNode.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset)
|
||||
}
|
||||
|
||||
override public func animateAdded(duration: Double) {
|
||||
self.layer.animateAlpha(from: 0.0, to: self.alpha, duration: 0.2)
|
||||
}
|
||||
|
||||
override public func animateRemoved(duration: Double) {
|
||||
self.alpha = 0.0
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: true)
|
||||
|
@ -66,6 +66,7 @@ swift_library(
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
|
||||
"//submodules/Components/ProgressIndicatorComponent:ProgressIndicatorComponent",
|
||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -155,6 +155,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
private let tabContainerNode: ChatListFilterTabContainerNode
|
||||
private var tabContainerData: ([ChatListFilterTabEntry], Bool)?
|
||||
|
||||
private var hasDownloads: Bool = false
|
||||
private var activeDownloadsDisposable: Disposable?
|
||||
private var clearUnseenDownloadsTimer: SwiftSignalKit.Timer?
|
||||
|
||||
@ -476,8 +477,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
self.navigationBar?.setContentNode(self.searchContentNode, animated: false)
|
||||
|
||||
enum State: Equatable {
|
||||
case empty
|
||||
case downloading(Double)
|
||||
case empty(hasDownloads: Bool)
|
||||
case downloading(progress: Double)
|
||||
case hasUnseen
|
||||
}
|
||||
|
||||
@ -626,14 +627,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
} else {
|
||||
totalProgress = totalProgressInBytes / totalBytes
|
||||
}
|
||||
return .downloading(totalProgress)
|
||||
return .downloading(progress: totalProgress)
|
||||
} else {
|
||||
for item in recentDownloadItems {
|
||||
if !item.isSeen {
|
||||
return .hasUnseen
|
||||
}
|
||||
}
|
||||
return .empty
|
||||
return .empty(hasDownloads: !recentDownloadItems.isEmpty)
|
||||
}
|
||||
}
|
||||
|> mapToSignal { value -> Signal<State, NoError> in
|
||||
@ -642,11 +643,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|> distinctUntilChanged
|
||||
|> deliverOnMainQueue)
|
||||
|
||||
/*if !"".isEmpty {
|
||||
stateSignal = Signal<State, NoError>.single(.downloading)
|
||||
|> then(Signal<State, NoError>.single(.hasUnseen) |> delay(3.0, queue: .mainQueue()))
|
||||
}*/
|
||||
|
||||
self.activeDownloadsDisposable = stateSignal.start(next: { [weak self] state in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -655,6 +651,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
let progressValue: Double?
|
||||
switch state {
|
||||
case let .downloading(progress):
|
||||
strongSelf.hasDownloads = true
|
||||
|
||||
animation = LottieAnimationComponent.Animation(
|
||||
name: "anim_search_downloading",
|
||||
colors: [
|
||||
@ -669,6 +667,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
strongSelf.clearUnseenDownloadsTimer?.invalidate()
|
||||
strongSelf.clearUnseenDownloadsTimer = nil
|
||||
case .hasUnseen:
|
||||
strongSelf.hasDownloads = true
|
||||
|
||||
animation = LottieAnimationComponent.Animation(
|
||||
name: "anim_search_downloaded",
|
||||
colors: [
|
||||
@ -701,7 +701,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}, queue: .mainQueue())
|
||||
strongSelf.clearUnseenDownloadsTimer?.start()
|
||||
}
|
||||
case .empty:
|
||||
case let .empty(hasDownloadsValue):
|
||||
strongSelf.hasDownloads = hasDownloadsValue
|
||||
|
||||
animation = nil
|
||||
progressValue = nil
|
||||
|
||||
@ -2084,7 +2086,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
|
||||
if let searchContentNode = strongSelf.searchContentNode {
|
||||
if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, displaySearchFilters: displaySearchFilters, initialFilter: filter, navigationController: strongSelf.navigationController as? NavigationController) {
|
||||
if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, displaySearchFilters: displaySearchFilters, hasDownloads: strongSelf.hasDownloads, initialFilter: filter, navigationController: strongSelf.navigationController as? NavigationController) {
|
||||
let (filterContainerNode, activate) = filterContainerNodeAndActivate
|
||||
if displaySearchFilters {
|
||||
strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: false)
|
||||
@ -2102,6 +2104,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
if !tabsIsEmpty {
|
||||
Queue.mainQueue().after(0.01) {
|
||||
filterContainerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 38.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
filterContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
|
||||
strongSelf.tabContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -64.0), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
|
@ -1193,14 +1193,14 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
func activateSearch(placeholderNode: SearchBarPlaceholderNode, displaySearchFilters: Bool, initialFilter: ChatListSearchFilter, navigationController: NavigationController?) -> (ASDisplayNode, (Bool) -> Void)? {
|
||||
func activateSearch(placeholderNode: SearchBarPlaceholderNode, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter, navigationController: NavigationController?) -> (ASDisplayNode, (Bool) -> Void)? {
|
||||
guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let filter: ChatListNodePeersFilter = []
|
||||
|
||||
let contentNode = ChatListSearchContainerNode(context: self.context, filter: filter, groupId: self.groupId, displaySearchFilters: displaySearchFilters, initialFilter: initialFilter, openPeer: { [weak self] peer, _, dismissSearch in
|
||||
let contentNode = ChatListSearchContainerNode(context: self.context, filter: filter, groupId: self.groupId, displaySearchFilters: displaySearchFilters, hasDownloads: hasDownloads, initialFilter: initialFilter, openPeer: { [weak self] peer, _, dismissSearch in
|
||||
self?.requestOpenPeerFromSearch?(peer, dismissSearch)
|
||||
}, openDisabledPeer: { _ in
|
||||
}, openRecentPeerOptions: { [weak self] peer in
|
||||
|
@ -30,6 +30,7 @@ import ShareController
|
||||
import UndoUI
|
||||
import TextFormat
|
||||
import Postbox
|
||||
import TelegramAnimatedStickerNode
|
||||
|
||||
private enum ChatListTokenId: Int32 {
|
||||
case archive
|
||||
@ -83,6 +84,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
private let peersFilter: ChatListNodePeersFilter
|
||||
private let groupId: EngineChatList.Group
|
||||
private let displaySearchFilters: Bool
|
||||
private let hasDownloads: Bool
|
||||
private var interaction: ChatListSearchInteraction?
|
||||
private let openMessage: (EnginePeer, EngineMessage.Id, Bool) -> Void
|
||||
private let navigationController: NavigationController?
|
||||
@ -127,11 +129,12 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter, groupId: EngineChatList.Group, displaySearchFilters: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (EnginePeer, EnginePeer?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer) -> Void, openRecentPeerOptions: @escaping (EnginePeer) -> Void, openMessage originalOpenMessage: @escaping (EnginePeer, EngineMessage.Id, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) {
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter, groupId: EngineChatList.Group, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (EnginePeer, EnginePeer?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer) -> Void, openRecentPeerOptions: @escaping (EnginePeer) -> Void, openMessage originalOpenMessage: @escaping (EnginePeer, EngineMessage.Id, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) {
|
||||
self.context = context
|
||||
self.peersFilter = filter
|
||||
self.groupId = groupId
|
||||
self.displaySearchFilters = displaySearchFilters
|
||||
self.hasDownloads = hasDownloads
|
||||
self.navigationController = navigationController
|
||||
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
@ -265,7 +268,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
if let suggestedFilters = strongSelf.suggestedFilters, !suggestedFilters.isEmpty {
|
||||
filters = suggestedFilters
|
||||
} else {
|
||||
filters = [.chats, .media, .downloads, .links, .files, .music, .voice]
|
||||
filters = defaultAvailableSearchPanes(hasDownloads: strongSelf.hasDownloads).map(\.filter)
|
||||
}
|
||||
strongSelf.filterContainerNode.update(size: CGSize(width: layout.size.width - 40.0, height: 38.0), sideInset: layout.safeInsets.left - 20.0, filters: filters.map { .filter($0) }, selectedFilter: strongSelf.selectedFilter?.id, transitionFraction: strongSelf.transitionFraction, presentationData: strongSelf.presentationData, transition: transition)
|
||||
}
|
||||
@ -736,7 +739,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
|
||||
let availablePanes: [ChatListSearchPaneKey]
|
||||
if self.displaySearchFilters {
|
||||
availablePanes = defaultAvailableSearchPanes
|
||||
availablePanes = defaultAvailableSearchPanes(hasDownloads: self.hasDownloads)
|
||||
} else {
|
||||
availablePanes = [.chats]
|
||||
}
|
||||
@ -817,9 +820,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
if isCachedValue {
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Delete from Cache", textColor: .primary, icon: { _ in
|
||||
return nil
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DownloadList_DeleteFromCache, textColor: .primary, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
guard let strongSelf = self, let downloadResource = downloadResource else {
|
||||
f(.default)
|
||||
@ -832,9 +834,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
})))
|
||||
} else {
|
||||
if let downloadResource = downloadResource, !downloadResource.isFirstInList {
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Raise Priority", textColor: .primary, icon: { _ in
|
||||
return nil
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DownloadList_RaisePriority, textColor: .primary, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Raise"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
guard let strongSelf = self else {
|
||||
f(.default)
|
||||
@ -849,9 +850,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
})))
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Cancel Downloading", textColor: .primary, icon: { _ in
|
||||
return nil
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DownloadList_CancelDownloading, textColor: .primary, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
guard let strongSelf = self, let downloadResource = downloadResource else {
|
||||
f(.default)
|
||||
@ -1097,20 +1097,13 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
let title: String
|
||||
let text: String
|
||||
|
||||
//TODO:localize
|
||||
if messageIds.count == 1 {
|
||||
title = "Remove Document?"
|
||||
text = "Are you sure you want to remove this\ndocument from Downloads?\nIt will be deleted from your disk, but\nwill remain accessible in the cloud.";
|
||||
} else {
|
||||
title = "Remove \(messages.count) Documents?"
|
||||
text = "Do you want to remove these\n\(messages.count) documents from Downloads?\nThey will be deleted from your disk,\nbut will remain accessible\nin the cloud."
|
||||
}
|
||||
title = strongSelf.presentationData.strings.DownloadList_RemoveFileAlertTitle(Int32(messages.count))
|
||||
text = strongSelf.presentationData.strings.DownloadList_RemoveFileAlertText(Int32(messages.count))
|
||||
|
||||
strongSelf.present?(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [
|
||||
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
|
||||
}),
|
||||
//TODO:localize
|
||||
TextAlertAction(type: .defaultAction, title: "Remove", action: {
|
||||
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.DownloadList_RemoveFileAlertRemove, action: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1449,3 +1442,117 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
|
||||
self.controller.didAppearInContextPreview()
|
||||
}
|
||||
}
|
||||
|
||||
final class ActionSheetAnimationAndTextItem: ActionSheetItem {
|
||||
public let title: String
|
||||
public let text: String
|
||||
|
||||
public init(title: String, text: String) {
|
||||
self.title = title
|
||||
self.text = text
|
||||
}
|
||||
|
||||
public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
||||
let node = ActionSheetAnimationAndTextItemNode(theme: theme)
|
||||
node.setItem(self)
|
||||
return node
|
||||
}
|
||||
|
||||
public func updateNode(_ node: ActionSheetItemNode) {
|
||||
guard let node = node as? ActionSheetAnimationAndTextItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
node.setItem(self)
|
||||
node.requestLayoutUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
final class ActionSheetAnimationAndTextItemNode: ActionSheetItemNode {
|
||||
private let defaultFont: UIFont
|
||||
|
||||
private let theme: ActionSheetControllerTheme
|
||||
|
||||
private var item: ActionSheetAnimationAndTextItem?
|
||||
|
||||
private let animationNode: AnimatedStickerNode
|
||||
private let textLabel: ImmediateTextNode
|
||||
private let titleLabel: ImmediateTextNode
|
||||
|
||||
private let accessibilityArea: AccessibilityAreaNode
|
||||
|
||||
override public init(theme: ActionSheetControllerTheme) {
|
||||
self.theme = theme
|
||||
self.defaultFont = Font.regular(floor(theme.baseFontSize * 13.0 / 17.0))
|
||||
|
||||
self.animationNode = AnimatedStickerNode()
|
||||
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "ClearDownloadList"), width: 256, height: 256, playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
|
||||
self.animationNode.visibility = true
|
||||
|
||||
self.titleLabel = ImmediateTextNode()
|
||||
self.titleLabel.isUserInteractionEnabled = false
|
||||
self.titleLabel.maximumNumberOfLines = 0
|
||||
self.titleLabel.displaysAsynchronously = false
|
||||
self.titleLabel.truncationType = .end
|
||||
self.titleLabel.isAccessibilityElement = false
|
||||
|
||||
self.textLabel = ImmediateTextNode()
|
||||
self.textLabel.isUserInteractionEnabled = false
|
||||
self.textLabel.maximumNumberOfLines = 0
|
||||
self.textLabel.displaysAsynchronously = false
|
||||
self.textLabel.truncationType = .end
|
||||
self.textLabel.isAccessibilityElement = false
|
||||
|
||||
self.accessibilityArea = AccessibilityAreaNode()
|
||||
self.accessibilityArea.accessibilityTraits = .staticText
|
||||
|
||||
super.init(theme: theme)
|
||||
|
||||
self.addSubnode(self.animationNode)
|
||||
|
||||
self.titleLabel.isUserInteractionEnabled = false
|
||||
self.textLabel.isUserInteractionEnabled = false
|
||||
|
||||
self.addSubnode(self.titleLabel)
|
||||
self.addSubnode(self.textLabel)
|
||||
|
||||
self.addSubnode(self.accessibilityArea)
|
||||
}
|
||||
|
||||
func setItem(_ item: ActionSheetAnimationAndTextItem) {
|
||||
self.item = item
|
||||
|
||||
let defaultTitleFont = Font.semibold(floor(theme.baseFontSize * 17.0 / 17.0))
|
||||
let defaultFont = Font.regular(floor(theme.baseFontSize * 16.0 / 17.0))
|
||||
|
||||
self.titleLabel.attributedText = NSAttributedString(string: item.title, font: defaultTitleFont, textColor: self.theme.primaryTextColor, paragraphAlignment: .center)
|
||||
self.textLabel.attributedText = NSAttributedString(string: item.text, font: defaultFont, textColor: self.theme.secondaryTextColor, paragraphAlignment: .center)
|
||||
self.accessibilityArea.accessibilityLabel = item.title
|
||||
}
|
||||
|
||||
public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let topInset: CGFloat = 20.0
|
||||
let textSpacing: CGFloat = 10.0
|
||||
let bottomInset: CGFloat = 16.0
|
||||
let imageInset: CGFloat = 6.0
|
||||
|
||||
let titleSize = self.titleLabel.updateLayout(CGSize(width: max(1.0, constrainedSize.width - 20.0), height: constrainedSize.height))
|
||||
let textSize = self.textLabel.updateLayout(CGSize(width: max(1.0, constrainedSize.width - 20.0), height: constrainedSize.height))
|
||||
var size = CGSize(width: constrainedSize.width, height: max(57.0, titleSize.height + textSpacing + textSize.height + bottomInset))
|
||||
|
||||
let imageSize = CGSize(width: 140.0, height: 140.0)
|
||||
size.height += topInset + 160.0 + imageInset
|
||||
|
||||
self.animationNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: topInset), size: imageSize)
|
||||
self.animationNode.updateLayout(size: imageSize)
|
||||
|
||||
self.titleLabel.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - textSize.height - textSpacing - bottomInset), size: titleSize)
|
||||
self.textLabel.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: size.height - textSize.height - bottomInset), size: textSize)
|
||||
|
||||
self.accessibilityArea.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
self.updateInternalLayout(size, constrainedSize: constrainedSize)
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
@ -85,8 +85,7 @@ private final class ItemNode: ASDisplayNode {
|
||||
title = presentationData.strings.ChatList_Search_FilterMedia
|
||||
icon = nil
|
||||
case .downloads:
|
||||
//TODO:localize
|
||||
title = "Downloads"
|
||||
title = presentationData.strings.ChatList_Search_FilterDownloads
|
||||
icon = nil
|
||||
case .links:
|
||||
title = presentationData.strings.ChatList_Search_FilterLinks
|
||||
|
@ -552,19 +552,17 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
let header: ChatListSearchItemHeader
|
||||
switch orderingKey {
|
||||
case .downloading:
|
||||
//TODO:localize
|
||||
if allPaused {
|
||||
header = ChatListSearchItemHeader(type: .downloading, theme: presentationData.theme, strings: presentationData.strings, actionTitle: "Resume All", action: {
|
||||
header = ChatListSearchItemHeader(type: .downloading, theme: presentationData.theme, strings: presentationData.strings, actionTitle: presentationData.strings.DownloadList_ResumeAll, action: {
|
||||
toggleAllPaused()
|
||||
})
|
||||
} else {
|
||||
header = ChatListSearchItemHeader(type: .downloading, theme: presentationData.theme, strings: presentationData.strings, actionTitle: "Pause All", action: {
|
||||
header = ChatListSearchItemHeader(type: .downloading, theme: presentationData.theme, strings: presentationData.strings, actionTitle: presentationData.strings.DownloadList_PauseAll, action: {
|
||||
toggleAllPaused()
|
||||
})
|
||||
}
|
||||
case .downloaded:
|
||||
//TODO:localize
|
||||
header = ChatListSearchItemHeader(type: .recentDownloads, theme: presentationData.theme, strings: presentationData.strings, actionTitle: "Clear", action: {
|
||||
header = ChatListSearchItemHeader(type: .recentDownloads, theme: presentationData.theme, strings: presentationData.strings, actionTitle: presentationData.strings.DownloadList_Clear, action: {
|
||||
openClearRecentlyDownloaded()
|
||||
})
|
||||
case .index:
|
||||
@ -949,6 +947,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|> map { items -> (inProgressItems: [DownloadItem], doneItems: [RenderedRecentDownloadItem]) in
|
||||
return (items.compactMap { $0 }, recentDownloadItems)
|
||||
}
|
||||
|> delay(0.1, queue: .mainQueue())
|
||||
}
|
||||
} else {
|
||||
downloadItems = .single(([], []))
|
||||
@ -1076,7 +1075,6 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}
|
||||
}
|
||||
|> map { notificationSettings, unreadCounts, peers -> (peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)]) in
|
||||
|
||||
var unread: [EnginePeer.Id: (Int32, Bool)] = [:]
|
||||
for peer in peers {
|
||||
var isMuted: Bool = false
|
||||
@ -1626,7 +1624,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|
||||
let animated = (previousSelectedMessageIds == nil) != (strongSelf.selectedMessages == nil)
|
||||
let firstTime = previousEntries == nil
|
||||
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags?.0 != nil, isEmpty: !isSearching && (entriesAndFlags?.0.isEmpty ?? false), isLoading: isSearching, animated: animated, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: peersFilter, key: strongSelf.key, tagMask: tagMask, interaction: chatListInteraction, listInteraction: listInteraction, peerContextAction: { message, node, rect, gesture in
|
||||
var transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags?.0 != nil, isEmpty: !isSearching && (entriesAndFlags?.0.isEmpty ?? false), isLoading: isSearching, animated: animated, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: peersFilter, key: strongSelf.key, tagMask: tagMask, interaction: chatListInteraction, listInteraction: listInteraction, peerContextAction: { message, node, rect, gesture in
|
||||
interaction.peerContextAction?(message, node, rect, gesture)
|
||||
}, toggleExpandLocalResults: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
@ -1658,10 +1656,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
var items: [ActionSheetItem] = []
|
||||
|
||||
//TODO:localize
|
||||
items.append(ActionSheetTextItem(title: "Telegram allows to store all received and sent\ndocuments in the cloud and save storage\nspace on your device."))
|
||||
items.append(ActionSheetAnimationAndTextItem(title: strongSelf.presentationData.strings.DownloadList_ClearAlertTitle, text: strongSelf.presentationData.strings.DownloadList_ClearAlertText))
|
||||
|
||||
//TODO:localize
|
||||
items.append(ActionSheetButtonItem(title: "Manage Device Storage", color: .accent, action: { [weak actionSheet] in
|
||||
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.DownloadList_OptionManageDeviceStorage, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -1670,8 +1667,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
strongSelf.context.sharedContext.openStorageUsage(context: strongSelf.context)
|
||||
}))
|
||||
|
||||
//TODO:localize
|
||||
items.append(ActionSheetButtonItem(title: "Clear Downloads List", color: .destructive, action: { [weak actionSheet] in
|
||||
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.DownloadList_ClearDownloadList, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -1712,9 +1708,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
})
|
||||
})
|
||||
strongSelf.currentEntries = newEntries
|
||||
/*if key == .downloads, !firstTime {
|
||||
if key == .downloads, !firstTime, !"".isEmpty {
|
||||
transition.animated = true
|
||||
}*/
|
||||
}
|
||||
strongSelf.enqueueTransition(transition, firstTime: firstTime)
|
||||
|
||||
var messages: [EngineMessage] = []
|
||||
|
@ -55,7 +55,36 @@ public enum ChatListSearchPaneKey {
|
||||
case voice
|
||||
}
|
||||
|
||||
let defaultAvailableSearchPanes: [ChatListSearchPaneKey] = [.chats, .media, .downloads, .links, .files, .music, .voice]
|
||||
extension ChatListSearchPaneKey {
|
||||
var filter: ChatListSearchFilter {
|
||||
switch self {
|
||||
case .chats:
|
||||
return .chats
|
||||
case .media:
|
||||
return .media
|
||||
case .downloads:
|
||||
return .downloads
|
||||
case .links:
|
||||
return .links
|
||||
case .files:
|
||||
return .files
|
||||
case .music:
|
||||
return .music
|
||||
case .voice:
|
||||
return .voice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func defaultAvailableSearchPanes(hasDownloads: Bool) -> [ChatListSearchPaneKey] {
|
||||
var result: [ChatListSearchPaneKey] = [.chats, .media, .downloads, .links, .files, .music, .voice]
|
||||
|
||||
if !hasDownloads {
|
||||
result.removeAll(where: { $0 == .downloads })
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
struct ChatListSearchPaneSpecifier: Equatable {
|
||||
var key: ChatListSearchPaneKey
|
||||
|
@ -522,7 +522,10 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
|
||||
iconImage = .roundVideo(file)
|
||||
} else if !isAudio {
|
||||
let fileName: String = file.fileName ?? "File"
|
||||
var fileName: String = file.fileName ?? "File"
|
||||
if file.isVideo {
|
||||
fileName = item.presentationData.strings.Message_Video
|
||||
}
|
||||
titleText = NSAttributedString(string: fileName, font: titleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor)
|
||||
|
||||
var fileExtension: String?
|
||||
@ -569,8 +572,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
} else if let image = media as? TelegramMediaImage {
|
||||
selectedMedia = image
|
||||
|
||||
//TODO:localize
|
||||
let fileName: String = "Photo"
|
||||
let fileName: String = item.presentationData.strings.Message_Photo
|
||||
titleText = NSAttributedString(string: fileName, font: titleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor)
|
||||
|
||||
if let representation = smallestImageRepresentation(image.representations) {
|
||||
@ -656,7 +658,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
if let file = selectedMedia as? TelegramMediaFile {
|
||||
updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList)
|
||||
|> mapToSignal { value -> Signal<FileMediaResourceStatus, NoError> in
|
||||
if case .Fetching = value.fetchStatus {
|
||||
if case .Fetching = value.fetchStatus, !item.isDownloadList {
|
||||
return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue())
|
||||
} else {
|
||||
return .single(value)
|
||||
@ -686,7 +688,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
} else if let image = selectedMedia as? TelegramMediaImage {
|
||||
updatedStatusSignal = messageImageMediaResourceStatus(context: item.context, image: image, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult || item.isDownloadList)
|
||||
|> mapToSignal { value -> Signal<FileMediaResourceStatus, NoError> in
|
||||
if case .Fetching = value.fetchStatus {
|
||||
if case .Fetching = value.fetchStatus, !item.isDownloadList {
|
||||
return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue())
|
||||
} else {
|
||||
return .single(value)
|
||||
@ -1301,7 +1303,12 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
transition.updateFrame(node: self.descriptionNode, frame: descriptionFrame)
|
||||
}
|
||||
|
||||
let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
|
||||
let alphaTransition: ContainedViewLayoutTransition
|
||||
if item.isDownloadList {
|
||||
alphaTransition = .immediate
|
||||
} else {
|
||||
alphaTransition = .animated(duration: 0.3, curve: .easeInOut)
|
||||
}
|
||||
if downloadingString != nil {
|
||||
alphaTransition.updateAlpha(node: self.descriptionProgressNode, alpha: 1.0)
|
||||
alphaTransition.updateAlpha(node: self.descriptionNode, alpha: 0.0)
|
||||
|
@ -1052,14 +1052,28 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
|
||||
})
|
||||
|
||||
self.textBackgroundNode.isHidden = true
|
||||
|
||||
if let accessoryComponentView = node.accessoryComponentView {
|
||||
let tempContainer = UIView()
|
||||
|
||||
let accessorySize = accessoryComponentView.bounds.size
|
||||
tempContainer.frame = CGRect(origin: CGPoint(x: self.textBackgroundNode.frame.maxX - accessorySize.width - 4.0, y: floor((self.textBackgroundNode.frame.minY + self.textBackgroundNode.frame.height - accessorySize.height) / 2.0)), size: accessorySize)
|
||||
|
||||
let targetTempContainerFrame = CGRect(origin: CGPoint(x: targetTextBackgroundFrame.maxX - accessorySize.width - 4.0, y: floor((targetTextBackgroundFrame.minY + 8.0 + targetTextBackgroundFrame.height - accessorySize.height) / 2.0)), size: accessorySize)
|
||||
|
||||
tempContainer.layer.animateFrame(from: tempContainer.frame, to: targetTempContainerFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
|
||||
|
||||
accessoryComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
tempContainer.addSubview(accessoryComponentView)
|
||||
self.view.addSubview(tempContainer)
|
||||
}
|
||||
|
||||
self.textBackgroundNode.layer.animateFrame(from: self.textBackgroundNode.frame, to: targetTextBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak node] _ in
|
||||
textBackgroundCompleted = true
|
||||
intermediateCompletion()
|
||||
|
||||
if let node = node, let accessoryComponentView = node.accessoryComponentView {
|
||||
accessoryComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
accessoryComponentView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
if let node = node, let accessoryComponentContainer = node.accessoryComponentContainer, let accessoryComponentView = node.accessoryComponentView {
|
||||
accessoryComponentContainer.addSubview(accessoryComponentView)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -36,7 +36,8 @@ public class SearchBarPlaceholderNode: ASDisplayNode {
|
||||
|
||||
public private(set) var placeholderString: NSAttributedString?
|
||||
|
||||
var accessoryComponentView: ComponentHostView<Empty>?
|
||||
private(set) var accessoryComponentContainer: UIView?
|
||||
private(set) var accessoryComponentView: ComponentHostView<Empty>?
|
||||
|
||||
convenience public override init() {
|
||||
self.init(fieldStyle: .legacy)
|
||||
@ -110,13 +111,22 @@ public class SearchBarPlaceholderNode: ASDisplayNode {
|
||||
|
||||
public func setAccessoryComponent(component: AnyComponent<Empty>?) {
|
||||
if let component = component {
|
||||
let accessoryComponentContainer: UIView
|
||||
if let current = self.accessoryComponentContainer {
|
||||
accessoryComponentContainer = current
|
||||
} else {
|
||||
accessoryComponentContainer = UIView()
|
||||
self.accessoryComponentContainer = accessoryComponentContainer
|
||||
self.view.addSubview(accessoryComponentContainer)
|
||||
}
|
||||
|
||||
let accessoryComponentView: ComponentHostView<Empty>
|
||||
if let current = self.accessoryComponentView {
|
||||
accessoryComponentView = current
|
||||
} else {
|
||||
accessoryComponentView = ComponentHostView()
|
||||
self.accessoryComponentView = accessoryComponentView
|
||||
self.view.addSubview(accessoryComponentView)
|
||||
accessoryComponentContainer.addSubview(accessoryComponentView)
|
||||
}
|
||||
let accessorySize = accessoryComponentView.update(
|
||||
transition: .immediate,
|
||||
@ -124,7 +134,8 @@ public class SearchBarPlaceholderNode: ASDisplayNode {
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 32.0, height: 32.0)
|
||||
)
|
||||
accessoryComponentView.frame = CGRect(origin: CGPoint(x: self.bounds.width - accessorySize.width - 4.0, y: floor((self.bounds.height - accessorySize.height) / 2.0)), size: accessorySize)
|
||||
accessoryComponentContainer.frame = CGRect(origin: CGPoint(x: self.bounds.width - accessorySize.width - 4.0, y: floor((self.bounds.height - accessorySize.height) / 2.0)), size: accessorySize)
|
||||
accessoryComponentView.frame = CGRect(origin: CGPoint(), size: accessorySize)
|
||||
} else if let accessoryComponentView = self.accessoryComponentView {
|
||||
self.accessoryComponentView = nil
|
||||
accessoryComponentView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||
@ -220,8 +231,8 @@ public class SearchBarPlaceholderNode: ASDisplayNode {
|
||||
transition.updateAlpha(node: strongSelf.backgroundNode, alpha: outerAlpha)
|
||||
transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: constrainedSize.width, height: height)))
|
||||
|
||||
if let accessoryComponentView = strongSelf.accessoryComponentView {
|
||||
accessoryComponentView.frame = CGRect(origin: CGPoint(x: constrainedSize.width - accessoryComponentView.bounds.width - 4.0, y: floor((constrainedSize.height - accessoryComponentView.bounds.height) / 2.0)), size: accessoryComponentView.bounds.size)
|
||||
if let accessoryComponentContainer = strongSelf.accessoryComponentContainer {
|
||||
accessoryComponentContainer.frame = CGRect(origin: CGPoint(x: constrainedSize.width - accessoryComponentContainer.bounds.width - 4.0, y: floor((constrainedSize.height - accessoryComponentContainer.bounds.height) / 2.0)), size: accessoryComponentContainer.bounds.size)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -658,7 +658,6 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
}
|
||||
).minSize(CGSize(width: 44.0, height: 44.0)))))*/
|
||||
|
||||
//TODO:localize
|
||||
let navigationBar = navigationBar.update(
|
||||
component: NavigationBarComponent(
|
||||
topInset: environment.statusBarHeight,
|
||||
@ -670,7 +669,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
})
|
||||
),
|
||||
rightItems: navigationRightItems,
|
||||
centerItem: AnyComponent(Text(text: "Live Stream", font: Font.semibold(17.0), color: .white))
|
||||
centerItem: AnyComponent(Text(text: environment.strings.VoiceChatChannel_Title, font: Font.semibold(17.0), color: .white))
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
|
||||
transition: context.transition
|
||||
@ -681,10 +680,10 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
var infoItem: AnyComponent<Empty>?
|
||||
if let originInfo = context.state.originInfo {
|
||||
let memberCountString: String
|
||||
if originInfo.memberCount == 1 {
|
||||
memberCountString = "1 viewer"
|
||||
if originInfo.memberCount == 0 {
|
||||
memberCountString = environment.strings.LiveStream_NoViewers
|
||||
} else {
|
||||
memberCountString = "\(originInfo.memberCount) viewers"
|
||||
memberCountString = environment.strings.LiveStream_ViewerCount(Int32(originInfo.memberCount))
|
||||
}
|
||||
infoItem = AnyComponent(OriginInfoComponent(
|
||||
title: originInfo.title,
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Raise.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Raise.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "promote.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
98
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Raise.imageset/promote.pdf
vendored
Normal file
98
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Raise.imageset/promote.pdf
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 5.335022 3.205078 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
6.194798 17.265186 m
|
||||
6.454496 17.524885 6.875551 17.524885 7.135249 17.265186 c
|
||||
13.135250 11.265186 l
|
||||
13.394949 11.005488 13.394949 10.584434 13.135250 10.324736 c
|
||||
12.875551 10.065037 12.454496 10.065037 12.194798 10.324736 c
|
||||
6.665023 15.854509 l
|
||||
1.135250 10.324736 l
|
||||
0.875551 10.065037 0.454496 10.065037 0.194797 10.324736 c
|
||||
-0.064902 10.584434 -0.064902 11.005488 0.194797 11.265186 c
|
||||
6.194798 17.265186 l
|
||||
h
|
||||
7.135226 12.265148 m
|
||||
6.875528 12.524847 6.454473 12.524847 6.194775 12.265148 c
|
||||
0.194774 6.265148 l
|
||||
-0.064925 6.005449 -0.064925 5.584394 0.194774 5.324696 c
|
||||
0.454473 5.064997 0.875528 5.064997 1.135227 5.324696 c
|
||||
6.665000 10.854470 l
|
||||
12.194775 5.324696 l
|
||||
12.454473 5.064997 12.875528 5.064997 13.135227 5.324696 c
|
||||
13.394926 5.584394 13.394926 6.005449 13.135227 6.265148 c
|
||||
7.135226 12.265148 l
|
||||
h
|
||||
6.194775 7.265148 m
|
||||
0.194774 1.265148 l
|
||||
-0.064925 1.005449 -0.064925 0.584394 0.194774 0.324696 c
|
||||
0.454473 0.064997 0.875528 0.064997 1.135227 0.324696 c
|
||||
6.665000 5.854470 l
|
||||
12.194775 0.324696 l
|
||||
12.454473 0.064997 12.875528 0.064997 13.135227 0.324696 c
|
||||
13.394926 0.584394 13.394926 1.005449 13.135227 1.265148 c
|
||||
7.135226 7.265148 l
|
||||
6.875528 7.524847 6.454473 7.524847 6.194775 7.265148 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1325
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001415 00000 n
|
||||
0000001438 00000 n
|
||||
0000001611 00000 n
|
||||
0000001685 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1744
|
||||
%%EOF
|
@ -577,6 +577,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
filter: self.filter,
|
||||
groupId: EngineChatList.Group(.root),
|
||||
displaySearchFilters: false,
|
||||
hasDownloads: false,
|
||||
openPeer: { [weak self] peer, chatPeer, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
Loading…
x
Reference in New Issue
Block a user