Download list improvements

This commit is contained in:
Ali 2022-02-22 22:08:42 +04:00
parent dea605dd15
commit 8086ea1ecb
16 changed files with 323 additions and 152 deletions

View File

@ -116,10 +116,10 @@ public func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayer
return nil
}
public func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? {
if isGlobalSearch {
public func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? {
if isGlobalSearch && !isDownloadList {
return (PeerMessagesMediaPlaylistId.custom, PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index))
} else if isRecentActions {
} else if isRecentActions && !isDownloadList {
return (PeerMessagesMediaPlaylistId.recentActions(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index))
} else {
return (PeerMessagesMediaPlaylistId.peer(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index))

View File

@ -116,7 +116,8 @@ public class ChatListSearchItemNode: ListViewItemNode {
let backgroundColor = nextIsPinned ? item.theme.chatList.pinnedItemBackgroundColor : item.theme.chatList.itemBackgroundColor
let placeholderColor = item.theme.list.itemSecondaryTextColor
let (_, searchBarApply) = searchBarNodeLayout(NSAttributedString(string: placeholder ?? "", font: searchBarFont, textColor: placeholderColor), CGSize(width: baseWidth - 20.0, height: 36.0), 1.0, placeholderColor, nextIsPinned ? item.theme.chatList.pinnedSearchBarColor : item.theme.chatList.regularSearchBarColor, backgroundColor, .immediate)
let placeholderString = NSAttributedString(string: placeholder ?? "", font: searchBarFont, textColor: placeholderColor)
let (_, searchBarApply) = searchBarNodeLayout(placeholderString, placeholderString, CGSize(width: baseWidth - 20.0, height: 36.0), 1.0, placeholderColor, nextIsPinned ? item.theme.chatList.pinnedSearchBarColor : item.theme.chatList.regularSearchBarColor, backgroundColor, .immediate)
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 54.0), insets: UIEdgeInsets())

View File

@ -469,7 +469,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})
if !previewing {
self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.DialogList_SearchLabel, activate: { [weak self] in
self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.DialogList_SearchLabel, compactPlaceholder: self.presentationData.strings.DialogList_SearchLabelCompact, activate: { [weak self] in
self?.activateSearch()
})
self.searchContentNode?.updateExpansionProgress(0.0)
@ -601,9 +601,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
let stateSignal: Signal<State, NoError> = (combineLatest(queue: .mainQueue(), entriesWithFetchStatuses, recentDownloadItems(postbox: context.account.postbox))
|> map { entries, recentDownloadItems -> State in
if !entries.isEmpty {
let displayRecentDownloads = context.account.postbox.tailChatListView(groupId: .root, filterPredicate: nil, count: 11, summaryComponents: ChatListEntrySummaryComponents(components: [:]))
|> map { view -> Bool in
return view.0.entries.count >= 10
}
|> distinctUntilChanged
let stateSignal: Signal<State, NoError> = (combineLatest(queue: .mainQueue(), entriesWithFetchStatuses, recentDownloadItems(postbox: context.account.postbox), displayRecentDownloads)
|> map { entries, recentDownloadItems, displayRecentDownloads -> State in
if !entries.isEmpty && displayRecentDownloads {
var totalBytes = 0.0
var totalProgressInBytes = 0.0
for (entry, progress) in entries {
@ -799,7 +805,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.navigationItem.backBarButtonItem = backBarButtonItem
}
self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.DialogList_SearchLabel)
self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.DialogList_SearchLabel, compactPlaceholder: self.presentationData.strings.DialogList_SearchLabelCompact)
let editing = self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing
let editItem: UIBarButtonItem
if editing {
@ -2088,7 +2094,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
activate()
activate(filter != .downloads)
if let searchContentNode = strongSelf.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode {
searchContentNode.search(filter: filter, query: query)

View File

@ -1193,7 +1193,7 @@ final class ChatListControllerNode: ASDisplayNode {
}
}
func activateSearch(placeholderNode: SearchBarPlaceholderNode, displaySearchFilters: Bool, initialFilter: ChatListSearchFilter, navigationController: NavigationController?) -> (ASDisplayNode, () -> Void)? {
func activateSearch(placeholderNode: SearchBarPlaceholderNode, displaySearchFilters: Bool, initialFilter: ChatListSearchFilter, navigationController: NavigationController?) -> (ASDisplayNode, (Bool) -> Void)? {
guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
return nil
}
@ -1226,7 +1226,7 @@ final class ChatListControllerNode: ASDisplayNode {
})
self.containerNode.accessibilityElementsHidden = true
return (contentNode.filterContainerNode, { [weak self] in
return (contentNode.filterContainerNode, { [weak self] focus in
guard let strongSelf = self else {
return
}
@ -1239,7 +1239,7 @@ final class ChatListControllerNode: ASDisplayNode {
strongSelf.insertSubnode(subnode, belowSubnode: navigationBar)
}
}
}, placeholder: placeholderNode)
}, placeholder: placeholderNode, focus: focus)
})
}

View File

@ -870,7 +870,28 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
})
})))
if !actions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty {
if isCachedValue {
if !items.isEmpty {
items.append(.separator)
}
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
c.dismiss(completion: {
if let strongSelf = self {
strongSelf.dismissInput()
strongSelf.updateState { state in
return state.withUpdatedSelectedMessageIds([message.id])
}
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
}
})
})))
}
/*if !actions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty {
if !items.isEmpty {
items.append(.separator)
}
@ -905,7 +926,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
f(.dismissWithoutContent)
})))
}
}
}*/
return items
}
@ -1061,34 +1082,53 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}
func deleteMessages(messageIds: Set<EngineMessage.Id>?) {
let isDownloads = self.paneContainerNode.currentPaneKey == .downloads
if let messageIds = messageIds ?? self.stateValue.selectedMessageIds, !messageIds.isEmpty {
let (peers, messages) = self.currentMessages
let _ = (self.context.account.postbox.transaction { transaction -> Void in
for id in messageIds {
if transaction.getMessage(id) == nil, let message = messages[id] {
storeMessageFromSearch(transaction: transaction, message: message._asMessage())
}
if isDownloads {
let _ = (self.context.account.postbox.transaction { transaction -> [Message] in
return messageIds.compactMap(transaction.getMessage)
}
}).start()
self.activeActionDisposable.set((self.context.sharedContext.chatAvailableMessageActions(postbox: self.context.account.postbox, accountPeerId: self.context.account.peerId, messageIds: messageIds, messages: messages, peers: peers)
|> deliverOnMainQueue).start(next: { [weak self] actions in
if let strongSelf = self, !actions.options.isEmpty {
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
var items: [ActionSheetItem] = []
let personalPeerName: String? = nil
|> deliverOnMainQueue).start(next: { [weak self] messages in
guard let strongSelf = self else {
return
}
if actions.options.contains(.deleteGlobally) {
let globalTitle: String
if let personalPeerName = personalPeerName {
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).string
} else {
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone
}
items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).start()
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."
}
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: {
guard let strongSelf = self else {
return
}
var resourceIds = Set<MediaResourceId>()
for message in messages {
for media in message.media {
if let file = media as? TelegramMediaFile {
resourceIds.insert(file.resource.id)
}
}
}
let _ = (strongSelf.context.account.postbox.mediaBox.removeCachedResources(resourceIds, force: true, notify: true)
|> deliverOnMainQueue).start(completed: {
guard let strongSelf = self else {
return
}
strongSelf.updateState { state in
return state.withUpdatedSelectedMessageIds(nil)
@ -1096,34 +1136,74 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
}))
}
if actions.options.contains(.deleteLocally) {
let localOptionText = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe
items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forLocalPeer).start()
strongSelf.updateState { state in
return state.withUpdatedSelectedMessageIds(nil)
}
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
}))
}
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
})
])])
strongSelf.view.endEditing(true)
strongSelf.present?(actionSheet, nil)
}
}))
], actionLayout: .horizontal, parseMarkdown: true), nil)
})
} else {
let (peers, messages) = self.currentMessages
let _ = (self.context.account.postbox.transaction { transaction -> Void in
for id in messageIds {
if transaction.getMessage(id) == nil, let message = messages[id] {
storeMessageFromSearch(transaction: transaction, message: message._asMessage())
}
}
}).start()
self.activeActionDisposable.set((self.context.sharedContext.chatAvailableMessageActions(postbox: self.context.account.postbox, accountPeerId: self.context.account.peerId, messageIds: messageIds, messages: messages, peers: peers)
|> deliverOnMainQueue).start(next: { [weak self] actions in
if let strongSelf = self, !actions.options.isEmpty {
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
var items: [ActionSheetItem] = []
let personalPeerName: String? = nil
if actions.options.contains(.deleteGlobally) {
let globalTitle: String
if let personalPeerName = personalPeerName {
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).string
} else {
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone
}
items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).start()
strongSelf.updateState { state in
return state.withUpdatedSelectedMessageIds(nil)
}
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
}))
}
if actions.options.contains(.deleteLocally) {
let localOptionText = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe
items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forLocalPeer).start()
strongSelf.updateState { state in
return state.withUpdatedSelectedMessageIds(nil)
}
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
}))
}
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
strongSelf.view.endEditing(true)
strongSelf.present?(actionSheet, nil)
}
}))
}
}
}

View File

@ -400,7 +400,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
}
}
public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int, isFirstInList: Bool)?) -> Void)?, openStorageSettings: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ListViewItem {
public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ListViewItem {
switch self {
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
let primaryPeer: EnginePeer
@ -564,8 +564,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
}
case .downloaded:
//TODO:localize
header = ChatListSearchItemHeader(type: .recentDownloads, theme: presentationData.theme, strings: presentationData.strings, actionTitle: "Settings", action: {
openStorageSettings()
header = ChatListSearchItemHeader(type: .recentDownloads, theme: presentationData.theme, strings: presentationData.strings, actionTitle: "Clear", action: {
openClearRecentlyDownloaded()
})
case .index:
header = ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
@ -628,12 +628,12 @@ private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [
return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates)
}
public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int, isFirstInList: Bool)?) -> Void)?, openStorageSettings: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ChatListSearchContainerTransition {
public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ChatListSearchContainerTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openStorageSettings: openStorageSettings, toggleAllPaused: toggleAllPaused), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openStorageSettings: openStorageSettings, toggleAllPaused: toggleAllPaused), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused), directionHint: nil) }
return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, isLoading: isLoading, query: searchQuery, animated: animated)
}
@ -981,8 +981,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
return false
}
return presentationDataPromise.get()
|> map { presentationData -> ([ChatListSearchEntry], Bool)? in
return combineLatest(queue: .mainQueue(), presentationDataPromise.get(), selectionPromise.get())
|> map { presentationData, selectionState -> ([ChatListSearchEntry], Bool)? in
var entries: [ChatListSearchEntry] = []
var existingMessageIds = Set<MessageId>()
@ -1047,7 +1047,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
peer = EngineRenderedPeer(peer: EnginePeer(channelPeer))
}
}
entries.append(.message(message, peer, nil, presentationData, 1, nil, false, .downloaded(timestamp: item.timestamp, index: message.index), (item.resourceId, item.size, false), .recentlyDownloaded, false))
entries.append(.message(message, peer, nil, presentationData, 1, selectionState?.contains(message.id), false, .downloaded(timestamp: item.timestamp, index: message.index), (item.resourceId, item.size, false), .recentlyDownloaded, false))
}
return (entries.sorted(), false)
}
@ -1501,6 +1502,17 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
})
}
let playlistLocation: PeerMessagesPlaylistLocation?
if strongSelf.key == .downloads {
playlistLocation = nil
} else {
playlistLocation = .custom(messages: foundMessages |> map { message, a, b in
return (message.map { $0._asMessage() }, a, b)
}, at: message.id, loadMore: {
loadMore()
})
}
return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, mode: mode, navigationController: navigationController, dismissInput: {
interaction.dismissInput()
}, present: { c, a in
@ -1524,11 +1536,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, openPeer: { peer, navigation in
}, callPeer: { _, _ in
}, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: .custom(messages: foundMessages |> map { message, a, b in
return (message.map { $0._asMessage() }, a, b)
}, at: message.id, loadMore: {
loadMore()
}), gallerySource: gallerySource))
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation, gallerySource: gallerySource))
}, openMessageContextMenu: { [weak self] message, _, node, rect, gesture in
guard let strongSelf = self, let currentEntries = strongSelf.currentEntries else {
return
@ -1641,11 +1649,44 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, searchPeer: { peer in
}, searchQuery: strongSelf.searchQueryValue, searchOptions: strongSelf.searchOptionsValue, messageContextAction: { message, node, rect, gesture, paneKey, downloadResource in
interaction.messageContextAction(message, node, rect, gesture, paneKey, downloadResource)
}, openStorageSettings: {
}, openClearRecentlyDownloaded: {
guard let strongSelf = self else {
return
}
strongSelf.context.sharedContext.openStorageUsage(context: strongSelf.context)
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
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."))
//TODO:localize
items.append(ActionSheetButtonItem(title: "Manage Device Storage", color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
guard let strongSelf = self else {
return
}
strongSelf.context.sharedContext.openStorageUsage(context: strongSelf.context)
}))
//TODO:localize
items.append(ActionSheetButtonItem(title: "Clear Downloads List", color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
guard let strongSelf = self else {
return
}
let _ = clearRecentDownloadList(postbox: strongSelf.context.account.postbox).start()
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
strongSelf.interaction.dismissInput()
strongSelf.interaction.present(actionSheet, nil)
}, toggleAllPaused: {
guard let strongSelf = self else {
return
@ -1833,7 +1874,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
loadMore()
}
if [.file, .music, .voiceOrInstantVideo].contains(tagMask) {
if [.file, .music, .voiceOrInstantVideo].contains(tagMask) || self.key == .downloads {
self.mediaStatusDisposable = (context.sharedContext.mediaManager.globalMediaPlayerState
|> mapToSignal { playlistStateAndType -> Signal<(Account, SharedMediaPlayerItemPlaybackState, MediaManagerPlayerType)?, NoError> in
if let (account, state, type) = playlistStateAndType {
@ -1846,7 +1887,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
return .single(nil) |> delay(0.2, queue: .mainQueue())
}
case .music:
if tagMask != .music {
if tagMask != .music && self.key != .downloads {
return .single(nil) |> delay(0.2, queue: .mainQueue())
}
case .file:

View File

@ -6,12 +6,12 @@ import SwiftSignalKit
import UniversalMediaPlayer
import AccountContext
private func internalMessageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> Signal<MediaPlayerStatus?, NoError> {
private func internalMessageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal<MediaPlayerStatus?, NoError> {
guard let playerType = peerMessageMediaPlayerType(message) else {
return .single(nil)
}
if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) {
if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch, isDownloadList: isDownloadList) {
return context.sharedContext.mediaManager.filteredPlaylistState(accountId: context.account.id, playlistId: playlistId, itemId: itemId, type: playerType)
|> mapToSignal { state -> Signal<MediaPlayerStatus?, NoError> in
return .single(state?.status)
@ -21,31 +21,32 @@ private func internalMessageFileMediaPlaybackStatus(context: AccountContext, fil
}
}
public func messageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> Signal<MediaPlayerStatus, NoError> {
public func messageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal<MediaPlayerStatus, NoError> {
var duration = 0.0
if let value = file.duration {
duration = Double(value)
}
let defaultStatus = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true)
return internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) |> map { status in
return internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch, isDownloadList: isDownloadList)
|> map { status in
return status ?? defaultStatus
}
}
public func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> Signal<Float, NoError> {
public func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal<Float, NoError> {
guard let playerType = peerMessageMediaPlayerType(message) else {
return .never()
}
if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) {
if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch, isDownloadList: isDownloadList) {
return context.sharedContext.mediaManager.filteredPlayerAudioLevelEvents(accountId: context.account.id, playlistId: playlistId, itemId: itemId, type: playerType)
} else {
return .never()
}
}
public func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false) -> Signal<FileMediaResourceStatus, NoError> {
let playbackStatus = internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) |> map { status -> MediaPlayerPlaybackStatus? in
public func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false, isDownloadList: Bool = false) -> Signal<FileMediaResourceStatus, NoError> {
let playbackStatus = internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch, isDownloadList: isDownloadList) |> map { status -> MediaPlayerPlaybackStatus? in
return status?.status
}

View File

@ -98,7 +98,7 @@ public final class HashtagSearchController: TelegramBaseController {
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], key: .chats, tagMask: nil, interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: {
}, toggleExpandGlobalResults: {
}, searchPeer: { _ in
}, searchQuery: "", searchOptions: nil, messageContextAction: nil, openStorageSettings: {}, toggleAllPaused: {})
}, searchQuery: "", searchOptions: nil, messageContextAction: nil, openClearRecentlyDownloaded: {}, toggleAllPaused: {})
strongSelf.controllerNode.enqueueTransition(transition, firstTime: firstTime)
}
})

View File

@ -496,7 +496,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?
@ -543,8 +546,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_Video
titleText = NSAttributedString(string: fileName, font: titleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor)
if let representation = smallestImageRepresentation(image.representations) {
@ -624,7 +626,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
if statusUpdated {
if let file = selectedMedia as? TelegramMediaFile {
updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult || item.isDownloadList)
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 {
return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue())
@ -638,16 +640,20 @@ public final class ListMessageFileItemNode: ListMessageNode {
updatedStatusSignal = currentUpdatedStatusSignal
|> map { status in
switch status.mediaStatus {
case .fetchStatus:
case .fetchStatus:
if item.isDownloadList {
return FileMediaResourceStatus(mediaStatus: .fetchStatus(status.fetchStatus), fetchStatus: status.fetchStatus)
} else {
return FileMediaResourceStatus(mediaStatus: .fetchStatus(.Local), fetchStatus: status.fetchStatus)
case .playbackStatus:
return status
}
case .playbackStatus:
return status
}
}
}
}
if isVoice {
updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: file, message: message, isRecentActions: false, isGlobalSearch: item.isGlobalSearchResult || item.isDownloadList)
updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: file, message: message, isRecentActions: false, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList)
}
} 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)
@ -996,27 +1002,32 @@ public final class ListMessageFileItemNode: ListMessageNode {
if !isAudio && !isInstantVideo {
self.updateProgressFrame(size: contentSize, leftInset: layoutParams.leftInset, rightInset: layoutParams.rightInset, transition: .immediate)
} else {
if item.isDownloadList {
self.updateProgressFrame(size: contentSize, leftInset: layoutParams.leftInset, rightInset: layoutParams.rightInset, transition: .immediate)
}
switch status {
case let .fetchStatus(fetchStatus):
switch fetchStatus {
case .Fetching:
break
case .Local:
if isAudio || isInstantVideo {
iconStatusState = .play
}
case .Remote, .Paused:
if isAudio || isInstantVideo {
iconStatusState = .play
}
case let .fetchStatus(fetchStatus):
switch fetchStatus {
case let .Fetching(_, progress):
if item.isDownloadList {
iconStatusState = .progress(value: CGFloat(progress), cancelEnabled: false, appearance: nil)
}
case let .playbackStatus(playbackStatus):
switch playbackStatus {
case .playing:
iconStatusState = .pause
case .paused:
case .Local:
if isAudio || isInstantVideo {
iconStatusState = .play
}
case .Remote, .Paused:
if isAudio || isInstantVideo {
iconStatusState = .play
}
}
case let .playbackStatus(playbackStatus):
switch playbackStatus {
case .playing:
iconStatusState = .pause
case .paused:
iconStatusState = .play
}
}
}
self.iconStatusNode.backgroundNodeColor = iconStatusBackgroundColor
@ -1090,17 +1101,22 @@ public final class ListMessageFileItemNode: ListMessageNode {
break
case let .fetchStatus(fetchStatus):
maybeFetchStatus = fetchStatus
switch fetchStatus {
case .Fetching(_, let progress), .Paused(let progress):
if let file = self.currentMedia as? TelegramMediaFile, let size = file.size {
downloadingString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData))) / \(dataSizeString(size, forceDecimal: true, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData)))"
}
descriptionOffset = 14.0
case .Remote:
descriptionOffset = 14.0
case .Local:
break
}
}
if item.isDownloadList, let fetchStatus = self.fetchStatus {
maybeFetchStatus = fetchStatus
}
switch maybeFetchStatus {
case .Fetching(_, let progress), .Paused(let progress):
if let file = self.currentMedia as? TelegramMediaFile, let size = file.size {
downloadingString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData))) / \(dataSizeString(size, forceDecimal: true, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData)))"
}
descriptionOffset = 14.0
case .Remote:
descriptionOffset = 14.0
case .Local:
break
}
switch maybeFetchStatus {
@ -1163,7 +1179,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
} else {
if let linearProgressNode = self.linearProgressNode {
self.linearProgressNode = nil
linearProgressNode.layer.animateAlpha(from: 1.0, to: 1.0, duration: 0.2, removeOnCompletion: false, completion: { [weak linearProgressNode] _ in
linearProgressNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak linearProgressNode] _ in
linearProgressNode?.removeFromSupernode()
})
}
@ -1419,9 +1435,11 @@ private final class DownloadIconNode: ASImageNode {
}
func updateTheme(theme: PresentationTheme) {
self.image = PresentationResourcesChat.sharedMediaFileDownloadStartIcon(theme, generate: {
return generateDownloadIcon(color: theme.list.itemAccentColor)
})
if self.image != nil {
self.image = PresentationResourcesChat.sharedMediaFileDownloadStartIcon(theme, generate: {
return generateDownloadIcon(color: theme.list.itemAccentColor)
})
}
self.customColor = theme.list.itemAccentColor
self.animationNode?.customColor = self.customColor
}

View File

@ -134,12 +134,19 @@ public class SearchBarPlaceholderNode: ASDisplayNode {
}
}
public func asyncLayout() -> (_ placeholderString: NSAttributedString?, _ constrainedSize: CGSize, _ expansionProgress: CGFloat, _ iconColor: UIColor, _ foregroundColor: UIColor, _ backgroundColor: UIColor, _ transition: ContainedViewLayoutTransition) -> (CGFloat, () -> Void) {
public func asyncLayout() -> (_ placeholderString: NSAttributedString?, _ compactPlaceholderString: NSAttributedString?, _ constrainedSize: CGSize, _ expansionProgress: CGFloat, _ iconColor: UIColor, _ foregroundColor: UIColor, _ backgroundColor: UIColor, _ transition: ContainedViewLayoutTransition) -> (CGFloat, () -> Void) {
let labelLayout = TextNode.asyncLayout(self.labelNode)
let currentForegroundColor = self.foregroundColor
let currentIconColor = self.iconColor
return { placeholderString, constrainedSize, expansionProgress, iconColor, foregroundColor, backgroundColor, transition in
return { fullPlaceholderString, compactPlaceholderString, constrainedSize, expansionProgress, iconColor, foregroundColor, backgroundColor, transition in
let placeholderString: NSAttributedString?
if constrainedSize.width < 350.0 {
placeholderString = compactPlaceholderString
} else {
placeholderString = fullPlaceholderString
}
let (labelLayoutResult, labelApply) = labelLayout(TextNodeLayoutArguments(attributedString: placeholderString, backgroundColor: .clear, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: constrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var updatedColor: UIColor?

View File

@ -11,6 +11,7 @@ public let navigationBarSearchContentHeight: CGFloat = 54.0
public class NavigationBarSearchContentNode: NavigationBarContentNode {
public var theme: PresentationTheme?
public var placeholder: String
public var compactPlaceholder: String
public let placeholderNode: SearchBarPlaceholderNode
public var placeholderHeight: CGFloat?
@ -20,9 +21,10 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode {
private var validLayout: (CGSize, CGFloat, CGFloat)?
public init(theme: PresentationTheme, placeholder: String, activate: @escaping () -> Void) {
public init(theme: PresentationTheme, placeholder: String, compactPlaceholder: String? = nil, activate: @escaping () -> Void) {
self.theme = theme
self.placeholder = placeholder
self.compactPlaceholder = compactPlaceholder ?? placeholder
self.placeholderNode = SearchBarPlaceholderNode(fieldStyle: .modern)
self.placeholderNode.labelNode.displaysAsynchronously = false
@ -36,9 +38,10 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode {
self.placeholderNode.activate = activate
}
public func updateThemeAndPlaceholder(theme: PresentationTheme, placeholder: String) {
public func updateThemeAndPlaceholder(theme: PresentationTheme, placeholder: String, compactPlaceholder: String? = nil) {
self.theme = theme
self.placeholder = placeholder
self.compactPlaceholder = compactPlaceholder ?? placeholder
self.placeholderNode.accessibilityLabel = placeholder
if let disabledOverlay = self.disabledOverlay {
disabledOverlay.backgroundColor = theme.rootController.navigationBar.opaqueBackgroundColor.withAlphaComponent(0.5)
@ -105,7 +108,11 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode {
let overscrollProgress = max(0.0, max(0.0, self.expansionProgress - 1.0 + fraction) / fraction - visibleProgress)
let searchBarNodeLayout = self.placeholderNode.asyncLayout()
let (searchBarHeight, searchBarApply) = searchBarNodeLayout(NSAttributedString(string: self.placeholder, font: searchBarFont, textColor: self.theme?.rootController.navigationSearchBar.inputPlaceholderTextColor ?? UIColor(rgb: 0x8e8e93)), CGSize(width: baseWidth, height: fieldHeight), visibleProgress, self.theme?.rootController.navigationSearchBar.inputPlaceholderTextColor ?? UIColor(rgb: 0x8e8e93), self.theme?.rootController.navigationSearchBar.inputFillColor ?? .clear, self.theme?.rootController.navigationBar.opaqueBackgroundColor ?? .clear, transition)
let placeholderString = NSAttributedString(string: self.placeholder, font: searchBarFont, textColor: self.theme?.rootController.navigationSearchBar.inputPlaceholderTextColor ?? UIColor(rgb: 0x8e8e93))
let compactPlaceholderString = NSAttributedString(string: self.compactPlaceholder, font: searchBarFont, textColor: self.theme?.rootController.navigationSearchBar.inputPlaceholderTextColor ?? UIColor(rgb: 0x8e8e93))
let (searchBarHeight, searchBarApply) = searchBarNodeLayout(placeholderString, compactPlaceholderString, CGSize(width: baseWidth, height: fieldHeight), visibleProgress, self.theme?.rootController.navigationSearchBar.inputPlaceholderTextColor ?? UIColor(rgb: 0x8e8e93), self.theme?.rootController.navigationSearchBar.inputFillColor ?? .clear, self.theme?.rootController.navigationBar.opaqueBackgroundColor ?? .clear, transition)
searchBarApply()
let searchBarFrame = CGRect(origin: CGPoint(x: padding + leftInset, y: 8.0 + overscrollProgress * fieldHeight), size: CGSize(width: baseWidth, height: fieldHeight))

View File

@ -156,7 +156,7 @@ public final class SearchDisplayController {
self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: safeInsets, additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition)
}
public func activate(insertSubnode: @escaping (ASDisplayNode, Bool) -> Void, placeholder: SearchBarPlaceholderNode?) {
public func activate(insertSubnode: @escaping (ASDisplayNode, Bool) -> Void, placeholder: SearchBarPlaceholderNode?, focus: Bool = true) {
guard let (layout, navigationBarHeight) = self.containerLayout else {
return
}
@ -231,7 +231,9 @@ public final class SearchDisplayController {
insertSubnode(self.searchBar, true)
self.searchBar.layout()
self.searchBar.activate()
if focus {
self.searchBar.activate()
}
if let placeholder = placeholder {
self.searchBar.animateIn(from: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
if self.contentNode.hasDim {

View File

@ -112,7 +112,8 @@ class NotificationSearchItemNode: ListViewItemNode {
let backgroundColor = item.theme.chatList.itemBackgroundColor
let (_, searchBarApply) = searchBarNodeLayout(NSAttributedString(string: placeholder ?? "", font: searchBarFont, textColor: UIColor(rgb: 0x8e8e93)), CGSize(width: baseWidth - 16.0, height: 28.0), 1.0, UIColor(rgb: 0x8e8e93), item.theme.chatList.regularSearchBarColor, backgroundColor, .immediate)
let placeholderString = NSAttributedString(string: placeholder ?? "", font: searchBarFont, textColor: UIColor(rgb: 0x8e8e93))
let (_, searchBarApply) = searchBarNodeLayout(placeholderString, placeholderString, CGSize(width: baseWidth - 16.0, height: 28.0), 1.0, UIColor(rgb: 0x8e8e93), item.theme.chatList.regularSearchBarColor, backgroundColor, .immediate)
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 44.0), insets: UIEdgeInsets())

View File

@ -190,6 +190,13 @@ public func addRecentDownloadItem(postbox: Postbox, item: RecentDownloadItem) ->
|> ignoreValues
}
public func clearRecentDownloadList(postbox: Postbox) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Void in
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.RecentDownloads, items: [])
}
|> ignoreValues
}
public func markRecentDownloadItemsAsSeen(postbox: Postbox, items: [(messageId: MessageId, resourceId: String)]) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Void in
var unseenIds: [(messageId: MessageId, resourceId: String)] = []

View File

@ -20,7 +20,7 @@ import ContextUI
import ChatPresentationInterfaceState
private struct FetchControls {
let fetch: () -> Void
let fetch: (Bool) -> Void
let cancel: () -> Void
}
@ -228,7 +228,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
case .Remote, .Paused:
if let fetch = self.fetchControls.with({ return $0?.fetch }) {
fetch()
fetch(true)
}
case .Local:
break
@ -251,7 +251,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
case .Remote, .Paused:
if let fetch = self.fetchControls.with({ return $0?.fetch }) {
fetch()
fetch(true)
}
case .Local:
self.activateLocalContent()
@ -316,9 +316,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
updateImageSignal = chatMessageImageFile(account: arguments.context.account, fileReference: .message(message: MessageReference(arguments.message), media: arguments.file), thumbnail: true)
}
updatedFetchControls = FetchControls(fetch: { [weak self] in
updatedFetchControls = FetchControls(fetch: { [weak self] userInitiated in
if let strongSelf = self {
strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: arguments.context, message: arguments.message, file: arguments.file, userInitiated: true).start())
strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: arguments.context, message: arguments.message, file: arguments.file, userInitiated: userInitiated).start())
}
}, cancel: {
messageMediaFileCancelInteractiveFetch(context: arguments.context, messageId: arguments.message.id, file: arguments.file)
@ -331,15 +331,15 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|> map { resourceStatus, actualFetchStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in
return (resourceStatus, actualFetchStatus)
}
updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false)
updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false)
} else {
updatedStatusSignal = messageFileMediaResourceStatus(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions)
|> map { resourceStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in
return (resourceStatus, nil)
}
updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false)
updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false)
}
updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false)
updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false)
}
var consumableContentIcon: UIImage?
@ -765,7 +765,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
if let updatedFetchControls = updatedFetchControls {
let _ = strongSelf.fetchControls.swap(updatedFetchControls)
if arguments.automaticDownload {
updatedFetchControls.fetch()
updatedFetchControls.fetch(false)
}
}

View File

@ -720,7 +720,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
playbackStatusNode.frame = videoFrame.insetBy(dx: 1.5, dy: 1.5)
let status = messageFileMediaPlaybackStatus(context: item.context, file: file, message: item.message, isRecentActions: item.associatedData.isRecentActions, isGlobalSearch: false)
let status = messageFileMediaPlaybackStatus(context: item.context, file: file, message: item.message, isRecentActions: item.associatedData.isRecentActions, isGlobalSearch: false, isDownloadList: false)
playbackStatusNode.status = status
self.durationNode?.status = status
|> map(Optional.init)