Merge commit 'b83248d844d7d3fc178843f1f9cdc607004a5c68'

This commit is contained in:
Peter 2018-09-07 01:07:52 +03:00
commit b15ce8cc3b
25 changed files with 363 additions and 82 deletions

View File

@ -221,7 +221,7 @@ private func archivedStickerPacksControllerEntries(presentationData: Presentatio
return entries return entries
} }
public func archivedStickerPacksController(account: Account, archived: [ArchivedStickerPackItem]?) -> ViewController { public func archivedStickerPacksController(account: Account, archived: [ArchivedStickerPackItem]?, updatedPacks: @escaping([ArchivedStickerPackItem]?)->Void) -> ViewController {
let statePromise = ValuePromise(ArchivedStickerPacksControllerState(), ignoreRepeated: true) let statePromise = ValuePromise(ArchivedStickerPacksControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: ArchivedStickerPacksControllerState()) let stateValue = Atomic(value: ArchivedStickerPacksControllerState())
let updateState: ((ArchivedStickerPacksControllerState) -> ArchivedStickerPacksControllerState) -> Void = { f in let updateState: ((ArchivedStickerPacksControllerState) -> ArchivedStickerPacksControllerState) -> Void = { f in
@ -241,6 +241,11 @@ public func archivedStickerPacksController(account: Account, archived: [Archived
let stickerPacks = Promise<[ArchivedStickerPackItem]?>() let stickerPacks = Promise<[ArchivedStickerPackItem]?>()
stickerPacks.set(.single(archived) |> then(archivedStickerPacks(account: account) |> map(Optional.init))) stickerPacks.set(.single(archived) |> then(archivedStickerPacks(account: account) |> map(Optional.init)))
actionsDisposable.add(stickerPacks.get().start(next: { packs in
updatedPacks(packs)
}))
let installedStickerPacks = Promise<CombinedView>() let installedStickerPacks = Promise<CombinedView>()
installedStickerPacks.set(account.postbox.combinedView(keys: [.itemCollectionIds(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) installedStickerPacks.set(account.postbox.combinedView(keys: [.itemCollectionIds(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])]))

View File

@ -560,13 +560,16 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon
} }
})) }))
}, addAdmin: { }, addAdmin: {
presentControllerImpl?(ChannelMembersSearchController(account: account, peerId: peerId, mode: .promote, openPeer: { peer, participant in updateState { current in
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
if peer.id == account.peerId {
return presentControllerImpl?(ChannelMembersSearchController(account: account, peerId: peerId, mode: .promote, filters: [.exclude(current.temporaryAdmins.map({$0.peer.id}))], openPeer: { peer, participant in
} let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
if let participant = participant { if peer.id == account.peerId {
switch participant.participant { return
}
if let participant = participant {
switch participant.participant {
case .creator: case .creator:
return return
case let .member(_, _, _, banInfo): case let .member(_, _, _, banInfo):
@ -574,11 +577,15 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Channel_Members_AddAdminErrorBlacklisted, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Channel_Members_AddAdminErrorBlacklisted, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
return return
} }
}
} }
} presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: peer.id, initialParticipant: participant?.participant, updated: { _ in
presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: peer.id, initialParticipant: participant?.participant, updated: { _ in }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
return current
}
}, openAdmin: { participant in }, openAdmin: { participant in
if case let .member(adminId, _, _, _) = participant { if case let .member(adminId, _, _, _) = participant {
presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in
@ -619,6 +626,8 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon
}) })
} }
if !state.editing { if !state.editing {
if rightNavigationButton == nil { if rightNavigationButton == nil {
rightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: { rightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: {
@ -634,6 +643,8 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon
}) })
} }
} }
_ = stateValue.swap(state.withUpdatedTemporaryAdmins(admins))
} }
let previous = previousPeers let previous = previousPeers

View File

@ -142,7 +142,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings)> private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings)>
init(account: Account, peerId: PeerId, mode: ChannelMembersSearchMode, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) { init(account: Account, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) {
self.account = account self.account = account
self.openPeer = openPeer self.openPeer = openPeer
self.mode = mode self.mode = mode
@ -227,6 +227,12 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
var entries: [ChannelMembersSearchEntry] = [] var entries: [ChannelMembersSearchEntry] = []
var existingPeerIds = Set<PeerId>() var existingPeerIds = Set<PeerId>()
for filter in filters {
switch filter {
case let .exclude(ids):
existingPeerIds = existingPeerIds.union(ids)
}
}
switch mode { switch mode {
case .inviteActions, .banAndPromoteActions: case .inviteActions, .banAndPromoteActions:
existingPeerIds.insert(account.peerId) existingPeerIds.insert(account.peerId)

View File

@ -9,12 +9,17 @@ enum ChannelMembersSearchControllerMode {
case ban case ban
} }
enum ChannelMembersSearchFilter {
case exclude([PeerId])
}
final class ChannelMembersSearchController: ViewController { final class ChannelMembersSearchController: ViewController {
private let queue = Queue() private let queue = Queue()
private let account: Account private let account: Account
private let peerId: PeerId private let peerId: PeerId
private let mode: ChannelMembersSearchControllerMode private let mode: ChannelMembersSearchControllerMode
private let filters: [ChannelMembersSearchFilter]
private let openPeer: (Peer, RenderedChannelParticipant?) -> Void private let openPeer: (Peer, RenderedChannelParticipant?) -> Void
private var presentationData: PresentationData private var presentationData: PresentationData
@ -25,12 +30,12 @@ final class ChannelMembersSearchController: ViewController {
return self.displayNode as! ChannelMembersSearchControllerNode return self.displayNode as! ChannelMembersSearchControllerNode
} }
init(account: Account, peerId: PeerId, mode: ChannelMembersSearchControllerMode, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) { init(account: Account, peerId: PeerId, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter] = [], openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) {
self.account = account self.account = account
self.peerId = peerId self.peerId = peerId
self.mode = mode self.mode = mode
self.openPeer = openPeer self.openPeer = openPeer
self.filters = filters
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
@ -52,7 +57,7 @@ final class ChannelMembersSearchController: ViewController {
} }
override func loadDisplayNode() { override func loadDisplayNode() {
self.displayNode = ChannelMembersSearchControllerNode(account: self.account, theme: self.presentationData.theme, strings: self.presentationData.strings, peerId: self.peerId, mode: self.mode) self.displayNode = ChannelMembersSearchControllerNode(account: self.account, theme: self.presentationData.theme, strings: self.presentationData.strings, peerId: self.peerId, mode: self.mode, filters: self.filters)
self.controllerNode.navigationBar = self.navigationBar self.controllerNode.navigationBar = self.navigationBar
self.controllerNode.requestActivateSearch = { [weak self] in self.controllerNode.requestActivateSearch = { [weak self] in
self?.activateSearch() self?.activateSearch()

View File

@ -108,7 +108,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
private let account: Account private let account: Account
private let peerId: PeerId private let peerId: PeerId
private let mode: ChannelMembersSearchControllerMode private let mode: ChannelMembersSearchControllerMode
private let filters: [ChannelMembersSearchFilter]
let listNode: ListView let listNode: ListView
var navigationBar: NavigationBar? var navigationBar: NavigationBar?
@ -127,12 +127,12 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
private var disposable: Disposable? private var disposable: Disposable?
private var listControl: PeerChannelMemberCategoryControl? private var listControl: PeerChannelMemberCategoryControl?
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, peerId: PeerId, mode: ChannelMembersSearchControllerMode) { init(account: Account, theme: PresentationTheme, strings: PresentationStrings, peerId: PeerId, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter]) {
self.account = account self.account = account
self.listNode = ListView() self.listNode = ListView()
self.peerId = peerId self.peerId = peerId
self.mode = mode self.mode = mode
self.filters = filters
self.themeAndStrings = (theme, strings) self.themeAndStrings = (theme, strings)
super.init() super.init()
@ -168,10 +168,26 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
if participant.peer.id == account.peerId { if participant.peer.id == account.peerId {
continue continue
} }
for filter in filters {
switch filter {
case let .exclude(ids):
if ids.contains(participant.peer.id) {
continue
}
}
}
case .promote: case .promote:
if participant.peer.id == account.peerId { if participant.peer.id == account.peerId {
continue continue
} }
for filter in filters {
switch filter {
case let .exclude(ids):
if ids.contains(participant.peer.id) {
continue
}
}
}
if case .creator = participant.participant { if case .creator = participant.participant {
label = strings.Channel_Management_LabelCreator label = strings.Channel_Management_LabelCreator
enabled = false enabled = false
@ -268,7 +284,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
} }
if let placeholderNode = maybePlaceholderNode { if let placeholderNode = maybePlaceholderNode {
self.searchDisplayController = SearchDisplayController(theme: self.themeAndStrings.0, strings: self.themeAndStrings.1, contentNode: ChannelMembersSearchContainerNode(account: self.account, peerId: self.peerId, mode: .banAndPromoteActions, openPeer: { [weak self] peer, participant in self.searchDisplayController = SearchDisplayController(theme: self.themeAndStrings.0, strings: self.themeAndStrings.1, contentNode: ChannelMembersSearchContainerNode(account: self.account, peerId: self.peerId, mode: .banAndPromoteActions, filters: self.filters, openPeer: { [weak self] peer, participant in
self?.requestOpenPeerFromSearch?(peer, participant) self?.requestOpenPeerFromSearch?(peer, participant)
}), cancel: { [weak self] in }), cancel: { [weak self] in
if let requestDeactivateSearch = self?.requestDeactivateSearch { if let requestDeactivateSearch = self?.requestDeactivateSearch {

View File

@ -307,6 +307,26 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin
controllerInteraction.hiddenMedia = messageIdAndMedia controllerInteraction.hiddenMedia = messageIdAndMedia
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView {
itemNode.updateHiddenMedia()
}
}
}
}))
}
}, chatAvatarHiddenMedia: { signal, media in
if let strongSelf = self {
strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { messageId in
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
var messageIdAndMedia: [MessageId: [Media]] = [:]
if let messageId = messageId {
messageIdAndMedia[messageId] = [media]
}
controllerInteraction.hiddenMedia = messageIdAndMedia
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView { if let itemNode = itemNode as? ChatMessageItemView {
itemNode.updateHiddenMedia() itemNode.updateHiddenMedia()
@ -3828,7 +3848,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin
searchDisposable = MetaDisposable() searchDisposable = MetaDisposable()
self.searchDisposable = searchDisposable self.searchDisposable = searchDisposable
} }
searchDisposable.set((searchMessages(account: self.account, location: location, query: query) searchDisposable.set((searchMessages(account: self.account, location: location, query: query) |> map {$0.0}
|> delay(0.2, queue: Queue.mainQueue()) |> delay(0.2, queue: Queue.mainQueue())
|> deliverOnMainQueue).start(next: { [weak self] results in |> deliverOnMainQueue).start(next: { [weak self] results in
if let strongSelf = self { if let strongSelf = self {

View File

@ -5,6 +5,65 @@ import Display
import AsyncDisplayKit import AsyncDisplayKit
import TelegramCore import TelegramCore
private class ChatGridLiveSelectorRecognizer: UIPanGestureRecognizer {
private let selectionGestureActivationThreshold: CGFloat = 2.0
private let selectionGestureVerticalFailureThreshold: CGFloat = 5.0
var validatedGesture: Bool? = nil
var firstLocation: CGPoint = CGPoint()
var shouldBegin: (() -> Bool)?
override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
self.maximumNumberOfTouches = 1
}
override func reset() {
super.reset()
self.validatedGesture = nil
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
if let shouldBegin = self.shouldBegin, !shouldBegin() {
self.state = .failed
} else {
let touch = touches.first!
self.firstLocation = touch.location(in: self.view)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
let location = touches.first!.location(in: self.view)
let translation = CGPoint(x: location.x - firstLocation.x, y: location.y - firstLocation.y)
if validatedGesture == nil {
if (fabs(translation.y) >= selectionGestureVerticalFailureThreshold)
{
validatedGesture = false
}
else if (fabs(translation.x) >= selectionGestureActivationThreshold) {
validatedGesture = true
}
}
if let validatedGesture = validatedGesture {
if validatedGesture {
super.touchesMoved(touches, with: event)
}
}
}
}
struct ChatHistoryGridViewTransition { struct ChatHistoryGridViewTransition {
let historyView: ChatHistoryView let historyView: ChatHistoryView
let topOffsetWithinMonth: Int let topOffsetWithinMonth: Int
@ -180,13 +239,13 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode {
public private(set) var loadState: ChatHistoryNodeLoadState? public private(set) var loadState: ChatHistoryNodeLoadState?
private var loadStateUpdated: ((ChatHistoryNodeLoadState, Bool) -> Void)? private var loadStateUpdated: ((ChatHistoryNodeLoadState, Bool) -> Void)?
private let controllerInteraction: ChatControllerInteraction
public init(account: Account, peerId: PeerId, messageId: MessageId?, tagMask: MessageTags?, controllerInteraction: ChatControllerInteraction) { public init(account: Account, peerId: PeerId, messageId: MessageId?, tagMask: MessageTags?, controllerInteraction: ChatControllerInteraction) {
self.account = account self.account = account
self.peerId = peerId self.peerId = peerId
self.messageId = messageId self.messageId = messageId
self.tagMask = tagMask self.tagMask = tagMask
self.controllerInteraction = controllerInteraction
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
super.init() super.init()
@ -283,6 +342,39 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode {
} }
} }
} }
let selectorRecogizner = ChatGridLiveSelectorRecognizer(target: self, action: #selector(self.panGesture(_:)))
selectorRecogizner.shouldBegin = { [weak controllerInteraction] in
return controllerInteraction?.selectionState != nil
}
self.view.addGestureRecognizer(selectorRecogizner)
}
public override func didLoad() {
super.didLoad()
}
private var liveSelectingState: (selecting: Bool, currentMessageId: MessageId)?
@objc private func panGesture(_ recognizer: UIGestureRecognizer) -> Void {
guard let selectionState = controllerInteraction.selectionState else {return}
switch recognizer.state {
case .began:
if let itemNode = self.itemNodeAtPoint(recognizer.location(in: self.view)) as? GridMessageItemNode, let messageId = itemNode.messageId {
liveSelectingState = (selecting: !selectionState.selectedIds.contains(messageId), currentMessageId: messageId)
controllerInteraction.toggleMessagesSelection([messageId], !selectionState.selectedIds.contains(messageId))
}
case .changed:
if let liveSelectingState = liveSelectingState, let itemNode = self.itemNodeAtPoint(recognizer.location(in: self.view)) as? GridMessageItemNode, let messageId = itemNode.messageId, messageId != liveSelectingState.currentMessageId {
controllerInteraction.toggleMessagesSelection([messageId], liveSelectingState.selecting)
self.liveSelectingState?.currentMessageId = messageId
}
case .ended, .failed, .cancelled:
liveSelectingState = nil
case .possible:
break
}
} }
required public init?(coder aDecoder: NSCoder) { required public init?(coder aDecoder: NSCoder) {

View File

@ -151,7 +151,7 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode {
if let strongSelf = self { if let strongSelf = self {
let signal: Signal<[ChatHistorySearchEntry]?, NoError> let signal: Signal<[ChatHistorySearchEntry]?, NoError>
if let query = query, !query.isEmpty { if let query = query, !query.isEmpty {
let foundRemoteMessages: Signal<[Message], NoError> = searchMessages(account: account, location: .peer(peerId: peerId, fromId: nil, tags: tagMask), query: query) let foundRemoteMessages: Signal<[Message], NoError> = searchMessages(account: account, location: .peer(peerId: peerId, fromId: nil, tags: tagMask), query: query) |> map {$0.0}
|> delay(0.2, queue: Queue.concurrentDefaultQueue()) |> delay(0.2, queue: Queue.concurrentDefaultQueue())
signal = combineLatest(foundRemoteMessages, themeAndStringsPromise.get()) signal = combineLatest(foundRemoteMessages, themeAndStringsPromise.get())

View File

@ -268,14 +268,16 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
} }
} }
var hasUneditableAttributes = false
if let peer = message.peers[message.id.peerId] as? TelegramChannel { if let peer = message.peers[message.id.peerId] as? TelegramChannel {
if peer.hasBannedRights(.banSendMessages) { if peer.hasBannedRights(.banSendMessages) {
restrictEdit = true hasUneditableAttributes = true
} }
} }
if hasEditRights { if hasEditRights {
var hasUneditableAttributes = false
for attribute in message.attributes { for attribute in message.attributes {
if let _ = attribute as? InlineBotMessageAttribute { if let _ = attribute as? InlineBotMessageAttribute {
hasUneditableAttributes = true hasUneditableAttributes = true
@ -298,13 +300,16 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
} else if let _ = media as? TelegramMediaExpiredContent { } else if let _ = media as? TelegramMediaExpiredContent {
hasUneditableAttributes = true hasUneditableAttributes = true
break break
} else if let _ = media as? TelegramMediaMap {
hasUneditableAttributes = true
break
} }
} }
if !hasUneditableAttributes { if !hasUneditableAttributes {
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
if canPerformEditingActions(limits: limitsConfiguration, accountPeerId: account.peerId, message: message) { if canPerformEditingActions(limits: limitsConfiguration, accountPeerId: account.peerId, message: message) {
canEdit = !restrictEdit canEdit = true
} }
} }
} }

View File

@ -235,7 +235,7 @@ enum ChatListSearchEntryStableId: Hashable {
enum ChatListSearchEntry: Comparable, Identifiable { enum ChatListSearchEntry: Comparable, Identifiable {
case localPeer(Peer, Peer?, UnreadSearchBadge?, Int, PresentationTheme, PresentationStrings) case localPeer(Peer, Peer?, UnreadSearchBadge?, Int, PresentationTheme, PresentationStrings)
case globalPeer(FoundPeer, UnreadSearchBadge?, Int, PresentationTheme, PresentationStrings) case globalPeer(FoundPeer, UnreadSearchBadge?, Int, PresentationTheme, PresentationStrings)
case message(Message, ChatListPresentationData) case message(Message, CombinedPeerReadState?, ChatListPresentationData)
var stableId: ChatListSearchEntryStableId { var stableId: ChatListSearchEntryStableId {
switch self { switch self {
@ -243,7 +243,7 @@ enum ChatListSearchEntry: Comparable, Identifiable {
return .localPeerId(peer.id) return .localPeerId(peer.id)
case let .globalPeer(peer, _, _, _, _): case let .globalPeer(peer, _, _, _, _):
return .globalPeerId(peer.peer.id) return .globalPeerId(peer.peer.id)
case let .message(message, _): case let .message(message, _, _):
return .messageId(message.id) return .messageId(message.id)
} }
} }
@ -262,8 +262,8 @@ enum ChatListSearchEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case let .message(lhsMessage, lhsPresentationData): case let .message(lhsMessage, lhsCombinedPeerReadState, lhsPresentationData):
if case let .message(rhsMessage, rhsPresentationData) = rhs { if case let .message(rhsMessage, rhsCombinedPeerReadState, rhsPresentationData) = rhs {
if lhsMessage.id != rhsMessage.id { if lhsMessage.id != rhsMessage.id {
return false return false
} }
@ -273,6 +273,9 @@ enum ChatListSearchEntry: Comparable, Identifiable {
if lhsPresentationData !== rhsPresentationData { if lhsPresentationData !== rhsPresentationData {
return false return false
} }
if lhsCombinedPeerReadState != rhsCombinedPeerReadState {
return false
}
return true return true
} else { } else {
return false return false
@ -297,8 +300,8 @@ enum ChatListSearchEntry: Comparable, Identifiable {
case .message: case .message:
return true return true
} }
case let .message(lhsMessage, _): case let .message(lhsMessage, _, _):
if case let .message(rhsMessage, _) = rhs { if case let .message(rhsMessage, _, _) = rhs {
return MessageIndex(lhsMessage) < MessageIndex(rhsMessage) return MessageIndex(lhsMessage) < MessageIndex(rhsMessage)
} else { } else {
return false return false
@ -393,8 +396,8 @@ enum ChatListSearchEntry: Comparable, Identifiable {
return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .generalSearch, peer: .peer(peer: peer.peer, chatPeer: peer.peer), status: .addressName(suffixString), badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .globalPeers, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { _ in return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .generalSearch, peer: .peer(peer: peer.peer, chatPeer: peer.peer), status: .addressName(suffixString), badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .globalPeers, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { _ in
interaction.peerSelected(peer.peer) interaction.peerSelected(peer.peer)
}) })
case let .message(message, presentationData): case let .message(message, readState, presentationData):
return ChatListItem(presentationData: presentationData, account: account, peerGroupId: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(message)), content: .peer(message: message, peer: RenderedPeer(message: message), combinedReadState: nil, notificationSettings: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false), editing: false, hasActiveRevealControls: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, interaction: interaction) return ChatListItem(presentationData: presentationData, account: account, peerGroupId: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(message)), content: .peer(message: message, peer: RenderedPeer(message: message), combinedReadState: readState, notificationSettings: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false), editing: false, hasActiveRevealControls: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, interaction: interaction)
} }
} }
} }
@ -566,7 +569,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
} else { } else {
location = .general location = .general
} }
let foundRemoteMessages: Signal<([Message], Bool), NoError> = .single(([], true)) |> then(searchMessages(account: account, location: location, query: query) let foundRemoteMessages: Signal<(([Message], [PeerId : CombinedPeerReadState]), Bool), NoError> = .single((([], [:]), true)) |> then(searchMessages(account: account, location: location, query: query)
|> map { ($0, false) } |> map { ($0, false) }
|> delay(0.2, queue: Queue.concurrentDefaultQueue())) |> delay(0.2, queue: Queue.concurrentDefaultQueue()))
@ -619,8 +622,8 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
if !foundRemotePeers.2 { if !foundRemotePeers.2 {
index = 0 index = 0
for message in foundRemoteMessages.0 { for message in foundRemoteMessages.0.0 {
entries.append(.message(message, presentationData)) entries.append(.message(message, foundRemoteMessages.0.1[message.id.peerId], presentationData))
index += 1 index += 1
} }
} }

View File

@ -546,6 +546,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
if j != 0 && index + j >= 0 && index + j < sortedIndices.count { if j != 0 && index + j >= 0 && index + j < sortedIndices.count {
if abs(labelRects[index + j].width - labelRects[index].width) < 40.0 { if abs(labelRects[index + j].width - labelRects[index].width) < 40.0 {
labelRects[index + j].size.width = max(labelRects[index + j].width, labelRects[index].width) labelRects[index + j].size.width = max(labelRects[index + j].width, labelRects[index].width)
labelRects[index].size.width = labelRects[index + j].size.width
} }
} }
} }
@ -557,6 +558,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
labelRects[i].origin.x = floor((labelLayout.size.width - labelRects[i].width) / 2.0) labelRects[i].origin.x = floor((labelLayout.size.width - labelRects[i].width) / 2.0)
} }
let backgroundApply = backgroundLayout(item.presentationData.theme.theme.chat.serviceMessage.serviceMessageFillColor, labelRects, 10.0, 10.0, 0.0) let backgroundApply = backgroundLayout(item.presentationData.theme.theme.chat.serviceMessage.serviceMessageFillColor, labelRects, 10.0, 10.0, 0.0)
var backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + 4.0) var backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + 4.0)
@ -584,9 +586,9 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
let apply = imageNode.asyncLayout()(arguments) let apply = imageNode.asyncLayout()(arguments)
apply() apply()
strongSelf.fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: item.account, photoReference: .message(message: MessageReference(item.message), media: image)).start())
} }
let updateImageSignal = chatMessagePhoto(postbox: item.account.postbox, photoReference: ImageMediaReference.message(message: MessageReference(item.message), media: image)) strongSelf.fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: item.account, photoReference: .message(message: MessageReference(item.message), media: image)).start())
let updateImageSignal = chatMessagePhoto(postbox: item.account.postbox, photoReference: .message(message: MessageReference(item.message), media: image))
imageNode.setSignal(updateImageSignal) imageNode.setSignal(updateImageSignal)

View File

@ -264,7 +264,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
} }
for media in item.content.firstMessage.media { for media in item.content.firstMessage.media {
if let media = media as? TelegramMediaAction, case .phoneCall(_, _, _) = media.action { if let media = media as? TelegramMediaAction, case .phoneCall(_, _, _) = media.action {
} else {
return false return false
} }
} }

View File

@ -214,7 +214,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: strings.Conversation_PinnedMessage, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0))) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: strings.Conversation_PinnedMessage, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0)))
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: descriptionStringForMessage(message, strings: strings, accountPeerId: accountPeerId).0, font: Font.regular(15.0), textColor: theme.chat.inputPanel.primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0))) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: descriptionStringForMessage(message, strings: strings, accountPeerId: accountPeerId).0, font: Font.regular(15.0), textColor: !message.media.isEmpty ? theme.chat.inputPanel.secondaryTextColor : theme.chat.inputPanel.primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0)))
Queue.mainQueue().async { Queue.mainQueue().async {
if let strongSelf = self { if let strongSelf = self {

View File

@ -167,27 +167,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, callPeer: { peerId in }, callPeer: { peerId in
self?.controllerInteraction?.callPeer(peerId) self?.controllerInteraction?.callPeer(peerId)
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in})
if let strongSelf = self {
/*strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { entry in
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
var messageIdAndMedia: [MessageId: [Media]] = [:]
if let entry = entry, entry.index == centralIndex {
messageIdAndMedia[message.id] = [galleryMedia]
}
controllerInteraction.hiddenMedia = messageIdAndMedia
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView {
itemNode.updateHiddenMedia()
}
}
}
}))*/
}
})
} }
return false return false
}, openPeer: { [weak self] peerId, _, message in }, openPeer: { [weak self] peerId, _, message in

View File

@ -63,6 +63,7 @@ final class GalleryThumbnailContainerNode: ASDisplayNode {
} }
func updateItems(_ items: [GalleryThumbnailItem], centralIndex: Int, progress: CGFloat) { func updateItems(_ items: [GalleryThumbnailItem], centralIndex: Int, progress: CGFloat) {
var items: [GalleryThumbnailItem] = items.count <= 1 ? [] : items
var updated = false var updated = false
if self.items.count == items.count { if self.items.count == items.count {
for i in 0 ..< self.items.count { for i in 0 ..< self.items.count {

View File

@ -5,6 +5,42 @@ import TelegramCore
import Postbox import Postbox
import SwiftSignalKit import SwiftSignalKit
private let videoAccessoryFont: UIFont = Font.regular(11)
private final class GridMessageVideoAccessoryNode : ASDisplayNode {
private let textNode: ImmediateTextNode = ImmediateTextNode()
override init() {
super.init()
self.textNode.displaysAsynchronously = false
self.textNode.maximumNumberOfLines = 1
self.textNode.isLayerBacked = true
self.textNode.textAlignment = .left
self.textNode.lineSpacing = 0.1
addSubnode(self.textNode)
backgroundColor = UIColor(white: 0.0, alpha: 0.6)
}
var contentSize: CGSize {
return CGSize(width: textSize.width + 10, height: 16)
}
private var textSize: CGSize = CGSize()
func setup(_ duration: String) {
textNode.attributedText = NSAttributedString(string: duration, font: videoAccessoryFont, textColor: .white, paragraphAlignment: nil)
textSize = self.textNode.updateLayout(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
}
override func layout() {
if let _ = self.textNode.attributedText {
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((frame.width - textSize.width) / 2.0), y: floorToScreenPixels((frame.height - textSize.height) / 2.0) + 0.5), size: textSize)
}
}
}
private func mediaForMessage(_ message: Message) -> Media? { private func mediaForMessage(_ message: Message) -> Media? {
for media in message.media { for media in message.media {
if let media = media as? TelegramMediaImage { if let media = media as? TelegramMediaImage {
@ -111,7 +147,6 @@ final class GridMessageItem: GridItem {
private let account: Account private let account: Account
fileprivate let message: Message fileprivate let message: Message
private let controllerInteraction: ChatControllerInteraction private let controllerInteraction: ChatControllerInteraction
let section: GridSection? let section: GridSection?
init(theme: PresentationTheme, strings: PresentationStrings, account: Account, message: Message, controllerInteraction: ChatControllerInteraction) { init(theme: PresentationTheme, strings: PresentationStrings, account: Account, message: Message, controllerInteraction: ChatControllerInteraction) {
@ -145,11 +180,12 @@ final class GridMessageItem: GridItem {
final class GridMessageItemNode: GridItemNode { final class GridMessageItemNode: GridItemNode {
private var currentState: (Account, Media, CGSize)? private var currentState: (Account, Media, CGSize)?
private let imageNode: TransformImageNode private let imageNode: TransformImageNode
private var messageId: MessageId? private(set) var messageId: MessageId?
private var item: GridMessageItem? private var item: GridMessageItem?
private var controllerInteraction: ChatControllerInteraction? private var controllerInteraction: ChatControllerInteraction?
private var statusNode: RadialStatusNode private var statusNode: RadialStatusNode
private let videoAccessoryNode = GridMessageVideoAccessoryNode()
private var selectionNode: GridMessageSelectionNode? private var selectionNode: GridMessageSelectionNode?
private let fetchStatusDisposable = MetaDisposable() private let fetchStatusDisposable = MetaDisposable()
@ -164,6 +200,7 @@ final class GridMessageItemNode: GridItemNode {
super.init() super.init()
self.addSubnode(self.imageNode) self.addSubnode(self.imageNode)
self.imageNode.addSubnode(videoAccessoryNode)
} }
deinit { deinit {
@ -193,11 +230,20 @@ final class GridMessageItemNode: GridItemNode {
self.statusNode.transitionToState(.none, completion: { [weak self] in self.statusNode.transitionToState(.none, completion: { [weak self] in
self?.statusNode.isHidden = true self?.statusNode.isHidden = true
}) })
videoAccessoryNode.isHidden = true
self.resourceStatus = nil self.resourceStatus = nil
} else if let file = media as? TelegramMediaFile, file.isVideo { } else if let file = media as? TelegramMediaFile, file.isVideo {
mediaDimensions = file.dimensions mediaDimensions = file.dimensions
self.imageNode.setSignal(mediaGridMessageVideo(postbox: account.postbox, videoReference: .message(message: MessageReference(item.message), media: file))) self.imageNode.setSignal(mediaGridMessageVideo(postbox: account.postbox, videoReference: .message(message: MessageReference(item.message), media: file)))
if let duration = file.duration {
videoAccessoryNode.setup(String(format: "%d:%02d", duration / 60, duration % 60))
videoAccessoryNode.isHidden = false
} else {
videoAccessoryNode.isHidden = true
}
self.resourceStatus = nil self.resourceStatus = nil
self.fetchStatusDisposable.set((messageMediaFileStatus(account: account, messageId: messageId, file: file) |> deliverOnMainQueue).start(next: { [weak self] status in self.fetchStatusDisposable.set((messageMediaFileStatus(account: account, messageId: messageId, file: file) |> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self { if let strongSelf = self {
@ -233,6 +279,8 @@ final class GridMessageItemNode: GridItemNode {
if self.statusNode.supernode == nil { if self.statusNode.supernode == nil {
self.imageNode.addSubnode(self.statusNode) self.imageNode.addSubnode(self.statusNode)
} }
} else {
videoAccessoryNode.isHidden = true
} }
if let mediaDimensions = mediaDimensions { if let mediaDimensions = mediaDimensions {
@ -263,6 +311,8 @@ final class GridMessageItemNode: GridItemNode {
self.selectionNode?.frame = CGRect(origin: CGPoint(), size: self.bounds.size) self.selectionNode?.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
let progressDiameter: CGFloat = 40.0 let progressDiameter: CGFloat = 40.0
self.statusNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - progressDiameter) / 2.0), y: floor((imageFrame.size.height - progressDiameter) / 2.0)), size: CGSize(width: progressDiameter, height: progressDiameter)) self.statusNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - progressDiameter) / 2.0), y: floor((imageFrame.size.height - progressDiameter) / 2.0)), size: CGSize(width: progressDiameter, height: progressDiameter))
videoAccessoryNode.frame = CGRect(origin: CGPoint(x: imageFrame.maxX - videoAccessoryNode.contentSize.width - 5, y: imageFrame.maxY - videoAccessoryNode.contentSize.height - 5), size: videoAccessoryNode.contentSize)
} }
func updateSelectionState(animated: Bool) { func updateSelectionState(animated: Bool) {

View File

@ -51,7 +51,7 @@ private final class ChannelMembersSearchItemNode: ItemListControllerSearchNode {
private let containerNode: ChannelMembersSearchContainerNode private let containerNode: ChannelMembersSearchContainerNode
init(account: Account, peerId: PeerId, searchMode: ChannelMembersSearchMode, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, cancel: @escaping () -> Void) { init(account: Account, peerId: PeerId, searchMode: ChannelMembersSearchMode, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, cancel: @escaping () -> Void) {
self.containerNode = ChannelMembersSearchContainerNode(account: account, peerId: peerId, mode: searchMode, openPeer: { peer, participant in self.containerNode = ChannelMembersSearchContainerNode(account: account, peerId: peerId, mode: searchMode, filters: [], openPeer: { peer, participant in
openPeer(peer, participant) openPeer(peer, participant)
}) })
self.containerNode.cancel = { self.containerNode.cancel = {

View File

@ -38,7 +38,9 @@ final class HashtagSearchController: TelegramController {
let location: SearchMessagesLocation = .general let location: SearchMessagesLocation = .general
let search = searchMessages(account: account, location: location, query: query) let search = searchMessages(account: account, location: location, query: query)
let foundMessages: Signal<[ChatListSearchEntry], NoError> = search let foundMessages: Signal<[ChatListSearchEntry], NoError> = search
|> map { return $0.map({ .message($0, chatListPresentationData) }) } |> map { result in
return result.0.map({ .message($0, result.1[$0.id.peerId], chatListPresentationData) })
}
let interaction = ChatListNodeInteraction(activateSearch: { let interaction = ChatListNodeInteraction(activateSearch: {
}, peerSelected: { peer in }, peerSelected: { peer in

View File

@ -77,11 +77,14 @@ private let iconTextBackgroundImage = generateStretchableFilledCircleImage(radiu
final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode { final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode {
private let imageNodeBackground: ASDisplayNode private let imageNodeBackground: ASDisplayNode
private let imageNode: TransformImageNode private let imageNode: TransformImageNode
private var statusNode: RadialStatusNode?
private var videoLayer: (SoftwareVideoThumbnailLayer, SoftwareVideoLayerFrameManager, SampleBufferLayer)? private var videoLayer: (SoftwareVideoThumbnailLayer, SoftwareVideoLayerFrameManager, SampleBufferLayer)?
private var currentImageResource: TelegramMediaResource? private var currentImageResource: TelegramMediaResource?
private var currentVideoFile: TelegramMediaFile? private var currentVideoFile: TelegramMediaFile?
private var resourceStatus: MediaResourceStatus?
private(set) var item: HorizontalListContextResultsChatInputPanelItem? private(set) var item: HorizontalListContextResultsChatInputPanelItem?
private var statusDisposable = MetaDisposable()
override var visibility: ListViewItemNodeVisibility { override var visibility: ListViewItemNodeVisibility {
didSet { didSet {
@ -165,6 +168,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
displayLink.isPaused = true displayLink.isPaused = true
displayLink.invalidate() displayLink.invalidate()
} }
statusDisposable.dispose()
} }
override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
@ -189,7 +193,10 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
let sideInset: CGFloat = 4.0 let sideInset: CGFloat = 4.0
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
var updatedStatusSignal: Signal<MediaResourceStatus, NoError>?
//messageFileMediaResourceStatus(account: account, file: file, message: message, isRecentActions: isRecentActions)
var imageResource: TelegramMediaResource? var imageResource: TelegramMediaResource?
var stickerFile: TelegramMediaFile? var stickerFile: TelegramMediaFile?
var videoFile: TelegramMediaFile? var videoFile: TelegramMediaFile?
@ -206,6 +213,12 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
videoFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource)], mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])]) videoFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource)], mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])])
imageResource = nil imageResource = nil
} }
if let file = videoFile {
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(file.resource)
} else if let imageResource = imageResource {
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
}
case let .internalReference(_, _, _, title, _, image, file, _): case let .internalReference(_, _, _, title, _, image, file, _):
if let image = image { if let image = image {
if let largestRepresentation = largestImageRepresentation(image.representations) { if let largestRepresentation = largestImageRepresentation(image.representations) {
@ -230,7 +243,12 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
if file.isVideo && file.isAnimated { if file.isVideo && file.isAnimated {
videoFile = file videoFile = file
imageResource = nil imageResource = nil
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(file.resource)
} else if let imageResource = imageResource {
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
} }
} else if let imageResource = imageResource {
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
} }
} }
@ -282,6 +300,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
} }
} }
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: height, height: croppedImageDimensions.width + sideInset), insets: UIEdgeInsets()) let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: height, height: croppedImageDimensions.width + sideInset), insets: UIEdgeInsets())
return (nodeLayout, { _ in return (nodeLayout, { _ in
@ -333,6 +352,52 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
} }
} }
let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((nodeLayout.contentSize.width - 37) / 2), y: floorToScreenPixels((nodeLayout.contentSize.height - 37) / 2)), size: CGSize(width: 37, height: 37))
if let updatedStatusSignal = updatedStatusSignal {
strongSelf.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in
displayLinkDispatcher.dispatch {
if let strongSelf = strongSelf {
strongSelf.resourceStatus = status
if strongSelf.statusNode == nil {
let statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
strongSelf.statusNode = statusNode
strongSelf.addSubnode(statusNode)
}
strongSelf.statusNode?.frame = progressFrame
let state: RadialStatusNodeState
let statusForegroundColor: UIColor = .white
switch status {
case let .Fetching(_, progress):
state = RadialStatusNodeState.progress(color: statusForegroundColor, lineWidth: nil, value: CGFloat(progress), cancelEnabled: false)
case .Remote:
state = .download(statusForegroundColor)
case .Local:
state = .none
}
if let statusNode = strongSelf.statusNode {
if state == .none {
strongSelf.statusNode = nil
}
statusNode.transitionToState(state, completion: { [weak statusNode] in
if state == .none {
statusNode?.removeFromSupernode()
}
})
}
}
}
}))
}
if let (thumbnailLayer, _, layer) = strongSelf.videoLayer { if let (thumbnailLayer, _, layer) = strongSelf.videoLayer {
thumbnailLayer.bounds = CGRect(origin: CGPoint(), size: CGSize(width: croppedImageDimensions.width, height: croppedImageDimensions.height)) thumbnailLayer.bounds = CGRect(origin: CGPoint(), size: CGSize(width: croppedImageDimensions.width, height: croppedImageDimensions.height))
thumbnailLayer.position = CGPoint(x: height / 2.0, y: (nodeLayout.contentSize.height - sideInset) / 2.0 + sideInset) thumbnailLayer.position = CGPoint(x: height / 2.0, y: (nodeLayout.contentSize.height - sideInset) / 2.0 + sideInset)

View File

@ -378,7 +378,7 @@ public enum InstalledStickerPacksControllerMode {
case masks case masks
} }
public func installedStickerPacksController(account: Account, mode: InstalledStickerPacksControllerMode, archivedPacks:[ArchivedStickerPackItem]? = nil) -> ViewController { public func installedStickerPacksController(account: Account, mode: InstalledStickerPacksControllerMode, archivedPacks:[ArchivedStickerPackItem]? = nil, updatedPacks: @escaping([ArchivedStickerPackItem]?) -> Void = { _ in}) -> ViewController {
let initialState = InstalledStickerPacksControllerState().withUpdatedEditing(mode == .modal) let initialState = InstalledStickerPacksControllerState().withUpdatedEditing(mode == .modal)
let statePromise = ValuePromise(initialState, ignoreRepeated: true) let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState) let stateValue = Atomic(value: initialState)
@ -396,6 +396,9 @@ public func installedStickerPacksController(account: Account, mode: InstalledSti
let resolveDisposable = MetaDisposable() let resolveDisposable = MetaDisposable()
actionsDisposable.add(resolveDisposable) actionsDisposable.add(resolveDisposable)
let archivedPromise = Promise<[ArchivedStickerPackItem]?>()
var presentStickerPackController: ((StickerPackCollectionInfo) -> Void)? var presentStickerPackController: ((StickerPackCollectionInfo) -> Void)?
let arguments = InstalledStickerPacksControllerArguments(account: account, openStickerPack: { info in let arguments = InstalledStickerPacksControllerArguments(account: account, openStickerPack: { info in
@ -436,11 +439,14 @@ public func installedStickerPacksController(account: Account, mode: InstalledSti
} }
})) }))
}, openMasks: { }, openMasks: {
pushControllerImpl?(installedStickerPacksController(account: account, mode: .masks, archivedPacks: archivedPacks)) pushControllerImpl?(installedStickerPacksController(account: account, mode: .masks, archivedPacks: archivedPacks, updatedPacks: { _ in}))
}, openFeatured: { }, openFeatured: {
pushControllerImpl?(featuredStickerPacksController(account: account)) pushControllerImpl?(featuredStickerPacksController(account: account))
}, openArchived: { archived in }, openArchived: { archived in
pushControllerImpl?(archivedStickerPacksController(account: account, archived: archived)) pushControllerImpl?(archivedStickerPacksController(account: account, archived: archived, updatedPacks: { packs in
archivedPromise.set(.single(packs))
updatedPacks(packs)
}))
}, openSuggestOptions: { }, openSuggestOptions: {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let controller = ActionSheetController(presentationTheme: presentationData.theme) let controller = ActionSheetController(presentationTheme: presentationData.theme)
@ -484,7 +490,6 @@ public func installedStickerPacksController(account: Account, mode: InstalledSti
stickerPacks.set(account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [namespaceForMode(mode)])])) stickerPacks.set(account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [namespaceForMode(mode)])]))
let featured = Promise<[FeaturedStickerPackItem]>() let featured = Promise<[FeaturedStickerPackItem]>()
let archivedPromise = Promise<[ArchivedStickerPackItem]?>()
switch mode { switch mode {
case .general, .modal: case .general, .modal:

View File

@ -151,7 +151,7 @@ func chatMessagePreviewControllerData(account: Account, message: Message, standa
return nil return nil
} }
func openChatMessage(account: Account, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?, modal: Bool = false, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, transitionNode: @escaping (MessageId, Media) -> (ASDisplayNode, () -> UIView?)?, addToTransitionSurface: @escaping (UIView) -> Void, openUrl: @escaping (String) -> Void, openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, callPeer: @escaping (PeerId) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void, sendSticker: ((FileMediaReference) -> Void)?, setupTemporaryHiddenMedia: @escaping (Signal<InstantPageGalleryEntry?, NoError>, Int, Media) -> Void) -> Bool { func openChatMessage(account: Account, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?, modal: Bool = false, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, transitionNode: @escaping (MessageId, Media) -> (ASDisplayNode, () -> UIView?)?, addToTransitionSurface: @escaping (UIView) -> Void, openUrl: @escaping (String) -> Void, openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, callPeer: @escaping (PeerId) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void, sendSticker: ((FileMediaReference) -> Void)?, setupTemporaryHiddenMedia: @escaping (Signal<InstantPageGalleryEntry?, NoError>, Int, Media) -> Void, chatAvatarHiddenMedia: @escaping (Signal<MessageId?, NoError>, Media) -> Void) -> Bool {
if let mediaData = chatMessageGalleryControllerData(account: account, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, synchronousLoad: false) { if let mediaData = chatMessageGalleryControllerData(account: account, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, synchronousLoad: false) {
switch mediaData { switch mediaData {
case let .url(url): case let .url(url):
@ -327,7 +327,14 @@ func openChatMessage(account: Account, message: Message, standalone: Bool, rever
return true return true
} }
case let .chatAvatars(controller, media): case let .chatAvatars(controller, media):
chatAvatarHiddenMedia(controller.hiddenMedia |> map { value -> MessageId? in
if value != nil {
return message.id
} else {
return nil
}
}, media)
present(controller, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in present(controller, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in
if let selectedTransitionNode = transitionNode(message.id, media) { if let selectedTransitionNode = transitionNode(message.id, media) {
return GalleryTransitionArguments(transitionNode: selectedTransitionNode, addToTransitionSurface: addToTransitionSurface) return GalleryTransitionArguments(transitionNode: selectedTransitionNode, addToTransitionSurface: addToTransitionSurface)

View File

@ -156,7 +156,7 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec
openMessageImpl = { [weak self] id in openMessageImpl = { [weak self] id in
if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.historyNode.messageInCurrentHistoryView(id) { if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.historyNode.messageInCurrentHistoryView(id) {
return openChatMessage(account: strongSelf.account, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _ in }, enqueueMessage: { _ in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }) return openChatMessage(account: strongSelf.account, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _ in }, enqueueMessage: { _ in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in})
} }
return false return false
} }

View File

@ -104,8 +104,7 @@ public class PeerMediaCollectionController: TelegramController {
}, callPeer: { peerId in }, callPeer: { peerId in
self?.controllerInteraction?.callPeer(peerId) self?.controllerInteraction?.callPeer(peerId)
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in})
})
} }
return false return false
}, openPeer: { [weak self] id, navigation, _ in }, openPeer: { [weak self] id, navigation, _ in
@ -517,6 +516,7 @@ public class PeerMediaCollectionController: TelegramController {
func updateInterfaceState(animated: Bool = true, _ f: (PeerMediaCollectionInterfaceState) -> PeerMediaCollectionInterfaceState) { func updateInterfaceState(animated: Bool = true, _ f: (PeerMediaCollectionInterfaceState) -> PeerMediaCollectionInterfaceState) {
let updatedInterfaceState = f(self.interfaceState) let updatedInterfaceState = f(self.interfaceState)
if self.isNodeLoaded { if self.isNodeLoaded {
self.mediaCollectionDisplayNode.updateMediaCollectionInterfaceState(updatedInterfaceState, animated: animated) self.mediaCollectionDisplayNode.updateMediaCollectionInterfaceState(updatedInterfaceState, animated: animated)
} }
@ -545,6 +545,7 @@ public class PeerMediaCollectionController: TelegramController {
} }
self.mediaCollectionDisplayNode.selectedMessages = updatedInterfaceState.selectionState?.selectedIds self.mediaCollectionDisplayNode.selectedMessages = updatedInterfaceState.selectionState?.selectedIds
view.disablesInteractiveTransitionGestureRecognizer = updatedInterfaceState.selectionState != nil
} }
} }
} }

View File

@ -45,6 +45,7 @@ private struct SettingsItemArguments {
let openSupport: () -> Void let openSupport: () -> Void
let openFaq: () -> Void let openFaq: () -> Void
let openEditing: () -> Void let openEditing: () -> Void
let updateArchivedPacks: ([ArchivedStickerPackItem]?) -> Void
} }
private enum SettingsSection: Int32 { private enum SettingsSection: Int32 {
@ -285,7 +286,9 @@ private enum SettingsEntry: ItemListNodeEntry {
}) })
case let .stickers(theme, image, text, value, archivedPacks): case let .stickers(theme, image, text, value, archivedPacks):
return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: value, labelStyle: .badge, sectionId: ItemListSectionId(self.section), style: .blocks, action: { return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: value, labelStyle: .badge, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.pushController(installedStickerPacksController(account: arguments.account, mode: .general, archivedPacks: archivedPacks)) arguments.pushController(installedStickerPacksController(account: arguments.account, mode: .general, archivedPacks: archivedPacks, updatedPacks: { packs in
arguments.updateArchivedPacks(packs)
}))
}) })
case let .notificationsAndSounds(theme, image, text): case let .notificationsAndSounds(theme, image, text):
return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: { return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
@ -426,6 +429,9 @@ public func settingsController(account: Account, accountManager: AccountManager)
var changeProfilePhotoImpl: (() -> Void)? var changeProfilePhotoImpl: (() -> Void)?
var openSavedMessagesImpl: (() -> Void)? var openSavedMessagesImpl: (() -> Void)?
let archivedPacks = Promise<[ArchivedStickerPackItem]?>()
let openFaq: () -> Void = { let openFaq: () -> Void = {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
var faqUrl = presentationData.strings.Settings_FAQ_URL var faqUrl = presentationData.strings.Settings_FAQ_URL
@ -517,6 +523,8 @@ public func settingsController(account: Account, accountManager: AccountManager)
pushControllerImpl?(editSettingsController(account: account, currentName: .personName(firstName: peer.firstName ?? "", lastName: peer.lastName ?? ""), currentBioText: cachedData.about ?? "", accountManager: accountManager)) pushControllerImpl?(editSettingsController(account: account, currentName: .personName(firstName: peer.firstName ?? "", lastName: peer.lastName ?? ""), currentBioText: cachedData.about ?? "", accountManager: accountManager))
} }
}) })
}, updateArchivedPacks: { packs in
archivedPacks.set(.single(packs))
}) })
changeProfilePhotoImpl = { changeProfilePhotoImpl = {
@ -601,7 +609,6 @@ public func settingsController(account: Account, accountManager: AccountManager)
let peerView = account.viewTracker.peerView(account.peerId) let peerView = account.viewTracker.peerView(account.peerId)
let archivedPacks = Promise<[ArchivedStickerPackItem]?>()
archivedPacks.set(.single(nil) |> then(archivedStickerPacks(account: account) |> map(Optional.init))) archivedPacks.set(.single(nil) |> then(archivedStickerPacks(account: account) |> map(Optional.init)))
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), peerView, account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings]), combineLatest(account.viewTracker.featuredStickerPacks(), archivedPacks.get())) let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), peerView, account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings]), combineLatest(account.viewTracker.featuredStickerPacks(), archivedPacks.get()))

View File

@ -27,8 +27,7 @@ private func presentLiveLocationController(account: Account, peerId: PeerId, con
}, openPeer: { peer, navigation in }, openPeer: { peer, navigation in
}, callPeer: { _ in }, callPeer: { _ in
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in})
})
} }
}) })
} }