Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Mike Renoir 2022-02-23 10:48:03 +04:00
commit 2f70ec1ca2
37 changed files with 632 additions and 250 deletions

View File

@ -443,6 +443,7 @@
"DialogList.TabTitle" = "Chats"; "DialogList.TabTitle" = "Chats";
"DialogList.Title" = "Chats"; "DialogList.Title" = "Chats";
"DialogList.SearchLabel" = "Search for messages or users"; "DialogList.SearchLabel" = "Search for messages or users";
"DialogList.SearchLabelCompact" = "Search";
"DialogList.NoMessagesTitle" = "You have no conversations yet"; "DialogList.NoMessagesTitle" = "You have no conversations yet";
"DialogList.NoMessagesText" = "Start messaging by pressing the pencil button in the top right corner or go to the Contacts section."; "DialogList.NoMessagesText" = "Start messaging by pressing the pencil button in the top right corner or go to the Contacts section.";
"DialogList.SingleTypingSuffix" = "%@ is typing"; "DialogList.SingleTypingSuffix" = "%@ is typing";
@ -7304,3 +7305,24 @@ Sorry for the inconvenience.";
"Attachment.OpenSettings" = "Go to Settings"; "Attachment.OpenSettings" = "Go to Settings";
"Attachment.OpenCamera" = "Open Camera"; "Attachment.OpenCamera" = "Open Camera";
"Attachment.DeselectedPhotos_1" = "%@ photo deselected";
"Attachment.DeselectedPhotos_2" = "%@ photos deselected";
"Attachment.DeselectedPhotos_3_10" = "%@ photos deselected";
"Attachment.DeselectedPhotos_any" = "%@ photos deselected";
"Attachment.DeselectedPhotos_many" = "%@ photos deselected";
"Attachment.DeselectedPhotos_0" = "%@ photos deselected";
"Attachment.DeselectedVideos_1" = "%@ video deselected";
"Attachment.DeselectedVideos_2" = "%@ videos deselected";
"Attachment.DeselectedVideos_3_10" = "%@ videos deselected";
"Attachment.DeselectedVideos_any" = "%@ videos deselected";
"Attachment.DeselectedVideos_many" = "%@ videos deselected";
"Attachment.DeselectedVideos_0" = "%@ videos deselected";
"Attachment.DeselectedItems_1" = "%@ item deselected";
"Attachment.DeselectedItems_2" = "%@ items deselected";
"Attachment.DeselectedItems_3_10" = "%@ items deselected";
"Attachment.DeselectedItems_any" = "%@ items deselected";
"Attachment.DeselectedItems_many" = "%@ items deselected";
"Attachment.DeselectedItems_0" = "%@ items deselected";

View File

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

View File

@ -280,8 +280,8 @@ private final class AnimatedStickerDirectFrameSourceCache {
self.storeQueue = sharedStoreQueue self.storeQueue = sharedStoreQueue
self.frameCount = frameCount self.frameCount = frameCount
self.width = alignUp(size: width, align: 8) self.width = width// alignUp(size: width, align: 8)
self.height = alignUp(size: height, align: 8) self.height = height//alignUp(size: height, align: 8)
self.useHardware = useHardware self.useHardware = useHardware
let suffix : String let suffix : String

View File

@ -23,10 +23,15 @@ public enum AttachmentButtonType: Equatable {
public protocol AttachmentContainable: ViewController { public protocol AttachmentContainable: ViewController {
var requestAttachmentMenuExpansion: () -> Void { get set } var requestAttachmentMenuExpansion: () -> Void { get set }
func resetForReuse()
func prepareForReuse() func prepareForReuse()
} }
public extension AttachmentContainable { public extension AttachmentContainable {
func resetForReuse() {
}
func prepareForReuse() { func prepareForReuse() {
} }

View File

@ -110,7 +110,7 @@ private final class AttachButtonComponent: CombinedComponent {
availableSize: CGSize(width: 30.0, height: 30.0), availableSize: CGSize(width: 30.0, height: 30.0),
transition: context.transition transition: context.transition
) )
let title = title.update( let title = title.update(
component: Text( component: Text(
text: name, text: name,

View File

@ -116,7 +116,8 @@ public class ChatListSearchItemNode: ListViewItemNode {
let backgroundColor = nextIsPinned ? item.theme.chatList.pinnedItemBackgroundColor : item.theme.chatList.itemBackgroundColor let backgroundColor = nextIsPinned ? item.theme.chatList.pinnedItemBackgroundColor : item.theme.chatList.itemBackgroundColor
let placeholderColor = item.theme.list.itemSecondaryTextColor 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()) 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 { 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?.activateSearch()
}) })
self.searchContentNode?.updateExpansionProgress(0.0) 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)) let displayRecentDownloads = context.account.postbox.tailChatListView(groupId: .root, filterPredicate: nil, count: 11, summaryComponents: ChatListEntrySummaryComponents(components: [:]))
|> map { entries, recentDownloadItems -> State in |> map { view -> Bool in
if !entries.isEmpty { 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 totalBytes = 0.0
var totalProgressInBytes = 0.0 var totalProgressInBytes = 0.0
for (entry, progress) in entries { for (entry, progress) in entries {
@ -799,7 +805,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.navigationItem.backBarButtonItem = backBarButtonItem 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 editing = self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing
let editItem: UIBarButtonItem let editItem: UIBarButtonItem
if editing { if editing {
@ -2088,7 +2094,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
} }
activate() activate(filter != .downloads)
if let searchContentNode = strongSelf.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode { if let searchContentNode = strongSelf.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode {
searchContentNode.search(filter: filter, query: query) 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 { guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
return nil return nil
} }
@ -1226,7 +1226,7 @@ final class ChatListControllerNode: ASDisplayNode {
}) })
self.containerNode.accessibilityElementsHidden = true self.containerNode.accessibilityElementsHidden = true
return (contentNode.filterContainerNode, { [weak self] in return (contentNode.filterContainerNode, { [weak self] focus in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -1239,7 +1239,7 @@ final class ChatListControllerNode: ASDisplayNode {
strongSelf.insertSubnode(subnode, belowSubnode: navigationBar) 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 { if !items.isEmpty {
items.append(.separator) items.append(.separator)
} }
@ -905,7 +926,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
f(.dismissWithoutContent) f(.dismissWithoutContent)
}))) })))
} }
} }*/
return items return items
} }
@ -1061,34 +1082,53 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
} }
func deleteMessages(messageIds: Set<EngineMessage.Id>?) { func deleteMessages(messageIds: Set<EngineMessage.Id>?) {
let isDownloads = self.paneContainerNode.currentPaneKey == .downloads
if let messageIds = messageIds ?? self.stateValue.selectedMessageIds, !messageIds.isEmpty { if let messageIds = messageIds ?? self.stateValue.selectedMessageIds, !messageIds.isEmpty {
let (peers, messages) = self.currentMessages if isDownloads {
let _ = (self.context.account.postbox.transaction { transaction -> Void in let _ = (self.context.account.postbox.transaction { transaction -> [Message] in
for id in messageIds { return messageIds.compactMap(transaction.getMessage)
if transaction.getMessage(id) == nil, let message = messages[id] {
storeMessageFromSearch(transaction: transaction, message: message._asMessage())
}
} }
}).start() |> deliverOnMainQueue).start(next: { [weak self] messages in
guard let strongSelf = self else {
self.activeActionDisposable.set((self.context.sharedContext.chatAvailableMessageActions(postbox: self.context.account.postbox, accountPeerId: self.context.account.peerId, messageIds: messageIds, messages: messages, peers: peers) return
|> 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 title: String
let globalTitle: String let text: String
if let personalPeerName = personalPeerName {
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).string //TODO:localize
} else { if messageIds.count == 1 {
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone 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.";
items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in } else {
actionSheet?.dismissAnimated() title = "Remove \(messages.count) Documents?"
if let strongSelf = self { 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."
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).start() }
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 strongSelf.updateState { state in
return state.withUpdatedSelectedMessageIds(nil) return state.withUpdatedSelectedMessageIds(nil)
@ -1096,34 +1136,74 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
if let (layout, navigationBarHeight) = strongSelf.validLayout { if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) 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()
}) })
])]) ], actionLayout: .horizontal, parseMarkdown: true), nil)
strongSelf.view.endEditing(true) })
strongSelf.present?(actionSheet, 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 { switch self {
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType): case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
let primaryPeer: EnginePeer let primaryPeer: EnginePeer
@ -564,8 +564,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
} }
case .downloaded: case .downloaded:
//TODO:localize //TODO:localize
header = ChatListSearchItemHeader(type: .recentDownloads, theme: presentationData.theme, strings: presentationData.strings, actionTitle: "Settings", action: { header = ChatListSearchItemHeader(type: .recentDownloads, theme: presentationData.theme, strings: presentationData.strings, actionTitle: "Clear", action: {
openStorageSettings() openClearRecentlyDownloaded()
}) })
case .index: case .index:
header = ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) 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) 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 (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } 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 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, 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, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused), directionHint: nil) }
return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, isLoading: isLoading, query: searchQuery, animated: animated) 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 false
} }
return presentationDataPromise.get() return combineLatest(queue: .mainQueue(), presentationDataPromise.get(), selectionPromise.get())
|> map { presentationData -> ([ChatListSearchEntry], Bool)? in |> map { presentationData, selectionState -> ([ChatListSearchEntry], Bool)? in
var entries: [ChatListSearchEntry] = [] var entries: [ChatListSearchEntry] = []
var existingMessageIds = Set<MessageId>() var existingMessageIds = Set<MessageId>()
@ -1047,7 +1047,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
peer = EngineRenderedPeer(peer: EnginePeer(channelPeer)) 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) 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: { 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() interaction.dismissInput()
}, present: { c, a in }, present: { c, a in
@ -1524,11 +1536,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, openPeer: { peer, navigation in }, openPeer: { peer, navigation in
}, callPeer: { _, _ in }, callPeer: { _, _ in
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: .custom(messages: foundMessages |> map { message, a, b in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation, gallerySource: gallerySource))
return (message.map { $0._asMessage() }, a, b)
}, at: message.id, loadMore: {
loadMore()
}), gallerySource: gallerySource))
}, openMessageContextMenu: { [weak self] message, _, node, rect, gesture in }, openMessageContextMenu: { [weak self] message, _, node, rect, gesture in
guard let strongSelf = self, let currentEntries = strongSelf.currentEntries else { guard let strongSelf = self, let currentEntries = strongSelf.currentEntries else {
return return
@ -1641,11 +1649,44 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, searchPeer: { peer in }, searchPeer: { peer in
}, searchQuery: strongSelf.searchQueryValue, searchOptions: strongSelf.searchOptionsValue, messageContextAction: { message, node, rect, gesture, paneKey, downloadResource in }, searchQuery: strongSelf.searchQueryValue, searchOptions: strongSelf.searchOptionsValue, messageContextAction: { message, node, rect, gesture, paneKey, downloadResource in
interaction.messageContextAction(message, node, rect, gesture, paneKey, downloadResource) interaction.messageContextAction(message, node, rect, gesture, paneKey, downloadResource)
}, openStorageSettings: { }, openClearRecentlyDownloaded: {
guard let strongSelf = self else { guard let strongSelf = self else {
return 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: { }, toggleAllPaused: {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -1833,7 +1874,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
loadMore() loadMore()
} }
if [.file, .music, .voiceOrInstantVideo].contains(tagMask) { if [.file, .music, .voiceOrInstantVideo].contains(tagMask) || self.key == .downloads {
self.mediaStatusDisposable = (context.sharedContext.mediaManager.globalMediaPlayerState self.mediaStatusDisposable = (context.sharedContext.mediaManager.globalMediaPlayerState
|> mapToSignal { playlistStateAndType -> Signal<(Account, SharedMediaPlayerItemPlaybackState, MediaManagerPlayerType)?, NoError> in |> mapToSignal { playlistStateAndType -> Signal<(Account, SharedMediaPlayerItemPlaybackState, MediaManagerPlayerType)?, NoError> in
if let (account, state, type) = playlistStateAndType { if let (account, state, type) = playlistStateAndType {
@ -1846,7 +1887,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
return .single(nil) |> delay(0.2, queue: .mainQueue()) return .single(nil) |> delay(0.2, queue: .mainQueue())
} }
case .music: case .music:
if tagMask != .music { if tagMask != .music && self.key != .downloads {
return .single(nil) |> delay(0.2, queue: .mainQueue()) return .single(nil) |> delay(0.2, queue: .mainQueue())
} }
case .file: case .file:

View File

@ -1106,6 +1106,11 @@ open class NavigationBar: ASDisplayNode {
let leftButtonSize = self.leftButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape) let leftButtonSize = self.leftButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape)
leftTitleInset = leftButtonSize.width + leftButtonInset + 1.0 leftTitleInset = leftButtonSize.width + leftButtonInset + 1.0
var transition = transition
if self.leftButtonNode.frame.width.isZero {
transition = .immediate
}
self.leftButtonNode.alpha = 1.0 self.leftButtonNode.alpha = 1.0
transition.updateFrame(node: self.leftButtonNode, frame: CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize)) transition.updateFrame(node: self.leftButtonNode, frame: CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize))
} }
@ -1118,6 +1123,11 @@ open class NavigationBar: ASDisplayNode {
let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight)), isLandscape: isLandscape) let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight)), isLandscape: isLandscape)
rightTitleInset = rightButtonSize.width + leftButtonInset + 1.0 rightTitleInset = rightButtonSize.width + leftButtonInset + 1.0
self.rightButtonNode.alpha = 1.0 self.rightButtonNode.alpha = 1.0
var transition = transition
if self.rightButtonNode.frame.width.isZero {
transition = .immediate
}
transition.updateFrame(node: self.rightButtonNode, frame: CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize)) transition.updateFrame(node: self.rightButtonNode, frame: CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize))
} }
@ -1182,6 +1192,10 @@ open class NavigationBar: ASDisplayNode {
self.titleNode.alpha = progress * progress self.titleNode.alpha = progress * progress
} }
} else { } else {
var transition = transition
if self.titleNode.frame.width.isZero {
transition = .immediate
}
self.titleNode.alpha = 1.0 self.titleNode.alpha = 1.0
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)) transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize))
} }

View File

@ -6,12 +6,12 @@ import SwiftSignalKit
import UniversalMediaPlayer import UniversalMediaPlayer
import AccountContext 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 { guard let playerType = peerMessageMediaPlayerType(message) else {
return .single(nil) 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) return context.sharedContext.mediaManager.filteredPlaylistState(accountId: context.account.id, playlistId: playlistId, itemId: itemId, type: playerType)
|> mapToSignal { state -> Signal<MediaPlayerStatus?, NoError> in |> mapToSignal { state -> Signal<MediaPlayerStatus?, NoError> in
return .single(state?.status) 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 var duration = 0.0
if let value = file.duration { if let value = file.duration {
duration = Double(value) 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) 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 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 { guard let playerType = peerMessageMediaPlayerType(message) else {
return .never() 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) return context.sharedContext.mediaManager.filteredPlayerAudioLevelEvents(accountId: context.account.id, playlistId: playlistId, itemId: itemId, type: playerType)
} else { } else {
return .never() return .never()
} }
} }
public func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false) -> Signal<FileMediaResourceStatus, NoError> { 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) |> map { status -> MediaPlayerPlaybackStatus? in let playbackStatus = internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch, isDownloadList: isDownloadList) |> map { status -> MediaPlayerPlaybackStatus? in
return status?.status 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: { 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: { }, toggleExpandGlobalResults: {
}, searchPeer: { _ in }, searchPeer: { _ in
}, searchQuery: "", searchOptions: nil, messageContextAction: nil, openStorageSettings: {}, toggleAllPaused: {}) }, searchQuery: "", searchOptions: nil, messageContextAction: nil, openClearRecentlyDownloaded: {}, toggleAllPaused: {})
strongSelf.controllerNode.enqueueTransition(transition, firstTime: firstTime) strongSelf.controllerNode.enqueueTransition(transition, firstTime: firstTime)
} }
}) })

View File

@ -531,6 +531,9 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
{ {
TGDispatchOnMainThread(^ TGDispatchOnMainThread(^
{ {
if (!_subscribedForCameraChanges) {
return;
}
if ([keyPath isEqualToString:PGCameraAdjustingFocusKey]) if ([keyPath isEqualToString:PGCameraAdjustingFocusKey])
{ {
bool adjustingFocus = [[change objectForKey:NSKeyValueChangeNewKey] isEqualToNumber:@YES]; bool adjustingFocus = [[change objectForKey:NSKeyValueChangeNewKey] isEqualToNumber:@YES];

View File

@ -654,7 +654,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
if statusUpdated && item.displayFileInfo { if statusUpdated && item.displayFileInfo {
if let file = selectedMedia as? TelegramMediaFile { 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 |> mapToSignal { value -> Signal<FileMediaResourceStatus, NoError> in
if case .Fetching = value.fetchStatus { if case .Fetching = value.fetchStatus {
return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue()) return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue())
@ -668,16 +668,20 @@ public final class ListMessageFileItemNode: ListMessageNode {
updatedStatusSignal = currentUpdatedStatusSignal updatedStatusSignal = currentUpdatedStatusSignal
|> map { status in |> map { status in
switch status.mediaStatus { 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) return FileMediaResourceStatus(mediaStatus: .fetchStatus(.Local), fetchStatus: status.fetchStatus)
case .playbackStatus: }
return status case .playbackStatus:
return status
} }
} }
} }
} }
if isVoice { 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 { } 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) updatedStatusSignal = messageImageMediaResourceStatus(context: item.context, image: image, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult || item.isDownloadList)
@ -890,7 +894,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
} }
transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: leftInset + leftOffset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset - leftOffset, height: UIScreenPixel))) transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: leftInset + leftOffset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset - leftOffset, height: UIScreenPixel)))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel - nodeLayout.insets.top), size: CGSize(width: params.width, height: nodeLayout.contentSize.height + UIScreenPixel)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: nodeLayout.contentSize.height + UIScreenPixel))
if let backgroundNode = strongSelf.backgroundNode { if let backgroundNode = strongSelf.backgroundNode {
backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top), size: CGSize(width: params.width, height: nodeLayout.contentSize.height)) backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top), size: CGSize(width: params.width, height: nodeLayout.contentSize.height))
@ -1098,27 +1102,32 @@ public final class ListMessageFileItemNode: ListMessageNode {
if !isAudio && !isInstantVideo { if !isAudio && !isInstantVideo {
self.updateProgressFrame(size: contentSize, leftInset: layoutParams.leftInset, rightInset: layoutParams.rightInset, transition: .immediate) self.updateProgressFrame(size: contentSize, leftInset: layoutParams.leftInset, rightInset: layoutParams.rightInset, transition: .immediate)
} else { } else {
if item.isDownloadList {
self.updateProgressFrame(size: contentSize, leftInset: layoutParams.leftInset, rightInset: layoutParams.rightInset, transition: .immediate)
}
switch status { switch status {
case let .fetchStatus(fetchStatus): case let .fetchStatus(fetchStatus):
switch fetchStatus { switch fetchStatus {
case .Fetching: case let .Fetching(_, progress):
break if item.isDownloadList {
case .Local: iconStatusState = .progress(value: CGFloat(progress), cancelEnabled: true, appearance: nil)
if isAudio || isInstantVideo {
iconStatusState = .play
}
case .Remote, .Paused:
if isAudio || isInstantVideo {
iconStatusState = .play
}
} }
case let .playbackStatus(playbackStatus): case .Local:
switch playbackStatus { if isAudio || isInstantVideo {
case .playing:
iconStatusState = .pause
case .paused:
iconStatusState = .play 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 self.iconStatusNode.backgroundNodeColor = iconStatusBackgroundColor
@ -1196,17 +1205,22 @@ public final class ListMessageFileItemNode: ListMessageNode {
break break
case let .fetchStatus(fetchStatus): case let .fetchStatus(fetchStatus):
maybeFetchStatus = fetchStatus maybeFetchStatus = fetchStatus
switch fetchStatus { }
case .Fetching(_, let progress), .Paused(let progress):
if let file = self.currentMedia as? TelegramMediaFile, let size = file.size { if item.isDownloadList, let fetchStatus = self.fetchStatus {
downloadingString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData))) / \(dataSizeString(size, forceDecimal: true, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData)))" maybeFetchStatus = fetchStatus
} }
descriptionOffset = 14.0
case .Remote: switch maybeFetchStatus {
descriptionOffset = 14.0 case .Fetching(_, let progress), .Paused(let progress):
case .Local: if let file = self.currentMedia as? TelegramMediaFile, let size = file.size {
break 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 { switch maybeFetchStatus {
@ -1269,7 +1283,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
} else { } else {
if let linearProgressNode = self.linearProgressNode { if let linearProgressNode = self.linearProgressNode {
self.linearProgressNode = nil 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() linearProgressNode?.removeFromSupernode()
}) })
} }
@ -1525,9 +1539,11 @@ private final class DownloadIconNode: ASImageNode {
} }
func updateTheme(theme: PresentationTheme) { func updateTheme(theme: PresentationTheme) {
self.image = PresentationResourcesChat.sharedMediaFileDownloadStartIcon(theme, generate: { if self.image != nil {
return generateDownloadIcon(color: theme.list.itemAccentColor) self.image = PresentationResourcesChat.sharedMediaFileDownloadStartIcon(theme, generate: {
}) return generateDownloadIcon(color: theme.list.itemAccentColor)
})
}
self.customColor = theme.list.itemAccentColor self.customColor = theme.list.itemAccentColor
self.animationNode?.customColor = self.customColor self.animationNode?.customColor = self.customColor
} }

View File

@ -337,7 +337,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab
self.interaction?.openSearch() self.interaction?.openSearch()
} }
public func prepareForReuse() { public func resetForReuse() {
self.interaction?.updateMapMode(.map) self.interaction?.updateMapMode(.map)
self.interaction?.dismissSearch() self.interaction?.dismissSearch()
self.scrollToTop?() self.scrollToTop?()

View File

@ -127,7 +127,7 @@ final class MediaPickerGridItemNode: GridItemNode {
self.checkNode = checkNode self.checkNode = checkNode
self.setNeedsLayout() self.setNeedsLayout()
} }
if let asset = self.asset, let interaction = self.interaction, let selectionState = interaction.selectionState { if let asset = self.asset, let interaction = self.interaction, let selectionState = interaction.selectionState {
let selected = selectionState.isIdentifierSelected(asset.localIdentifier) let selected = selectionState.isIdentifierSelected(asset.localIdentifier)
if let legacyAsset = TGMediaAsset(phAsset: asset) { if let legacyAsset = TGMediaAsset(phAsset: asset) {
@ -177,7 +177,7 @@ final class MediaPickerGridItemNode: GridItemNode {
} }
}, error: { _ in }, error: { _ in
}, completed: nil)! }, completed: nil)!
return ActionDisposable { return ActionDisposable {
disposable.dispose() disposable.dispose()
} }
@ -185,9 +185,9 @@ final class MediaPickerGridItemNode: GridItemNode {
return EmptyDisposable return EmptyDisposable
} }
} }
let scale = min(2.0, UIScreenScale) let scale = min(2.0, UIScreenScale)
let targetSize = CGSize(width: 140.0 * scale, height: 140.0 * scale) let targetSize = CGSize(width: 128.0 * scale, height: 128.0 * scale)
let originalSignal = assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false) let originalSignal = assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false)
let imageSignal: Signal<UIImage?, NoError> = editedSignal let imageSignal: Signal<UIImage?, NoError> = editedSignal
|> mapToSignal { result in |> mapToSignal { result in

View File

@ -58,7 +58,7 @@ final class MediaPickerManageNode: ASDisplayNode {
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: theme.list.freeTextColor, paragraphAlignment: .left) self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: theme.list.freeTextColor, paragraphAlignment: .left)
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 16.0 - buttonWidth - 26.0, height: layout.size.height)) let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 16.0 - buttonWidth - 26.0, height: layout.size.height))
let panelHeight = max(64.0, textSize.height + 10.0) let panelHeight = max(64.0, textSize.height + 24.0)
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + 16.0, y: floorToScreenPixels((panelHeight - textSize.height) / 2.0) - 5.0), size: textSize)) transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + 16.0, y: floorToScreenPixels((panelHeight - textSize.height) / 2.0) - 5.0), size: textSize))
if themeUpdated { if themeUpdated {

View File

@ -8,6 +8,7 @@ import SwiftSignalKit
import AccountContext import AccountContext
import TelegramPresentationData import TelegramPresentationData
import TelegramUIPreferences import TelegramUIPreferences
import TelegramStringFormatting
import MergeLists import MergeLists
import Photos import Photos
import PhotosUI import PhotosUI
@ -75,8 +76,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
private var presentationData: PresentationData private var presentationData: PresentationData
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
fileprivate var interaction: MediaPickerInteraction?
private let peer: EnginePeer? private let peer: EnginePeer?
private let chatLocation: ChatLocation? private let chatLocation: ChatLocation?
private let bannedSendMedia: (Int32, Bool)?
private let titleView: MediaPickerTitleView private let titleView: MediaPickerTitleView
private let moreButtonNode: MediaPickerMoreButtonNode private let moreButtonNode: MediaPickerMoreButtonNode
@ -102,19 +106,19 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
enum State { enum State {
case noAccess(cameraAccess: AVAuthorizationStatus?) case noAccess(cameraAccess: AVAuthorizationStatus?)
case assets(fetchResult: PHFetchResult<PHAsset>?, mediaAccess: PHAuthorizationStatus, cameraAccess: AVAuthorizationStatus?) case assets(fetchResult: PHFetchResult<PHAsset>?, preload: Bool, mediaAccess: PHAuthorizationStatus, cameraAccess: AVAuthorizationStatus?)
} }
private weak var controller: MediaPickerScreen? private weak var controller: MediaPickerScreen?
fileprivate var interaction: MediaPickerInteraction?
private var presentationData: PresentationData private var presentationData: PresentationData
private let mediaAssetsContext: MediaAssetsContext private let mediaAssetsContext: MediaAssetsContext
private let backgroundNode: NavigationBackgroundNode private let backgroundNode: NavigationBackgroundNode
private let gridNode: GridNode private let gridNode: GridNode
private var cameraView: TGAttachmentCameraView? fileprivate var cameraView: TGAttachmentCameraView?
private var placeholderNode: MediaPickerPlaceholderNode? private var placeholderNode: MediaPickerPlaceholderNode?
private var manageNode: MediaPickerManageNode? private var manageNode: MediaPickerManageNode?
private var bannedTextNode: ImmediateTextNode?
private var selectionNode: MediaPickerSelectedListNode? private var selectionNode: MediaPickerSelectedListNode?
@ -123,6 +127,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
private var enqueuedTransactions: [MediaPickerGridTransaction] = [] private var enqueuedTransactions: [MediaPickerGridTransaction] = []
private var state: State? private var state: State?
private var preloadPromise = ValuePromise<Bool>(true)
private var itemsDisposable: Disposable? private var itemsDisposable: Disposable?
private var selectionChangedDisposable: Disposable? private var selectionChangedDisposable: Disposable?
private var itemsDimensionsUpdatedDisposable: Disposable? private var itemsDimensionsUpdatedDisposable: Disposable?
@ -131,7 +137,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
private let hiddenMediaId = Promise<String?>(nil) private let hiddenMediaId = Promise<String?>(nil)
private var didSetReady = false private var didSetReady = false
private let _ready = Promise<Bool>() private let _ready = Promise<Bool>(true)
var ready: Promise<Bool> { var ready: Promise<Bool> {
return self._ready return self._ready
} }
@ -154,45 +160,18 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.addSubnode(self.backgroundNode) self.addSubnode(self.backgroundNode)
self.addSubnode(self.gridNode) self.addSubnode(self.gridNode)
self.interaction = MediaPickerInteraction(openMedia: { [weak self] fetchResult, index, immediateThumbnail in let preloadPromise = self.preloadPromise
self?.openMedia(fetchResult: fetchResult, index: index, immediateThumbnail: immediateThumbnail)
}, openSelectedMedia: { [weak self] item, immediateThumbnail in
self?.openSelectedMedia(item: item, immediateThumbnail: immediateThumbnail)
}, toggleSelection: { [weak self] item, value in
if let strongSelf = self {
strongSelf.interaction?.selectionState?.setItem(item, selected: value)
}
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated in
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState {
if let currentItem = currentItem {
selectionState.setItem(currentItem, selected: true)
}
strongSelf.send(silently: silently, scheduleTime: scheduleTime, animated: animated)
}
}, schedule: { [weak self] in
if let strongSelf = self {
strongSelf.controller?.presentSchedulePicker(false, { [weak self] time in
self?.interaction?.sendSelected(nil, false, time, true)
})
}
}, dismissInput: { [weak self] in
if let strongSelf = self {
strongSelf.dismissInput()
}
}, selectionState: TGMediaSelectionContext(), editingState: TGMediaEditingContext())
self.interaction?.selectionState?.grouping = true
let updatedState = combineLatest(mediaAssetsContext.mediaAccess(), mediaAssetsContext.cameraAccess()) let updatedState = combineLatest(mediaAssetsContext.mediaAccess(), mediaAssetsContext.cameraAccess())
|> mapToSignal { mediaAccess, cameraAccess -> Signal<State, NoError> in |> mapToSignal { mediaAccess, cameraAccess -> Signal<State, NoError> in
if case .notDetermined = mediaAccess { if case .notDetermined = mediaAccess {
return .single(.assets(fetchResult: nil, mediaAccess: mediaAccess, cameraAccess: cameraAccess)) return .single(.assets(fetchResult: nil, preload: false, mediaAccess: mediaAccess, cameraAccess: cameraAccess))
} else if [.restricted, .denied].contains(mediaAccess) { } else if [.restricted, .denied].contains(mediaAccess) {
return .single(.noAccess(cameraAccess: cameraAccess)) return .single(.noAccess(cameraAccess: cameraAccess))
} else { } else {
return mediaAssetsContext.recentAssets() return combineLatest(mediaAssetsContext.recentAssets(), preloadPromise.get())
|> map { fetchResult in |> map { fetchResult, preload in
return .assets(fetchResult: fetchResult, mediaAccess: mediaAccess, cameraAccess: cameraAccess) return .assets(fetchResult: fetchResult, preload: preload, mediaAccess: mediaAccess, cameraAccess: cameraAccess)
} }
} }
} }
@ -212,7 +191,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.hiddenMediaDisposable = (self.hiddenMediaId.get() self.hiddenMediaDisposable = (self.hiddenMediaId.get()
|> deliverOnMainQueue).start(next: { [weak self] id in |> deliverOnMainQueue).start(next: { [weak self] id in
if let strongSelf = self { if let strongSelf = self {
strongSelf.interaction?.hiddenMediaId = id strongSelf.controller?.interaction?.hiddenMediaId = id
strongSelf.gridNode.forEachItemNode { itemNode in strongSelf.gridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? MediaPickerGridItemNode { if let itemNode = itemNode as? MediaPickerGridItemNode {
@ -224,7 +203,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
} }
}) })
if let selectionState = self.interaction?.selectionState { if let selectionState = self.controller?.interaction?.selectionState {
func selectionChangedSignal(selectionState: TGMediaSelectionContext) -> Signal<Void, NoError> { func selectionChangedSignal(selectionState: TGMediaSelectionContext) -> Signal<Void, NoError> {
return Signal { subscriber in return Signal { subscriber in
let disposable = selectionState.selectionChangedSignal()?.start(next: { next in let disposable = selectionState.selectionChangedSignal()?.start(next: { next in
@ -244,7 +223,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}) })
} }
if let editingState = self.interaction?.editingState { if let editingState = self.controller?.interaction?.editingState {
func itemsDimensionsUpdatedSignal(editingState: TGMediaEditingContext) -> Signal<Void, NoError> { func itemsDimensionsUpdatedSignal(editingState: TGMediaEditingContext) -> Signal<Void, NoError> {
return Signal { subscriber in return Signal { subscriber in
let disposable = editingState.cropAdjustmentsUpdatedSignal()?.start(next: { next in let disposable = editingState.cropAdjustmentsUpdatedSignal()?.start(next: { next in
@ -290,11 +269,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
cameraView.startPreview() cameraView.startPreview()
self.gridNode.scrollView.addSubview(cameraView) self.gridNode.scrollView.addSubview(cameraView)
// self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate) // self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
} }
private func dismissInput() { fileprivate func dismissInput() {
self.view.window?.endEditing(true) self.view.window?.endEditing(true)
} }
@ -302,7 +281,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
private var requestedCameraAccess = false private var requestedCameraAccess = false
private func updateState(_ state: State) { private func updateState(_ state: State) {
guard let interaction = self.interaction, let controller = self.controller else { guard let controller = self.controller, let interaction = controller.interaction else {
return return
} }
@ -325,14 +304,17 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.requestedCameraAccess = true self.requestedCameraAccess = true
self.mediaAssetsContext.requestCameraAccess() self.mediaAssetsContext.requestCameraAccess()
} }
case let .assets(fetchResult, mediaAccess, cameraAccess): case let .assets(fetchResult, preload, mediaAccess, cameraAccess):
if let fetchResult = fetchResult { if let fetchResult = fetchResult {
for i in 0 ..< fetchResult.count { let totalCount = fetchResult.count
entries.append(MediaPickerGridEntry(stableId: stableId, content: .asset(fetchResult, fetchResult.count - i - 1))) let count = preload ? min(10, totalCount) : totalCount
for i in 0 ..< count {
entries.append(MediaPickerGridEntry(stableId: stableId, content: .asset(fetchResult, totalCount - i - 1)))
stableId += 1 stableId += 1
} }
if case let .assets(previousFetchResult, _, previousCameraAccess) = previousState, previousFetchResult == nil || previousCameraAccess != cameraAccess { if case let .assets(previousFetchResult, _, _, previousCameraAccess) = previousState, previousFetchResult == nil || previousCameraAccess != cameraAccess {
updateLayout = true updateLayout = true
} }
@ -365,7 +347,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
} }
self.selectionNode?.updateSelectionState() self.selectionNode?.updateSelectionState()
let count = Int32(self.interaction?.selectionState?.count() ?? 0) let count = Int32(self.controller?.interaction?.selectionState?.count() ?? 0)
self.controller?.updateSelectionState(count: count) self.controller?.updateSelectionState(count: count)
if let (layout, navigationBarHeight) = self.validLayout { if let (layout, navigationBarHeight) = self.validLayout {
@ -387,7 +369,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
let selectionNode = MediaPickerSelectedListNode(context: controller.context) let selectionNode = MediaPickerSelectedListNode(context: controller.context)
selectionNode.alpha = 0.0 selectionNode.alpha = 0.0
selectionNode.isUserInteractionEnabled = false selectionNode.isUserInteractionEnabled = false
selectionNode.interaction = self.interaction selectionNode.interaction = self.controller?.interaction
self.insertSubnode(selectionNode, aboveSubnode: self.gridNode) self.insertSubnode(selectionNode, aboveSubnode: self.gridNode)
self.selectionNode = selectionNode self.selectionNode = selectionNode
@ -405,8 +387,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
} }
} }
private func openMedia(fetchResult: PHFetchResult<PHAsset>, index: Int, immediateThumbnail: UIImage?) { fileprivate func openMedia(fetchResult: PHFetchResult<PHAsset>, index: Int, immediateThumbnail: UIImage?) {
guard let controller = self.controller, let interaction = self.interaction, let (layout, _) = self.validLayout else { guard let controller = self.controller, let interaction = controller.interaction, let (layout, _) = self.validLayout else {
return return
} }
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {
@ -422,15 +404,15 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
return self?.transitionView(for: identifier) return self?.transitionView(for: identifier)
}, completed: { [weak self] result, silently, scheduleTime in }, completed: { [weak self] result, silently, scheduleTime in
if let strongSelf = self { if let strongSelf = self {
strongSelf.interaction?.sendSelected(result, silently, scheduleTime, false) strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false)
} }
}, presentStickers: controller.presentStickers, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in }, presentStickers: controller.presentStickers, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
self?.controller?.present(c, in: .window(.root), with: a) self?.controller?.present(c, in: .window(.root), with: a)
}) })
} }
private func openSelectedMedia(item: TGMediaSelectableItem, immediateThumbnail: UIImage?) { fileprivate func openSelectedMedia(item: TGMediaSelectableItem, immediateThumbnail: UIImage?) {
guard let controller = self.controller, let interaction = self.interaction, let (layout, _) = self.validLayout else { guard let controller = self.controller, let interaction = controller.interaction, let (layout, _) = self.validLayout else {
return return
} }
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {
@ -445,7 +427,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
return self?.transitionView(for: identifier) return self?.transitionView(for: identifier)
}, completed: { [weak self] result, silently, scheduleTime in }, completed: { [weak self] result, silently, scheduleTime in
if let strongSelf = self { if let strongSelf = self {
strongSelf.interaction?.sendSelected(result, silently, scheduleTime, false) strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false)
} }
}, presentStickers: controller.presentStickers, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in }, presentStickers: controller.presentStickers, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
self?.controller?.present(c, in: .window(.root), with: a) self?.controller?.present(c, in: .window(.root), with: a)
@ -453,7 +435,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
} }
fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool) { fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool) {
guard let signals = TGMediaAssetsController.resultSignals(for: self.interaction?.selectionState, editingContext: self.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: false, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: true) else { guard let signals = TGMediaAssetsController.resultSignals(for: self.controller?.interaction?.selectionState, editingContext: self.controller?.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: false, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: true) else {
return return
} }
self.controller?.legacyCompletion(signals, silently, scheduleTime) self.controller?.legacyCompletion(signals, silently, scheduleTime)
@ -549,11 +531,42 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
var cameraRect: CGRect? = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: CGSize(width: itemWidth, height: itemWidth * 2.0 + 1.0)) var cameraRect: CGRect? = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: CGSize(width: itemWidth, height: itemWidth * 2.0 + 1.0))
var manageHeight: CGFloat = 0.0 var manageHeight: CGFloat = 0.0
if case let .assets(_, mediaAccess, cameraAccess) = self.state { if case let .assets(_, _, mediaAccess, cameraAccess) = self.state {
if cameraAccess == nil { if cameraAccess == nil {
cameraRect = nil cameraRect = nil
} }
if case .notDetermined = mediaAccess { if let (untilDate, personal) = self.controller?.bannedSendMedia {
self.gridNode.alpha = 0.3
self.gridNode.isUserInteractionEnabled = false
let banDescription: String
if untilDate != 0 && untilDate != Int32.max {
banDescription = self.presentationData.strings.Conversation_RestrictedMediaTimed(stringForFullDate(timestamp: untilDate, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat)).string
} else if personal {
banDescription = self.presentationData.strings.Conversation_RestrictedMedia
} else {
banDescription = self.presentationData.strings.Conversation_DefaultRestrictedMedia
}
let bannedTextNode: ImmediateTextNode
if let current = self.bannedTextNode {
bannedTextNode = current
} else {
bannedTextNode = ImmediateTextNode()
bannedTextNode.maximumNumberOfLines = 0
bannedTextNode.textAlignment = .center
self.bannedTextNode = bannedTextNode
self.addSubnode(bannedTextNode)
}
bannedTextNode.attributedText = NSAttributedString(string: banDescription, font: Font.regular(15.0), textColor: self.presentationData.theme.list.freeTextColor, paragraphAlignment: .center)
let bannedTextSize = bannedTextNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 16.0, height: layout.size.height))
manageHeight = max(44.0, bannedTextSize.height + 20.0)
transition.updateFrame(node: bannedTextNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - bannedTextSize.width) / 2.0), y: insets.top + floorToScreenPixels((manageHeight - bannedTextSize.height) / 2.0) - 4.0), size: bannedTextSize))
} else if case .notDetermined = mediaAccess {
} else { } else {
if case .limited = mediaAccess { if case .limited = mediaAccess {
@ -604,7 +617,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
transition.updateFrame(node: self.backgroundNode, frame: bounds) transition.updateFrame(node: self.backgroundNode, frame: bounds)
self.backgroundNode.update(size: bounds.size, transition: transition) self.backgroundNode.update(size: bounds.size, transition: transition)
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: bounds.size, insets: gridInsets, scrollIndicatorInsets: nil, preloadSize: 200.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), fillWidth: true, lineSpacing: itemSpacing, itemSpacing: itemSpacing), cutout: cameraRect), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { [weak self] _ in self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: bounds.size, insets: gridInsets, scrollIndicatorInsets: nil, preloadSize: itemWidth, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), fillWidth: true, lineSpacing: itemSpacing, itemSpacing: itemSpacing), cutout: cameraRect), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { [weak self] _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -612,12 +625,16 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
strongSelf.didSetReady = true strongSelf.didSetReady = true
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {
strongSelf._ready.set(.single(true)) strongSelf._ready.set(.single(true))
Queue.mainQueue().after(0.5, {
strongSelf.preloadPromise.set(false)
})
} }
} }
}) })
if let selectionNode = self.selectionNode { if let selectionNode = self.selectionNode {
let selectedItems = self.interaction?.selectionState?.selectedItems() as? [TGMediaSelectableItem] ?? [] let selectedItems = self.controller?.interaction?.selectionState?.selectedItems() as? [TGMediaSelectableItem] ?? []
let updateSelectionNode = { let updateSelectionNode = {
selectionNode.updateLayout(size: bounds.size, insets: cleanGridInsets, items: selectedItems, grouped: self.controller?.groupedValue ?? true, theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper, bubbleCorners: self.presentationData.chatBubbleCorners, transition: transition) selectionNode.updateLayout(size: bounds.size, insets: cleanGridInsets, items: selectedItems, grouped: self.controller?.groupedValue ?? true, theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper, bubbleCorners: self.presentationData.chatBubbleCorners, transition: transition)
} }
@ -690,7 +707,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
private var groupedValue: Bool = true { private var groupedValue: Bool = true {
didSet { didSet {
self.groupedPromise.set(self.groupedValue) self.groupedPromise.set(self.groupedValue)
self.controllerNode.interaction?.selectionState?.grouping = self.groupedValue self.interaction?.selectionState?.grouping = self.groupedValue
if let layout = self.validLayout { if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: .immediate) self.containerLayoutUpdated(layout, transition: .immediate)
@ -699,11 +716,12 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
} }
private let groupedPromise = ValuePromise<Bool>(true) private let groupedPromise = ValuePromise<Bool>(true)
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?) { public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, bannedSendMedia: (Int32, Bool)?) {
self.context = context self.context = context
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
self.peer = peer self.peer = peer
self.chatLocation = chatLocation self.chatLocation = chatLocation
self.bannedSendMedia = bannedSendMedia
self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0) self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0)
self.titleView.title = self.presentationData.strings.Attachment_Gallery self.titleView.title = self.presentationData.strings.Attachment_Gallery
@ -763,6 +781,34 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
} }
} }
} }
self.interaction = MediaPickerInteraction(openMedia: { [weak self] fetchResult, index, immediateThumbnail in
self?.controllerNode.openMedia(fetchResult: fetchResult, index: index, immediateThumbnail: immediateThumbnail)
}, openSelectedMedia: { [weak self] item, immediateThumbnail in
self?.controllerNode.openSelectedMedia(item: item, immediateThumbnail: immediateThumbnail)
}, toggleSelection: { [weak self] item, value in
if let strongSelf = self {
strongSelf.interaction?.selectionState?.setItem(item, selected: value)
}
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated in
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState {
if let currentItem = currentItem {
selectionState.setItem(currentItem, selected: true)
}
strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated)
}
}, schedule: { [weak self] in
if let strongSelf = self {
strongSelf.presentSchedulePicker(false, { [weak self] time in
self?.interaction?.sendSelected(nil, false, time, true)
})
}
}, dismissInput: { [weak self] in
if let strongSelf = self {
strongSelf.controllerNode.dismissInput()
}
}, selectionState: TGMediaSelectionContext(), editingState: TGMediaEditingContext())
self.interaction?.selectionState?.grouping = true
} }
required init(coder aDecoder: NSCoder) { required init(coder aDecoder: NSCoder) {
@ -811,12 +857,18 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.dismiss() self.dismiss()
} }
public func prepareForReuse() { public func resetForReuse() {
if let webSearchController = self.webSearchController { if let webSearchController = self.webSearchController {
self.webSearchController = nil self.webSearchController = nil
webSearchController.dismiss() webSearchController.dismiss()
} }
self.scrollToTop?() self.scrollToTop?()
self.controllerNode.cameraView?.pausePreview()
}
public func prepareForReuse() {
self.controllerNode.cameraView?.resumePreview()
} }
@objc private func searchOrMorePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) { @objc private func searchOrMorePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) {
@ -881,7 +933,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
} }
public var mediaPickerContext: AttachmentMediaPickerContext? { public var mediaPickerContext: AttachmentMediaPickerContext? {
if let interaction = self.controllerNode.interaction { if let interaction = self.interaction {
return MediaPickerContext(interaction: interaction) return MediaPickerContext(interaction: interaction)
} else { } else {
return nil return nil

View File

@ -189,7 +189,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
} }
} }
final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate { final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDelegate {
private let context: AccountContext private let context: AccountContext
fileprivate let wallpaperBackgroundNode: WallpaperBackgroundNode fileprivate let wallpaperBackgroundNode: WallpaperBackgroundNode
@ -226,9 +226,11 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate {
} }
self.scrollNode.view.delegate = self self.scrollNode.view.delegate = self
self.scrollNode.view.panGestureRecognizer.cancelsTouchesInView = true
self.view.addGestureRecognizer(ReorderingGestureRecognizer(shouldBegin: { [weak self] point in self.view.addGestureRecognizer(ReorderingGestureRecognizer(shouldBegin: { [weak self] point in
if let strongSelf = self, !strongSelf.scrollNode.view.isTracking { if let strongSelf = self, !strongSelf.scrollNode.view.isDragging {
let point = strongSelf.view.convert(point, to: strongSelf.scrollNode.view)
for (_, itemNode) in strongSelf.itemNodes { for (_, itemNode) in strongSelf.itemNodes {
if itemNode.frame.contains(point) { if itemNode.frame.contains(point) {
return (true, true, itemNode) return (true, true, itemNode)
@ -242,20 +244,54 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate {
}, began: { [weak self] itemNode in }, began: { [weak self] itemNode in
self?.beginReordering(itemNode: itemNode) self?.beginReordering(itemNode: itemNode)
}, ended: { [weak self] point in }, ended: { [weak self] point in
self?.endReordering(point: point) if let strongSelf = self {
if var point = point {
point = strongSelf.view.convert(point, to: strongSelf.scrollNode.view)
strongSelf.endReordering(point: point)
} else {
strongSelf.endReordering(point: nil)
}
}
}, moved: { [weak self] offset in }, moved: { [weak self] offset in
self?.updateReordering(offset: offset) self?.updateReordering(offset: offset)
})) }))
Queue.mainQueue().after(0.1, {
self.updateAbsoluteRects()
})
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
} }
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.interaction?.dismissInput() self.interaction?.dismissInput()
} }
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.updateAbsoluteRects()
}
func scrollToTop(animated: Bool) { func scrollToTop(animated: Bool) {
self.scrollNode.view.setContentOffset(CGPoint(), animated: animated) self.scrollNode.view.setContentOffset(CGPoint(), animated: animated)
} }
func updateAbsoluteRects() {
guard let messageNodes = self.messageNodes, let (size, _, _, _, _, _, _) = self.validLayout else {
return
}
for itemNode in messageNodes {
var absoluteRect = itemNode.frame
if let supernode = self.supernode {
absoluteRect = supernode.convert(itemNode.bounds, from: itemNode)
}
absoluteRect.origin.y = size.height - absoluteRect.origin.y - absoluteRect.size.height
itemNode.updateAbsoluteRect(absoluteRect, within: self.bounds.size)
}
}
private func beginReordering(itemNode: MediaPickerSelectedItemNode) { private func beginReordering(itemNode: MediaPickerSelectedItemNode) {
self.isReordering = true self.isReordering = true
@ -265,7 +301,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate {
let reorderNode = ReorderingItemNode(itemNode: itemNode, initialLocation: itemNode.frame.origin) let reorderNode = ReorderingItemNode(itemNode: itemNode, initialLocation: itemNode.frame.origin)
self.reorderNode = reorderNode self.reorderNode = reorderNode
self.addSubnode(reorderNode) self.scrollNode.addSubnode(reorderNode)
itemNode.isHidden = true itemNode.isHidden = true
@ -549,6 +585,8 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate {
} }
} }
self.updateAbsoluteRects()
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight) self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
} }

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 labelLayout = TextNode.asyncLayout(self.labelNode)
let currentForegroundColor = self.foregroundColor let currentForegroundColor = self.foregroundColor
let currentIconColor = self.iconColor 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())) let (labelLayoutResult, labelApply) = labelLayout(TextNodeLayoutArguments(attributedString: placeholderString, backgroundColor: .clear, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: constrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var updatedColor: UIColor? var updatedColor: UIColor?

View File

@ -11,6 +11,7 @@ public let navigationBarSearchContentHeight: CGFloat = 54.0
public class NavigationBarSearchContentNode: NavigationBarContentNode { public class NavigationBarSearchContentNode: NavigationBarContentNode {
public var theme: PresentationTheme? public var theme: PresentationTheme?
public var placeholder: String public var placeholder: String
public var compactPlaceholder: String
public let placeholderNode: SearchBarPlaceholderNode public let placeholderNode: SearchBarPlaceholderNode
public var placeholderHeight: CGFloat? public var placeholderHeight: CGFloat?
@ -20,9 +21,10 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode {
private var validLayout: (CGSize, CGFloat, CGFloat)? 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.theme = theme
self.placeholder = placeholder self.placeholder = placeholder
self.compactPlaceholder = compactPlaceholder ?? placeholder
self.placeholderNode = SearchBarPlaceholderNode(fieldStyle: .modern) self.placeholderNode = SearchBarPlaceholderNode(fieldStyle: .modern)
self.placeholderNode.labelNode.displaysAsynchronously = false self.placeholderNode.labelNode.displaysAsynchronously = false
@ -36,9 +38,10 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode {
self.placeholderNode.activate = activate 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.theme = theme
self.placeholder = placeholder self.placeholder = placeholder
self.compactPlaceholder = compactPlaceholder ?? placeholder
self.placeholderNode.accessibilityLabel = placeholder self.placeholderNode.accessibilityLabel = placeholder
if let disabledOverlay = self.disabledOverlay { if let disabledOverlay = self.disabledOverlay {
disabledOverlay.backgroundColor = theme.rootController.navigationBar.opaqueBackgroundColor.withAlphaComponent(0.5) 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 overscrollProgress = max(0.0, max(0.0, self.expansionProgress - 1.0 + fraction) / fraction - visibleProgress)
let searchBarNodeLayout = self.placeholderNode.asyncLayout() 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() searchBarApply()
let searchBarFrame = CGRect(origin: CGPoint(x: padding + leftInset, y: 8.0 + overscrollProgress * fieldHeight), size: CGSize(width: baseWidth, height: fieldHeight)) 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) 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 { guard let (layout, navigationBarHeight) = self.containerLayout else {
return return
} }
@ -231,7 +231,9 @@ public final class SearchDisplayController {
insertSubnode(self.searchBar, true) insertSubnode(self.searchBar, true)
self.searchBar.layout() self.searchBar.layout()
self.searchBar.activate() if focus {
self.searchBar.activate()
}
if let placeholder = placeholder { if let placeholder = placeholder {
self.searchBar.animateIn(from: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) self.searchBar.animateIn(from: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
if self.contentNode.hasDim { if self.contentNode.hasDim {

View File

@ -112,7 +112,8 @@ class NotificationSearchItemNode: ListViewItemNode {
let backgroundColor = item.theme.chatList.itemBackgroundColor 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()) let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 44.0), insets: UIEdgeInsets())

View File

@ -1511,12 +1511,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
switch joinCallResult.connectionMode { switch joinCallResult.connectionMode {
case .rtc: case .rtc:
strongSelf.currentConnectionMode = .rtc strongSelf.currentConnectionMode = .rtc
strongSelf.genericCallContext?.setConnectionMode(.rtc, keepBroadcastConnectedIfWasEnabled: false) strongSelf.genericCallContext?.setConnectionMode(.rtc, keepBroadcastConnectedIfWasEnabled: false, isUnifiedBroadcast: false)
strongSelf.genericCallContext?.setJoinResponse(payload: clientParams) strongSelf.genericCallContext?.setJoinResponse(payload: clientParams)
case let .broadcast(isExternalStream): case let .broadcast(isExternalStream):
strongSelf.currentConnectionMode = .broadcast strongSelf.currentConnectionMode = .broadcast
strongSelf.genericCallContext?.setAudioStreamData(audioStreamData: OngoingGroupCallContext.AudioStreamData(engine: strongSelf.accountContext.engine, callId: callInfo.id, accessHash: callInfo.accessHash, isExternalStream: isExternalStream)) strongSelf.genericCallContext?.setAudioStreamData(audioStreamData: OngoingGroupCallContext.AudioStreamData(engine: strongSelf.accountContext.engine, callId: callInfo.id, accessHash: callInfo.accessHash, isExternalStream: isExternalStream))
strongSelf.genericCallContext?.setConnectionMode(.broadcast, keepBroadcastConnectedIfWasEnabled: false) strongSelf.genericCallContext?.setConnectionMode(.broadcast, keepBroadcastConnectedIfWasEnabled: false, isUnifiedBroadcast: isExternalStream)
} }
strongSelf.updateSessionState(internalState: .established(info: joinCallResult.callInfo, connectionMode: joinCallResult.connectionMode, clientParams: clientParams, localSsrc: ssrc, initialState: joinCallResult.state), audioSessionControl: strongSelf.audioSessionControl) strongSelf.updateSessionState(internalState: .established(info: joinCallResult.callInfo, connectionMode: joinCallResult.connectionMode, clientParams: clientParams, localSsrc: ssrc, initialState: joinCallResult.state), audioSessionControl: strongSelf.audioSessionControl)
@ -1784,15 +1784,25 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
} }
let chatPeer = self.accountContext.account.postbox.peerView(id: self.peerId)
|> map { view -> Peer? in
if let peer = peerViewMainPeer(view) {
return peer
} else {
return nil
}
}
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(), self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
participantsContext.state, participantsContext.state,
participantsContext.activeSpeakers, participantsContext.activeSpeakers,
self.speakingParticipantsContext.get(), self.speakingParticipantsContext.get(),
adminIds, adminIds,
myPeer, myPeer,
chatPeer,
accountContext.account.postbox.peerView(id: peerId), accountContext.account.postbox.peerView(id: peerId),
self.isReconnectingAsSpeakerPromise.get() self.isReconnectingAsSpeakerPromise.get()
).start(next: { [weak self] state, activeSpeakers, speakingParticipants, adminIds, myPeerAndCachedData, view, isReconnectingAsSpeaker in ).start(next: { [weak self] state, activeSpeakers, speakingParticipants, adminIds, myPeerAndCachedData, chatPeer, view, isReconnectingAsSpeaker in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -1874,6 +1884,30 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) }) participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) })
} }
} }
if let chatPeer = chatPeer, !participants.contains(where: { $0.peer.id == chatPeer.id }) {
participants.append(GroupCallParticipantsContext.Participant(
peer: chatPeer,
ssrc: 100,
videoDescription: GroupCallParticipantsContext.Participant.VideoDescription(
endpointId: "unified",
ssrcGroups: [],
audioSsrc: 100,
isPaused: false
),
presentationDescription: nil,
joinTimestamp: strongSelf.temporaryJoinTimestamp,
raiseHandRating: nil,
hasRaiseHand: false,
activityTimestamp: nil,
activityRank: nil,
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: false, mutedByYou: false),
volume: nil,
about: nil,
joinedVideo: false
))
participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) })
}
var otherParticipantsWithVideo = 0 var otherParticipantsWithVideo = 0
var videoWatchingParticipants = 0 var videoWatchingParticipants = 0
@ -2691,7 +2725,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
let clientParams = joinCallResult.jsonParams let clientParams = joinCallResult.jsonParams
screencastCallContext.setConnectionMode(.rtc, keepBroadcastConnectedIfWasEnabled: false) screencastCallContext.setConnectionMode(.rtc, keepBroadcastConnectedIfWasEnabled: false, isUnifiedBroadcast: false)
screencastCallContext.setJoinResponse(payload: clientParams) screencastCallContext.setJoinResponse(payload: clientParams)
}, error: { error in }, error: { error in
guard let _ = self else { guard let _ = self else {
@ -2885,7 +2919,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if !self.didInitializeConnectionMode || self.currentConnectionMode != .none { if !self.didInitializeConnectionMode || self.currentConnectionMode != .none {
self.didInitializeConnectionMode = true self.didInitializeConnectionMode = true
self.currentConnectionMode = .none self.currentConnectionMode = .none
self.genericCallContext?.setConnectionMode(.none, keepBroadcastConnectedIfWasEnabled: movingFromBroadcastToRtc) self.genericCallContext?.setConnectionMode(.none, keepBroadcastConnectedIfWasEnabled: movingFromBroadcastToRtc, isUnifiedBroadcast: false)
} }
self.internalState = .requesting self.internalState = .requesting

View File

@ -190,6 +190,13 @@ public func addRecentDownloadItem(postbox: Postbox, item: RecentDownloadItem) ->
|> ignoreValues |> 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> { public func markRecentDownloadItemsAsSeen(postbox: Postbox, items: [(messageId: MessageId, resourceId: String)]) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Void in return postbox.transaction { transaction -> Void in
var unseenIds: [(messageId: MessageId, resourceId: String)] = [] var unseenIds: [(messageId: MessageId, resourceId: String)] = []

View File

@ -863,6 +863,13 @@ public final class GroupCallParticipantsContext {
public var ssrcGroups: [SsrcGroup] public var ssrcGroups: [SsrcGroup]
public var audioSsrc: UInt32? public var audioSsrc: UInt32?
public var isPaused: Bool public var isPaused: Bool
public init(endpointId: String, ssrcGroups: [SsrcGroup], audioSsrc: UInt32?, isPaused: Bool) {
self.endpointId = endpointId
self.ssrcGroups = ssrcGroups
self.audioSsrc = audioSsrc
self.isPaused = isPaused
}
} }
public var peer: Peer public var peer: Peer

View File

@ -164,7 +164,7 @@ private class AttachmentFileControllerImpl: ItemListController, AttachmentContai
public var requestAttachmentMenuExpansion: () -> Void = {} public var requestAttachmentMenuExpansion: () -> Void = {}
var prepareForReuseImpl: () -> Void = {} var prepareForReuseImpl: () -> Void = {}
public func prepareForReuse() { public func resetForReuse() {
self.prepareForReuseImpl() self.prepareForReuseImpl()
self.scrollToTop?() self.scrollToTop?()
} }

View File

@ -9121,6 +9121,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
var layout = layout
if let _ = self.attachmentController {
layout = layout.withUpdatedInputHeight(nil)
}
var navigationBarTransition = transition var navigationBarTransition = transition
self.chatDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop, completion in self.chatDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop, completion in
self.chatDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop, completion: completion) self.chatDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop, completion: completion)
@ -10320,18 +10325,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
self.chatDisplayNode.dismissInput() self.chatDisplayNode.dismissInput()
let currentFilesController = Atomic<AttachmentContainable?>(value: nil) var bannedSendMedia: (Int32, Bool)?
let currentLocationController = Atomic<AttachmentContainable?>(value: nil)
var canSendPolls = true var canSendPolls = true
if peer is TelegramUser || peer is TelegramSecretChat { if peer is TelegramUser || peer is TelegramSecretChat {
canSendPolls = false canSendPolls = false
} else if let channel = peer as? TelegramChannel { } else if let channel = peer as? TelegramChannel {
if let value = channel.hasBannedPermission(.banSendMedia) {
bannedSendMedia = value
}
if channel.hasBannedPermission(.banSendPolls) != nil { if channel.hasBannedPermission(.banSendPolls) != nil {
canSendPolls = false canSendPolls = false
} }
} else if let group = peer as? TelegramGroup { } else if let group = peer as? TelegramGroup {
if group.hasBannedPermission(.banSendMedia) {
bannedSendMedia = (Int32.max, false)
}
if group.hasBannedPermission(.banSendPolls) { if group.hasBannedPermission(.banSendPolls) {
canSendPolls = false canSendPolls = false
} }
@ -10344,6 +10353,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText
let currentMediaController = Atomic<AttachmentContainable?>(value: nil)
let currentFilesController = Atomic<AttachmentContainable?>(value: nil)
let currentLocationController = Atomic<AttachmentContainable?>(value: nil)
let attachmentController = AttachmentController(context: self.context, updatedPresentationData: self.updatedPresentationData, buttons: availableTabs) let attachmentController = AttachmentController(context: self.context, updatedPresentationData: self.updatedPresentationData, buttons: availableTabs)
attachmentController.requestController = { [weak self, weak attachmentController] type, completion in attachmentController.requestController = { [weak self, weak attachmentController] type, completion in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -10351,7 +10364,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
switch type { switch type {
case .gallery: case .gallery:
strongSelf.presentMediaPicker(present: { controller, mediaPickerContext in strongSelf.controllerNavigationDisposable.set(nil)
let existingController = currentMediaController.with { $0 }
if let controller = existingController {
controller.prepareForReuse()
completion(controller, nil)
return
}
strongSelf.presentMediaPicker(bannedSendMedia: bannedSendMedia, present: { controller, mediaPickerContext in
let _ = currentMediaController.swap(controller)
completion(controller, mediaPickerContext) completion(controller, mediaPickerContext)
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in }, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
attachmentController?.mediaPickerContext = mediaPickerContext attachmentController?.mediaPickerContext = mediaPickerContext
@ -10361,7 +10382,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime) self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime)
}) })
strongSelf.controllerNavigationDisposable.set(nil)
case .file: case .file:
strongSelf.controllerNavigationDisposable.set(nil) strongSelf.controllerNavigationDisposable.set(nil)
let existingController = currentFilesController.with { $0 } let existingController = currentFilesController.with { $0 }
@ -10978,11 +10998,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.present(actionSheet, in: .window(.root)) self.present(actionSheet, in: .window(.root))
} }
private func presentMediaPicker(present: @escaping (AttachmentContainable, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?) -> Void) { private func presentMediaPicker(bannedSendMedia: (Int32, Bool)?, present: @escaping (AttachmentContainable, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?) -> Void) {
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
return return
} }
let controller = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), chatLocation: self.chatLocation) let controller = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), chatLocation: self.chatLocation, bannedSendMedia: bannedSendMedia)
let mediaPickerContext = controller.mediaPickerContext let mediaPickerContext = controller.mediaPickerContext
controller.openCamera = { [weak self] cameraView in controller.openCamera = { [weak self] cameraView in
self?.openCamera(cameraView: cameraView) self?.openCamera(cameraView: cameraView)

View File

@ -20,7 +20,7 @@ import ContextUI
import ChatPresentationInterfaceState import ChatPresentationInterfaceState
private struct FetchControls { private struct FetchControls {
let fetch: () -> Void let fetch: (Bool) -> Void
let cancel: () -> Void let cancel: () -> Void
} }
@ -228,7 +228,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
} }
case .Remote, .Paused: case .Remote, .Paused:
if let fetch = self.fetchControls.with({ return $0?.fetch }) { if let fetch = self.fetchControls.with({ return $0?.fetch }) {
fetch() fetch(true)
} }
case .Local: case .Local:
break break
@ -251,7 +251,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
} }
case .Remote, .Paused: case .Remote, .Paused:
if let fetch = self.fetchControls.with({ return $0?.fetch }) { if let fetch = self.fetchControls.with({ return $0?.fetch }) {
fetch() fetch(true)
} }
case .Local: case .Local:
self.activateLocalContent() 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) 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 { 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: { }, cancel: {
messageMediaFileCancelInteractiveFetch(context: arguments.context, messageId: arguments.message.id, file: arguments.file) 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 |> map { resourceStatus, actualFetchStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in
return (resourceStatus, actualFetchStatus) 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 { } else {
updatedStatusSignal = messageFileMediaResourceStatus(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions) updatedStatusSignal = messageFileMediaResourceStatus(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions)
|> map { resourceStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in |> map { resourceStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in
return (resourceStatus, nil) 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? var consumableContentIcon: UIImage?
@ -765,7 +765,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
if let updatedFetchControls = updatedFetchControls { if let updatedFetchControls = updatedFetchControls {
let _ = strongSelf.fetchControls.swap(updatedFetchControls) let _ = strongSelf.fetchControls.swap(updatedFetchControls)
if arguments.automaticDownload { 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) 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 playbackStatusNode.status = status
self.durationNode?.status = status self.durationNode?.status = status
|> map(Optional.init) |> map(Optional.init)

View File

@ -58,6 +58,8 @@ private final class NetworkBroadcastPartSource: BroadcastPartSource {
return self.engine.calls.requestStreamState(callId: self.callId, accessHash: self.accessHash).start(next: { result in return self.engine.calls.requestStreamState(callId: self.callId, accessHash: self.accessHash).start(next: { result in
if let channel = result?.channels.first { if let channel = result?.channels.first {
completion(channel.latestTimestamp) completion(channel.latestTimestamp)
} else {
completion(0)
} }
}) })
} else { } else {
@ -124,6 +126,11 @@ private final class NetworkBroadcastPartSource: BroadcastPartSource {
let part: OngoingGroupCallBroadcastPart let part: OngoingGroupCallBroadcastPart
switch result.status { switch result.status {
case let .data(dataValue): case let .data(dataValue):
/*#if DEBUG
let tempFile = EngineTempBox.shared.tempFile(fileName: "part.mp4")
let _ = try? dataValue.write(to: URL(fileURLWithPath: tempFile.path))
print("Dump stream part: \(tempFile.path)")
#endif*/
part = OngoingGroupCallBroadcastPart(timestampMilliseconds: timestampIdMilliseconds, responseTimestamp: result.responseTimestamp, status: .success, oggData: dataValue) part = OngoingGroupCallBroadcastPart(timestampMilliseconds: timestampIdMilliseconds, responseTimestamp: result.responseTimestamp, status: .success, oggData: dataValue)
case .notReady: case .notReady:
part = OngoingGroupCallBroadcastPart(timestampMilliseconds: timestampIdMilliseconds, responseTimestamp: result.responseTimestamp, status: .notReady, oggData: Data()) part = OngoingGroupCallBroadcastPart(timestampMilliseconds: timestampIdMilliseconds, responseTimestamp: result.responseTimestamp, status: .notReady, oggData: Data())
@ -606,7 +613,7 @@ public final class OngoingGroupCallContext {
self.context.stop() self.context.stop()
} }
func setConnectionMode(_ connectionMode: ConnectionMode, keepBroadcastConnectedIfWasEnabled: Bool) { func setConnectionMode(_ connectionMode: ConnectionMode, keepBroadcastConnectedIfWasEnabled: Bool, isUnifiedBroadcast: Bool) {
let mappedConnectionMode: OngoingCallConnectionMode let mappedConnectionMode: OngoingCallConnectionMode
switch connectionMode { switch connectionMode {
case .none: case .none:
@ -616,7 +623,7 @@ public final class OngoingGroupCallContext {
case .broadcast: case .broadcast:
mappedConnectionMode = .broadcast mappedConnectionMode = .broadcast
} }
self.context.setConnectionMode(mappedConnectionMode, keepBroadcastConnectedIfWasEnabled: keepBroadcastConnectedIfWasEnabled) self.context.setConnectionMode(mappedConnectionMode, keepBroadcastConnectedIfWasEnabled: keepBroadcastConnectedIfWasEnabled, isUnifiedBroadcast: isUnifiedBroadcast)
if (mappedConnectionMode != .rtc) { if (mappedConnectionMode != .rtc) {
self.joinPayload.set(.never()) self.joinPayload.set(.never())
@ -900,9 +907,9 @@ public final class OngoingGroupCallContext {
}) })
} }
public func setConnectionMode(_ connectionMode: ConnectionMode, keepBroadcastConnectedIfWasEnabled: Bool) { public func setConnectionMode(_ connectionMode: ConnectionMode, keepBroadcastConnectedIfWasEnabled: Bool, isUnifiedBroadcast: Bool) {
self.impl.with { impl in self.impl.with { impl in
impl.setConnectionMode(connectionMode, keepBroadcastConnectedIfWasEnabled: keepBroadcastConnectedIfWasEnabled) impl.setConnectionMode(connectionMode, keepBroadcastConnectedIfWasEnabled: keepBroadcastConnectedIfWasEnabled, isUnifiedBroadcast: isUnifiedBroadcast)
} }
} }

View File

@ -354,7 +354,7 @@ typedef NS_ENUM(int32_t, OngoingGroupCallRequestedVideoQuality) {
- (void)stop; - (void)stop;
- (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode keepBroadcastConnectedIfWasEnabled:(bool)keepBroadcastConnectedIfWasEnabled; - (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode keepBroadcastConnectedIfWasEnabled:(bool)keepBroadcastConnectedIfWasEnabled isUnifiedBroadcast:(bool)isUnifiedBroadcast;
- (void)emitJoinPayload:(void (^ _Nonnull)(NSString * _Nonnull, uint32_t))completion; - (void)emitJoinPayload:(void (^ _Nonnull)(NSString * _Nonnull, uint32_t))completion;
- (void)setJoinResponsePayload:(NSString * _Nonnull)payload; - (void)setJoinResponsePayload:(NSString * _Nonnull)payload;

View File

@ -1580,7 +1580,7 @@ private:
} }
} }
- (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode keepBroadcastConnectedIfWasEnabled:(bool)keepBroadcastConnectedIfWasEnabled { - (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode keepBroadcastConnectedIfWasEnabled:(bool)keepBroadcastConnectedIfWasEnabled isUnifiedBroadcast:(bool)isUnifiedBroadcast {
if (_instance) { if (_instance) {
tgcalls::GroupConnectionMode mappedConnectionMode; tgcalls::GroupConnectionMode mappedConnectionMode;
switch (connectionMode) { switch (connectionMode) {
@ -1601,7 +1601,7 @@ private:
break; break;
} }
} }
_instance->setConnectionMode(mappedConnectionMode, keepBroadcastConnectedIfWasEnabled); _instance->setConnectionMode(mappedConnectionMode, keepBroadcastConnectedIfWasEnabled, isUnifiedBroadcast);
} }
} }

@ -1 +1 @@
Subproject commit 20860ca29147b4faa4f0b75a0da58517d9d4856c Subproject commit 382d1b6756768021274cb1edfc1e144cfb101fb8

View File

@ -38,6 +38,7 @@ public enum UndoOverlayContent {
case mediaSaved(text: String) case mediaSaved(text: String)
case paymentSent(currencyValue: String, itemTitle: String) case paymentSent(currencyValue: String, itemTitle: String)
case inviteRequestSent(title: String, text: String) case inviteRequestSent(title: String, text: String)
case image(image: UIImage, text: String)
} }
public enum UndoOverlayAction { public enum UndoOverlayAction {

View File

@ -749,6 +749,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.textNode.maximumNumberOfLines = 2 self.textNode.maximumNumberOfLines = 2
displayUndo = false displayUndo = false
self.originalRemainingSeconds = 5 self.originalRemainingSeconds = 5
case let .image(image, text):
self.avatarNode = nil
self.iconNode = ASImageNode()
self.iconNode?.image = image
self.iconCheckNode = nil
self.animationNode = nil
self.animatedStickerNode = nil
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
displayUndo = true
self.originalRemainingSeconds = 5
} }
self.remainingSeconds = self.originalRemainingSeconds self.remainingSeconds = self.originalRemainingSeconds
@ -777,7 +787,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
switch content { switch content {
case .removedChat: case .removedChat:
self.panelWrapperNode.addSubnode(self.timerTextNode) self.panelWrapperNode.addSubnode(self.timerTextNode)
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .sticker, .copy, .mediaSaved, .paymentSent, .inviteRequestSent: case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .sticker, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent:
break break
case .dice: case .dice:
self.panelWrapperNode.clipsToBounds = true self.panelWrapperNode.clipsToBounds = true