Merge branch 'master' of github.com:peter-iakovlev/TelegramUI

This commit is contained in:
Ilya Laktyushin 2018-09-07 00:37:49 +03:00
commit 9fc21e5b5f
47 changed files with 929 additions and 238 deletions

View File

@ -5813,6 +5813,7 @@
PRODUCT_NAME = TelegramUI; PRODUCT_NAME = TelegramUI;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = singlefile;
SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_INSTALL_OBJC_HEADER = YES;
SWIFT_VERSION = 4.0; SWIFT_VERSION = 4.0;
USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include";
@ -6040,7 +6041,9 @@
PRODUCT_NAME = TelegramUI; PRODUCT_NAME = TelegramUI;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = singlefile;
SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_INSTALL_OBJC_HEADER = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 4.0; SWIFT_VERSION = 4.0;
USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include";
}; };
@ -6117,6 +6120,7 @@
PRODUCT_NAME = TelegramUI; PRODUCT_NAME = TelegramUI;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = singlefile;
SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_INSTALL_OBJC_HEADER = YES;
SWIFT_VERSION = 4.0; SWIFT_VERSION = 4.0;
USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include";
@ -6153,6 +6157,7 @@
PRODUCT_NAME = TelegramUI; PRODUCT_NAME = TelegramUI;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = singlefile;
SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_INSTALL_OBJC_HEADER = YES;
SWIFT_VERSION = 4.0; SWIFT_VERSION = 4.0;
USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include";

View File

@ -275,6 +275,7 @@ private struct ChannelAdminsControllerState: Equatable {
let removingPeerId: PeerId? let removingPeerId: PeerId?
let removedPeerIds: Set<PeerId> let removedPeerIds: Set<PeerId>
let temporaryAdmins: [RenderedChannelParticipant] let temporaryAdmins: [RenderedChannelParticipant]
let searchingMembers: Bool
init() { init() {
self.selectedType = nil self.selectedType = nil
@ -283,15 +284,17 @@ private struct ChannelAdminsControllerState: Equatable {
self.removingPeerId = nil self.removingPeerId = nil
self.removedPeerIds = Set() self.removedPeerIds = Set()
self.temporaryAdmins = [] self.temporaryAdmins = []
self.searchingMembers = false
} }
init(selectedType: CurrentAdministrationType?, editing: Bool, peerIdWithRevealedOptions: PeerId?, removingPeerId: PeerId?, removedPeerIds: Set<PeerId>, temporaryAdmins: [RenderedChannelParticipant]) { init(selectedType: CurrentAdministrationType?, editing: Bool, peerIdWithRevealedOptions: PeerId?, removingPeerId: PeerId?, removedPeerIds: Set<PeerId>, temporaryAdmins: [RenderedChannelParticipant], searchingMembers: Bool) {
self.selectedType = selectedType self.selectedType = selectedType
self.editing = editing self.editing = editing
self.peerIdWithRevealedOptions = peerIdWithRevealedOptions self.peerIdWithRevealedOptions = peerIdWithRevealedOptions
self.removingPeerId = removingPeerId self.removingPeerId = removingPeerId
self.removedPeerIds = removedPeerIds self.removedPeerIds = removedPeerIds
self.temporaryAdmins = temporaryAdmins self.temporaryAdmins = temporaryAdmins
self.searchingMembers = searchingMembers
} }
static func ==(lhs: ChannelAdminsControllerState, rhs: ChannelAdminsControllerState) -> Bool { static func ==(lhs: ChannelAdminsControllerState, rhs: ChannelAdminsControllerState) -> Bool {
@ -313,32 +316,39 @@ private struct ChannelAdminsControllerState: Equatable {
if lhs.temporaryAdmins != rhs.temporaryAdmins { if lhs.temporaryAdmins != rhs.temporaryAdmins {
return false return false
} }
if lhs.searchingMembers != rhs.searchingMembers {
return false
}
return true return true
} }
func withUpdatedSearchingMembers(_ searchingMembers: Bool) -> ChannelAdminsControllerState {
return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins, searchingMembers: searchingMembers)
}
func withUpdatedSelectedType(_ selectedType: CurrentAdministrationType?) -> ChannelAdminsControllerState { func withUpdatedSelectedType(_ selectedType: CurrentAdministrationType?) -> ChannelAdminsControllerState {
return ChannelAdminsControllerState(selectedType: selectedType, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins) return ChannelAdminsControllerState(selectedType: selectedType, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins, searchingMembers: self.searchingMembers)
} }
func withUpdatedEditing(_ editing: Bool) -> ChannelAdminsControllerState { func withUpdatedEditing(_ editing: Bool) -> ChannelAdminsControllerState {
return ChannelAdminsControllerState(selectedType: self.selectedType, editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins) return ChannelAdminsControllerState(selectedType: self.selectedType, editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins, searchingMembers: self.searchingMembers)
} }
func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChannelAdminsControllerState { func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChannelAdminsControllerState {
return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins) return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins, searchingMembers: self.searchingMembers)
} }
func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> ChannelAdminsControllerState { func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> ChannelAdminsControllerState {
return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins) return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins, searchingMembers: self.searchingMembers)
} }
func withUpdatedRemovedPeerIds(_ removedPeerIds: Set<PeerId>) -> ChannelAdminsControllerState { func withUpdatedRemovedPeerIds(_ removedPeerIds: Set<PeerId>) -> ChannelAdminsControllerState {
return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: removedPeerIds, temporaryAdmins: self.temporaryAdmins) return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: removedPeerIds, temporaryAdmins: self.temporaryAdmins, searchingMembers: self.searchingMembers)
} }
func withUpdatedTemporaryAdmins(_ temporaryAdmins: [RenderedChannelParticipant]) -> ChannelAdminsControllerState { func withUpdatedTemporaryAdmins(_ temporaryAdmins: [RenderedChannelParticipant]) -> ChannelAdminsControllerState {
return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: temporaryAdmins) return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: temporaryAdmins, searchingMembers: self.searchingMembers)
} }
} }
@ -550,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):
@ -564,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
@ -593,6 +610,7 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, view, admins -> (ItemListControllerState, (ItemListNodeState<ChannelAdminsEntry>, ChannelAdminsEntry.ItemGenerationArguments)) in |> map { presentationData, state, view, admins -> (ItemListControllerState, (ItemListNodeState<ChannelAdminsEntry>, ChannelAdminsEntry.ItemGenerationArguments)) in
var rightNavigationButton: ItemListNavigationButton? var rightNavigationButton: ItemListNavigationButton?
var secondaryRightNavigationButton: ItemListNavigationButton?
if let admins = admins, admins.count > 1 { if let admins = admins, admins.count > 1 {
if state.editing { if state.editing {
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
@ -606,7 +624,27 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon
return state.withUpdatedEditing(true) return state.withUpdatedEditing(true)
} }
}) })
} }
if !state.editing {
if rightNavigationButton == nil {
rightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: {
updateState { state in
return state.withUpdatedSearchingMembers(true)
}
})
} else {
secondaryRightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: {
updateState { state in
return state.withUpdatedSearchingMembers(true)
}
})
}
}
_ = stateValue.swap(state.withUpdatedTemporaryAdmins(admins))
} }
let previous = previousPeers let previous = previousPeers
@ -617,8 +655,22 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon
isGroup = false isGroup = false
} }
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(isGroup ? presentationData.strings.ChatAdmins_Title : presentationData.strings.Channel_Management_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) var searchItem: ItemListControllerSearch?
let listState = ItemListNodeState(entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: account.peerId, view: view, state: state, participants: admins), style: .blocks, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count) if state.searchingMembers {
searchItem = ChannelMembersSearchItem(account: account, peerId: peerId, searchMode: .searchAdmins, cancel: {
updateState { state in
return state.withUpdatedSearchingMembers(false)
}
}, openPeer: { _, participant in
if let participant = participant?.participant, case .member = participant {
presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
})
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(isGroup ? presentationData.strings.ChatAdmins_Title : presentationData.strings.Channel_Management_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: account.peerId, view: view, state: state, participants: admins), style: .blocks, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} |> afterDisposed { } |> afterDisposed {

View File

@ -210,17 +210,20 @@ private struct ChannelBlacklistControllerState: Equatable {
let editing: Bool let editing: Bool
let peerIdWithRevealedOptions: PeerId? let peerIdWithRevealedOptions: PeerId?
let removingPeerId: PeerId? let removingPeerId: PeerId?
let searchingMembers: Bool
init() { init() {
self.editing = false self.editing = false
self.peerIdWithRevealedOptions = nil self.peerIdWithRevealedOptions = nil
self.removingPeerId = nil self.removingPeerId = nil
self.searchingMembers = false
} }
init(editing: Bool, peerIdWithRevealedOptions: PeerId?, removingPeerId: PeerId?) { init(editing: Bool, peerIdWithRevealedOptions: PeerId?, removingPeerId: PeerId?, searchingMembers: Bool) {
self.editing = editing self.editing = editing
self.peerIdWithRevealedOptions = peerIdWithRevealedOptions self.peerIdWithRevealedOptions = peerIdWithRevealedOptions
self.removingPeerId = removingPeerId self.removingPeerId = removingPeerId
self.searchingMembers = searchingMembers
} }
static func ==(lhs: ChannelBlacklistControllerState, rhs: ChannelBlacklistControllerState) -> Bool { static func ==(lhs: ChannelBlacklistControllerState, rhs: ChannelBlacklistControllerState) -> Bool {
@ -233,20 +236,28 @@ private struct ChannelBlacklistControllerState: Equatable {
if lhs.removingPeerId != rhs.removingPeerId { if lhs.removingPeerId != rhs.removingPeerId {
return false return false
} }
if lhs.searchingMembers != rhs.searchingMembers {
return false
}
return true return true
} }
func withUpdatedEditing(_ editing: Bool) -> ChannelBlacklistControllerState { func withUpdatedSearchingMembers(_ searchingMembers: Bool) -> ChannelBlacklistControllerState {
return ChannelBlacklistControllerState(editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId) return ChannelBlacklistControllerState(editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, searchingMembers: searchingMembers)
} }
func withUpdatedEditing(_ editing: Bool) -> ChannelBlacklistControllerState {
return ChannelBlacklistControllerState(editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, searchingMembers: self.searchingMembers)
}
func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChannelBlacklistControllerState { func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChannelBlacklistControllerState {
return ChannelBlacklistControllerState(editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions, removingPeerId: self.removingPeerId) return ChannelBlacklistControllerState(editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, searchingMembers: self.searchingMembers)
} }
func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> ChannelBlacklistControllerState { func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> ChannelBlacklistControllerState {
return ChannelBlacklistControllerState(editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: removingPeerId) return ChannelBlacklistControllerState(editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: removingPeerId, searchingMembers: self.searchingMembers)
} }
} }
@ -406,6 +417,7 @@ public func channelBlacklistController(account: Account, peerId: PeerId) -> View
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, view, blacklist -> (ItemListControllerState, (ItemListNodeState<ChannelBlacklistEntry>, ChannelBlacklistEntry.ItemGenerationArguments)) in |> map { presentationData, state, view, blacklist -> (ItemListControllerState, (ItemListNodeState<ChannelBlacklistEntry>, ChannelBlacklistEntry.ItemGenerationArguments)) in
var rightNavigationButton: ItemListNavigationButton? var rightNavigationButton: ItemListNavigationButton?
var secondaryRightNavigationButton: ItemListNavigationButton?
if let blacklist = blacklist, !blacklist.isEmpty { if let blacklist = blacklist, !blacklist.isEmpty {
if state.editing { if state.editing {
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
@ -414,12 +426,28 @@ public func channelBlacklistController(account: Account, peerId: PeerId) -> View
} }
}) })
} else { } else {
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
updateState { state in updateState { state in
return state.withUpdatedEditing(true) return state.withUpdatedEditing(true)
} }
}) })
} }
if !state.editing {
if rightNavigationButton == nil {
rightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: {
updateState { state in
return state.withUpdatedSearchingMembers(true)
}
})
} else {
secondaryRightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: {
updateState { state in
return state.withUpdatedSearchingMembers(true)
}
})
}
}
} }
var emptyStateItem: ItemListControllerEmptyStateItem? var emptyStateItem: ItemListControllerEmptyStateItem?
@ -430,8 +458,22 @@ public func channelBlacklistController(account: Account, peerId: PeerId) -> View
let previous = previousBlacklist let previous = previousBlacklist
previousBlacklist = blacklist previousBlacklist = blacklist
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Channel_BlackList_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) var searchItem: ItemListControllerSearch?
let listState = ItemListNodeState(entries: channelBlacklistControllerEntries(presentationData: presentationData, view: view, state: state, blacklist: blacklist), style: .blocks, emptyStateItem: emptyStateItem, animateChanges: previous != nil && blacklist != nil && (previous!.restricted.count + previous!.banned.count) >= (blacklist!.restricted.count + blacklist!.banned.count)) if state.searchingMembers {
searchItem = ChannelMembersSearchItem(account: account, peerId: peerId, searchMode: .searchBanned, cancel: {
updateState { state in
return state.withUpdatedSearchingMembers(false)
}
}, openPeer: { _, participant in
if let participant = participant?.participant, case .member = participant {
presentControllerImpl?(channelBannedMemberController(account: account, peerId: peerId, memberId: participant.peerId, initialParticipant: participant, updated: { _ in
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
})
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Channel_BlackList_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(entries: channelBlacklistControllerEntries(presentationData: presentationData, view: view, state: state, blacklist: blacklist), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && blacklist != nil && (previous!.restricted.count + previous!.banned.count) >= (blacklist!.restricted.count + blacklist!.banned.count))
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} |> afterDisposed { } |> afterDisposed {

View File

@ -105,11 +105,11 @@ private enum ChannelInfoEntry: ItemListNodeEntry {
switch self { switch self {
case .info: case .info:
return 0 return 0
case .addressName:
return 1
case .about:
return 2
case .channelPhotoSetup: case .channelPhotoSetup:
return 1
case .addressName:
return 2
case .about:
return 3 return 3
case .channelTypeSetup: case .channelTypeSetup:
return 4 return 4
@ -454,7 +454,7 @@ private func channelInfoEntries(account: Account, presentationData: Presentation
entries.append(.channelTypeSetup(theme: presentationData.theme, text: presentationData.strings.Channel_Edit_LinkItem, value: linkText)) entries.append(.channelTypeSetup(theme: presentationData.theme, text: presentationData.strings.Channel_Edit_LinkItem, value: linkText))
} else if let username = peer.username, !username.isEmpty { } else if let username = peer.username, !username.isEmpty, state.editingState == nil {
entries.append(.addressName(theme: presentationData.theme, text: presentationData.strings.Channel_LinkItem, value: username)) entries.append(.addressName(theme: presentationData.theme, text: presentationData.strings.Channel_LinkItem, value: username))
} }
@ -524,7 +524,7 @@ private func channelInfoEntries(account: Account, presentationData: Presentation
//if state.editingState != nil { //if state.editingState != nil {
entries.append(ChannelInfoEntry.deleteChannel(theme: presentationData.theme, text: presentationData.strings.ChannelInfo_DeleteChannel)) entries.append(ChannelInfoEntry.deleteChannel(theme: presentationData.theme, text: presentationData.strings.ChannelInfo_DeleteChannel))
//} //}
} else { } else if state.editingState == nil {
entries.append(ChannelInfoEntry.report(theme: presentationData.theme, text: presentationData.strings.ReportPeer_Report)) entries.append(ChannelInfoEntry.report(theme: presentationData.theme, text: presentationData.strings.ReportPeer_Report))
if peer.participationStatus == .member { if peer.participationStatus == .member {
entries.append(ChannelInfoEntry.leave(theme: presentationData.theme, text: presentationData.strings.Channel_LeaveChannel)) entries.append(ChannelInfoEntry.leave(theme: presentationData.theme, text: presentationData.strings.Channel_LeaveChannel))

View File

@ -49,9 +49,9 @@ struct ChannelMemberListState {
enum ChannelMemberListCategory { enum ChannelMemberListCategory {
case recent case recent
case recentSearch(String) case recentSearch(String)
case admins case admins(String?)
case restricted case restricted(String?)
case banned case banned(String?)
} }
private protocol ChannelMemberCategoryListContext { private protocol ChannelMemberCategoryListContext {
@ -153,19 +153,31 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
private func loadSignal(offset: Int32, count: Int32, hash: Int32) -> Signal<[RenderedChannelParticipant]?, NoError> { private func loadSignal(offset: Int32, count: Int32, hash: Int32) -> Signal<[RenderedChannelParticipant]?, NoError> {
let requestCategory: ChannelMembersCategory let requestCategory: ChannelMembersCategory
var adminQuery: String? = nil
switch self.category { switch self.category {
case .recent: case .recent:
requestCategory = .recent(.all) requestCategory = .recent(.all)
case let .recentSearch(query): case let .recentSearch(query):
requestCategory = .recent(.search(query)) requestCategory = .recent(.search(query))
case .admins: case let .admins(query):
requestCategory = .admins requestCategory = .admins
case .restricted: adminQuery = query
requestCategory = .restricted(.all) case let .restricted(query):
case .banned: requestCategory = .restricted(query != nil ? .search(query!) : .all)
requestCategory = .banned(.all) case let .banned(query):
requestCategory = .banned(query != nil ? .search(query!) : .all)
}
return channelMembers(postbox: self.postbox, network: self.network, peerId: self.peerId, category: requestCategory, offset: offset, limit: count, hash: hash) |> map { members in
switch requestCategory {
case .admins:
if let query = adminQuery {
return members?.filter({$0.peer.displayTitle.lowercased().components(separatedBy: " ").contains(where: {$0.hasPrefix(query.lowercased())})})
}
default:
break
}
return members
} }
return channelMembers(postbox: self.postbox, network: self.network, peerId: self.peerId, category: requestCategory, offset: offset, limit: count, hash: hash)
} }
private func loadMoreSignal(count: Int32) -> Signal<[RenderedChannelParticipant], NoError> { private func loadMoreSignal(count: Int32) -> Signal<[RenderedChannelParticipant], NoError> {
@ -561,14 +573,14 @@ final class PeerChannelMemberCategoriesContext {
mappedCategory = .recent mappedCategory = .recent
case let .recentSearch(query): case let .recentSearch(query):
mappedCategory = .recentSearch(query) mappedCategory = .recentSearch(query)
case .admins: case let .admins(query):
mappedCategory = .admins mappedCategory = .admins(query)
default: default:
mappedCategory = .recent mappedCategory = .recent
} }
context = ChannelMemberSingleCategoryListContext(postbox: self.postbox, network: self.network, peerId: self.peerId, category: mappedCategory) context = ChannelMemberSingleCategoryListContext(postbox: self.postbox, network: self.network, peerId: self.peerId, category: mappedCategory)
case .restrictedAndBanned: case let .restrictedAndBanned(query):
context = ChannelMemberMultiCategoryListContext(postbox: self.postbox, network: self.network, peerId: self.peerId, categories: [.restricted, .banned]) context = ChannelMemberMultiCategoryListContext(postbox: self.postbox, network: self.network, peerId: self.peerId, categories: [.restricted(query), .banned(query)])
} }
let contextWithSubscribers = PeerChannelMemberContextWithSubscribers(context: context, becameEmpty: { [weak self] in let contextWithSubscribers = PeerChannelMemberContextWithSubscribers(context: context, becameEmpty: { [weak self] in
assert(Queue.mainQueue().isCurrent()) assert(Queue.mainQueue().isCurrent())

View File

@ -190,17 +190,20 @@ private struct ChannelMembersControllerState: Equatable {
let editing: Bool let editing: Bool
let peerIdWithRevealedOptions: PeerId? let peerIdWithRevealedOptions: PeerId?
let removingPeerId: PeerId? let removingPeerId: PeerId?
let searchingMembers: Bool
init() { init() {
self.editing = false self.editing = false
self.peerIdWithRevealedOptions = nil self.peerIdWithRevealedOptions = nil
self.removingPeerId = nil self.removingPeerId = nil
self.searchingMembers = false
} }
init(editing: Bool, peerIdWithRevealedOptions: PeerId?, removingPeerId: PeerId?) { init(editing: Bool, peerIdWithRevealedOptions: PeerId?, removingPeerId: PeerId?, searchingMembers: Bool) {
self.editing = editing self.editing = editing
self.peerIdWithRevealedOptions = peerIdWithRevealedOptions self.peerIdWithRevealedOptions = peerIdWithRevealedOptions
self.removingPeerId = removingPeerId self.removingPeerId = removingPeerId
self.searchingMembers = searchingMembers
} }
static func ==(lhs: ChannelMembersControllerState, rhs: ChannelMembersControllerState) -> Bool { static func ==(lhs: ChannelMembersControllerState, rhs: ChannelMembersControllerState) -> Bool {
@ -213,20 +216,26 @@ private struct ChannelMembersControllerState: Equatable {
if lhs.removingPeerId != rhs.removingPeerId { if lhs.removingPeerId != rhs.removingPeerId {
return false return false
} }
if lhs.searchingMembers != rhs.searchingMembers {
return false
}
return true return true
} }
func withUpdatedSearchingMembers(_ searchingMembers: Bool) -> ChannelMembersControllerState {
return ChannelMembersControllerState(editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, searchingMembers: searchingMembers)
}
func withUpdatedEditing(_ editing: Bool) -> ChannelMembersControllerState { func withUpdatedEditing(_ editing: Bool) -> ChannelMembersControllerState {
return ChannelMembersControllerState(editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId) return ChannelMembersControllerState(editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, searchingMembers: self.searchingMembers)
} }
func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChannelMembersControllerState { func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChannelMembersControllerState {
return ChannelMembersControllerState(editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions, removingPeerId: self.removingPeerId) return ChannelMembersControllerState(editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, searchingMembers: self.searchingMembers)
} }
func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> ChannelMembersControllerState { func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> ChannelMembersControllerState {
return ChannelMembersControllerState(editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: removingPeerId) return ChannelMembersControllerState(editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: removingPeerId, searchingMembers: self.searchingMembers)
} }
} }
@ -458,6 +467,7 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, view, peers -> (ItemListControllerState, (ItemListNodeState<ChannelMembersEntry>, ChannelMembersEntry.ItemGenerationArguments)) in |> map { presentationData, state, view, peers -> (ItemListControllerState, (ItemListNodeState<ChannelMembersEntry>, ChannelMembersEntry.ItemGenerationArguments)) in
var rightNavigationButton: ItemListNavigationButton? var rightNavigationButton: ItemListNavigationButton?
var secondaryRightNavigationButton: ItemListNavigationButton?
if let peers = peers, !peers.isEmpty { if let peers = peers, !peers.isEmpty {
if state.editing { if state.editing {
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
@ -471,9 +481,31 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo
return state.withUpdatedEditing(true) return state.withUpdatedEditing(true)
} }
}) })
if let cachedData = view.cachedData as? CachedChannelData, cachedData.participantsSummary.memberCount ?? 0 >= 200 {
secondaryRightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: {
updateState { state in
return state.withUpdatedSearchingMembers(true)
}
})
}
} }
} }
var searchItem: ItemListControllerSearch?
if state.searchingMembers {
searchItem = ChannelMembersSearchItem(account: account, peerId: peerId, cancel: {
updateState { state in
return state.withUpdatedSearchingMembers(false)
}
}, openPeer: { peer, _ in
if let infoController = peerInfoController(account: account, peer: peer) {
pushControllerImpl?(infoController)
// arguments.pushController(infoController)
}
})
}
var emptyStateItem: ItemListControllerEmptyStateItem? var emptyStateItem: ItemListControllerEmptyStateItem?
if peers == nil { if peers == nil {
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
@ -482,8 +514,8 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo
let previous = previousPeers let previous = previousPeers
previousPeers = peers previousPeers = peers
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Channel_Subscribers_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Channel_Subscribers_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(entries: ChannelMembersControllerEntries(account: account, presentationData: presentationData, view: view, state: state, participants: peers), style: .blocks, emptyStateItem: emptyStateItem, animateChanges: previous != nil && peers != nil && previous!.count >= peers!.count) let listState = ItemListNodeState(entries: ChannelMembersControllerEntries(account: account, presentationData: presentationData, view: view, state: state, participants: peers), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && peers != nil && previous!.count >= peers!.count)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} |> afterDisposed { } |> afterDisposed {

View File

@ -7,6 +7,8 @@ import TelegramCore
enum ChannelMembersSearchMode { enum ChannelMembersSearchMode {
case searchMembers case searchMembers
case searchAdmins
case searchBanned
case banAndPromoteActions case banAndPromoteActions
case inviteActions case inviteActions
} }
@ -140,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
@ -184,6 +186,28 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
foundGroupMembers = .single([]) foundGroupMembers = .single([])
foundMembers = channelMembers(postbox: account.postbox, network: account.network, peerId: peerId, category: .recent(.search(query))) foundMembers = channelMembers(postbox: account.postbox, network: account.network, peerId: peerId, category: .recent(.search(query)))
|> map { $0 ?? [] } |> map { $0 ?? [] }
case .searchAdmins:
foundGroupMembers = Signal { subscriber in
let (disposable, listControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: account.postbox, network: account.network, peerId: peerId, searchQuery: query, updated: { state in
if case .ready = state.loadingState {
subscriber.putNext(state.list)
subscriber.putCompletion()
}
})
return disposable
} |> runOn(Queue.mainQueue())
foundMembers = .single([])
case .searchBanned:
foundGroupMembers = Signal { subscriber in
let (disposable, listControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.restrictedAndBanned(postbox: account.postbox, network: account.network, peerId: peerId, searchQuery: query, updated: { state in
if case .ready = state.loadingState {
subscriber.putNext(state.list)
subscriber.putCompletion()
}
})
return disposable
} |> runOn(Queue.mainQueue())
foundMembers = .single([])
} }
let foundContacts: Signal<[Peer], NoError> let foundContacts: Signal<[Peer], NoError>
@ -193,7 +217,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
foundContacts = account.postbox.searchContacts(query: query.lowercased()) foundContacts = account.postbox.searchContacts(query: query.lowercased())
foundRemotePeers = .single(([], [])) |> then(searchPeers(account: account, query: query) foundRemotePeers = .single(([], [])) |> then(searchPeers(account: account, query: query)
|> delay(0.2, queue: Queue.concurrentDefaultQueue())) |> delay(0.2, queue: Queue.concurrentDefaultQueue()))
case .searchMembers: case .searchMembers, .searchBanned, .searchAdmins:
foundContacts = .single([]) foundContacts = .single([])
foundRemotePeers = .single(([], [])) foundRemotePeers = .single(([], []))
} }
@ -203,10 +227,16 @@ 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)
case .searchMembers: case .searchMembers, .searchAdmins, .searchBanned:
break break
} }
@ -219,7 +249,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
switch mode { switch mode {
case .inviteActions, .banAndPromoteActions: case .inviteActions, .banAndPromoteActions:
section = .members section = .members
case .searchMembers: case .searchMembers, .searchBanned, .searchAdmins:
section = .none section = .none
} }
@ -231,6 +261,30 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
enabled = false enabled = false
} }
} }
switch mode {
case .searchAdmins:
switch participant.participant {
case .creator:
label = themeAndStrings.1.Channel_Management_LabelCreator
case let .member(_, _, adminInfo, _):
if let adminInfo = adminInfo {
if let peer = participant.peers[adminInfo.promotedBy] {
label = themeAndStrings.1.Channel_Management_PromotedBy(peer.displayTitle).0
}
}
}
case .searchBanned:
switch participant.participant {
case let .member(_, _, _, banInfo):
if let banInfo = banInfo, let peer = participant.peers[banInfo.restrictedBy] {
label = themeAndStrings.1.Channel_Management_RestrictedBy(peer.displayTitle).0
}
default:
break
}
default:
break
}
entries.append(ChannelMembersSearchEntry(index: index, content: .participant(participant, label, enabled), section: section)) entries.append(ChannelMembersSearchEntry(index: index, content: .participant(participant, label, enabled), section: section))
index += 1 index += 1
} }
@ -243,7 +297,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
switch mode { switch mode {
case .inviteActions, .banAndPromoteActions: case .inviteActions, .banAndPromoteActions:
section = .members section = .members
case .searchMembers: case .searchMembers, .searchBanned, .searchAdmins:
section = .none section = .none
} }
@ -255,6 +309,8 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
enabled = false enabled = false
} }
} }
entries.append(ChannelMembersSearchEntry(index: index, content: .participant(participant, label, enabled), section: section)) entries.append(ChannelMembersSearchEntry(index: index, content: .participant(participant, label, enabled), section: section))
index += 1 index += 1
} }

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

@ -309,6 +309,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

@ -1050,7 +1050,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
inputContextPanelNode.updateLayout(size: startPanelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: .immediate, interfaceState: self.chatPresentationInterfaceState) inputContextPanelNode.updateLayout(size: startPanelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: .immediate, interfaceState: self.chatPresentationInterfaceState)
} }
if !inputContextPanelNode.frame.equalTo(panelFrame) { if !inputContextPanelNode.frame.equalTo(panelFrame) || inputContextPanelNode.theme !== self.chatPresentationInterfaceState.theme {
transition.updateFrame(node: inputContextPanelNode, frame: panelFrame) transition.updateFrame(node: inputContextPanelNode, frame: panelFrame)
inputContextPanelNode.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition, interfaceState: self.chatPresentationInterfaceState) inputContextPanelNode.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition, interfaceState: self.chatPresentationInterfaceState)
} }

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,21 +239,25 @@ 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()
self.chatPresentationDataPromise.set(.single((ChatPresentationData(theme: ChatPresentationThemeData(theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper), fontSize: self.presentationData.fontSize, strings: self.presentationData.strings, timeFormat: self.presentationData.timeFormat)))) // self.chatPresentationDataPromise.set(.single(()))
self.chatPresentationDataPromise.set(account.telegramApplicationContext.presentationData |> map { presentationData in
return ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), fontSize: presentationData.fontSize, strings: self.presentationData.strings, timeFormat: presentationData.timeFormat)
})
self.floatingSections = true self.floatingSections = true
//self.preloadPages = false //self.preloadPages = false
let messageViewQueue = self.messageViewQueue let messageViewQueue = self.messageViewQueue
@ -279,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) {
@ -414,6 +510,7 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode {
self.dequeuedInitialTransitionOnLayout = true self.dequeuedInitialTransitionOnLayout = true
self.dequeueHistoryViewTransition() self.dequeueHistoryViewTransition()
} }
} }
public func disconnect() { public func disconnect() {

View File

@ -680,6 +680,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
strongSelf.forEachItemHeaderNode { itemHeaderNode in strongSelf.forEachItemHeaderNode { itemHeaderNode in
if let dateNode = itemHeaderNode as? ChatMessageDateHeaderNode { if let dateNode = itemHeaderNode as? ChatMessageDateHeaderNode {
dateNode.updateThemeAndStrings(theme: themeData, strings: presentationData.strings) dateNode.updateThemeAndStrings(theme: themeData, strings: presentationData.strings)
} else if let dateNode = itemHeaderNode as? ListMessageDateHeaderNode {
dateNode.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings)
} }
} }
strongSelf.chatPresentationDataPromise.set(.single(ChatPresentationData(theme: themeData, fontSize: presentationData.fontSize, strings: presentationData.strings, timeFormat: presentationData.timeFormat))) strongSelf.chatPresentationDataPromise.set(.single(ChatPresentationData(theme: themeData, fontSize: presentationData.fontSize, strings: presentationData.strings, timeFormat: presentationData.timeFormat)))

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

@ -12,10 +12,10 @@ class ChatInputContextPanelNode: ASDisplayNode {
let account: Account let account: Account
var interfaceInteraction: ChatPanelInterfaceInteraction? var interfaceInteraction: ChatPanelInterfaceInteraction?
var placement: ChatInputContextPanelPlacement = .overPanels var placement: ChatInputContextPanelPlacement = .overPanels
var theme: PresentationTheme
init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { init(account: Account, theme: PresentationTheme, strings: PresentationStrings) {
self.account = account self.account = account
self.theme = theme
super.init() super.init()
} }

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

@ -479,6 +479,35 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
} }
} }
override func updateHiddenMedia(_ media: [Media]?) -> Bool {
var mediaHidden = false
var currentMedia: Media?
if let item = item {
mediaLoop: for media in item.message.media {
if let media = media as? TelegramMediaAction {
switch media.action {
case let .photoUpdated(image):
currentMedia = image
break mediaLoop
default:
break
}
}
}
}
if let currentMedia = currentMedia, let media = media {
for item in media {
if item.isSemanticallyEqual(to: currentMedia) {
mediaHidden = true
break
}
}
}
self.imageNode?.isHidden = mediaHidden
return mediaHidden
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))) { override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))) {
let makeLabelLayout = TextNode.asyncLayout(self.labelNode) let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
@ -517,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
} }
} }
} }
@ -528,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)
@ -555,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

@ -115,7 +115,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self.panelButtonNode.addTarget(self, action: #selector(self.infoButtonPressed), forControlEvents: .touchUpInside) self.panelButtonNode.addTarget(self, action: #selector(self.infoButtonPressed), forControlEvents: .touchUpInside)
let (adminsDisposable, _) = self.account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: self.account.postbox, network: self.account.network, peerId: self.peer.id, updated: { [weak self] state in let (adminsDisposable, _) = self.account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: self.account.postbox, network: self.account.network, peerId: self.peer.id, searchQuery: nil, updated: { [weak self] state in
self?.adminsState = state self?.adminsState = state
}) })
self.adminsDisposable = adminsDisposable self.adminsDisposable = adminsDisposable
@ -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

@ -495,10 +495,29 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
var text: String = "" var text: String = ""
var entities: [MessageTextEntity] = [] var entities: [MessageTextEntity] = []
let isBroadcast: Bool
if let peer = peer as? TelegramChannel {
switch peer.info {
case .broadcast:
isBroadcast = true
case .group:
isBroadcast = false
}
} else {
isBroadcast = false
}
if case let .member(_, _, _, prevBanInfo) = prev.participant { if case let .member(_, _, _, prevBanInfo) = prev.participant {
if case let .member(_, _, _, newBanInfo) = new.participant { if case let .member(_, _, _, newBanInfo) = new.participant {
let newFlags = newBanInfo?.rights.flags ?? [] let newFlags = newBanInfo?.rights.flags ?? []
var addedRights = newBanInfo?.rights.flags ?? []
var removedRights:TelegramChannelBannedRightsFlags = []
if let prevBanInfo = prevBanInfo {
addedRights = addedRights.subtracting(prevBanInfo.rights.flags)
removedRights = prevBanInfo.rights.flags.subtracting(newBanInfo?.rights.flags ?? [])
}
if (prevBanInfo == nil || !prevBanInfo!.rights.flags.contains(.banReadMessages)) && newFlags.contains(.banReadMessages) { if (prevBanInfo == nil || !prevBanInfo!.rights.flags.contains(.banReadMessages)) && newFlags.contains(.banReadMessages) {
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageKickedName(new.peer.displayTitle) : self.presentationData.strings.Channel_AdminLog_MessageKickedNameUsername(new.peer.displayTitle, "@" + new.peer.addressName!), generateEntities: { index in appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageKickedName(new.peer.displayTitle) : self.presentationData.strings.Channel_AdminLog_MessageKickedNameUsername(new.peer.displayTitle, "@" + new.peer.addressName!), generateEntities: { index in
var result: [MessageTextEntityType] = [] var result: [MessageTextEntityType] = []
@ -510,6 +529,16 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
return result return result
}, to: &text, entities: &entities) }, to: &text, entities: &entities)
text += "\n" text += "\n"
} else if isBroadcast, newBanInfo == nil, prevBanInfo != nil {
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageUnkickedName(new.peer.displayTitle) : self.presentationData.strings.Channel_AdminLog_MessageUnkickedNameUsername(new.peer.displayTitle, "@" + new.peer.addressName!), generateEntities: { index in
var result: [MessageTextEntityType] = []
if index == 0 {
result.append(.TextMention(peerId: new.peer.id))
} else if index == 1 {
result.append(.Mention)
}
return result
}, to: &text, entities: &entities)
} else { } else {
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRestrictedName(new.peer.displayTitle) : self.presentationData.strings.Channel_AdminLog_MessageRestrictedNameUsername(new.peer.displayTitle, "@" + new.peer.addressName!), generateEntities: { index in appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRestrictedName(new.peer.displayTitle) : self.presentationData.strings.Channel_AdminLog_MessageRestrictedNameUsername(new.peer.displayTitle, "@" + new.peer.addressName!), generateEntities: { index in
var result: [MessageTextEntityType] = [] var result: [MessageTextEntityType] = []
@ -555,13 +584,12 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
] ]
for (flag, string) in order { for (flag, string) in order {
if prevFlags.contains(flag) != newFlags.contains(flag) { if addedRights.contains(flag) {
text += "\n" text += "\n-"
if prevFlags.contains(flag) { appendAttributedText(text: string, withEntities: [.Italic], to: &text, entities: &entities)
text += "+" }
} else { if removedRights.contains(flag) {
text += "-" text += "\n+"
}
appendAttributedText(text: string, withEntities: [.Italic], to: &text, entities: &entities) appendAttributedText(text: string, withEntities: [.Italic], to: &text, entities: &entities)
} }
} }

View File

@ -25,6 +25,10 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable {
return CommandChatInputContextPanelEntryStableId(command: self.command) return CommandChatInputContextPanelEntryStableId(command: self.command)
} }
func withUpdatedTheme(_ theme: PresentationTheme) -> CommandChatInputContextPanelEntry {
return CommandChatInputContextPanelEntry(index: self.index, command: self.command, theme: theme)
}
static func ==(lhs: CommandChatInputContextPanelEntry, rhs: CommandChatInputContextPanelEntry) -> Bool { static func ==(lhs: CommandChatInputContextPanelEntry, rhs: CommandChatInputContextPanelEntry) -> Bool {
return lhs.index == rhs.index && lhs.command == rhs.command && lhs.theme === rhs.theme return lhs.index == rhs.index && lhs.command == rhs.command && lhs.theme === rhs.theme
} }
@ -55,7 +59,6 @@ private func preparedTransition(from fromEntries: [CommandChatInputContextPanelE
} }
final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
private var theme: PresentationTheme
private let listView: ListView private let listView: ListView
private var currentEntries: [CommandChatInputContextPanelEntry]? private var currentEntries: [CommandChatInputContextPanelEntry]?
@ -64,7 +67,6 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
private var validLayout: (CGSize, CGFloat, CGFloat)? private var validLayout: (CGSize, CGFloat, CGFloat)?
override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme
self.listView = ListView() self.listView = ListView()
self.listView.isOpaque = false self.listView.isOpaque = false
@ -94,9 +96,13 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
entries.append(entry) entries.append(entry)
index += 1 index += 1
} }
prepareTransition(from: self.currentEntries ?? [], to: entries)
}
private func prepareTransition(from: [CommandChatInputContextPanelEntry]? , to: [CommandChatInputContextPanelEntry]) {
let firstTime = self.currentEntries == nil let firstTime = self.currentEntries == nil
let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, account: self.account, commandSelected: { [weak self] command, sendImmediately in let transition = preparedTransition(from: from ?? [], to: to, account: self.account, commandSelected: { [weak self] command, sendImmediately in
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
if sendImmediately { if sendImmediately {
interfaceInteraction.sendBotCommand(command.peer, "/" + command.command.text) interfaceInteraction.sendBotCommand(command.peer, "/" + command.command.text)
@ -126,7 +132,7 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
} }
} }
}) })
self.currentEntries = entries self.currentEntries = to
self.enqueueTransition(transition, firstTime: firstTime) self.enqueueTransition(transition, firstTime: firstTime)
} }
@ -226,6 +232,14 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
self.dequeueTransition() self.dequeueTransition()
} }
} }
if self.theme !== interfaceState.theme {
self.theme = interfaceState.theme
self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor
let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? []
prepareTransition(from: self.currentEntries, to: new)
}
} }
override func animateOut(completion: @escaping () -> Void) { override func animateOut(completion: @escaping () -> Void) {

View File

@ -19,6 +19,10 @@ private struct EmojisChatInputContextPanelEntry: Comparable, Identifiable {
return EmojisChatInputContextPanelEntryStableId(symbol: self.symbol, text: self.text) return EmojisChatInputContextPanelEntryStableId(symbol: self.symbol, text: self.text)
} }
func withUpdatedTheme(_ theme: PresentationTheme) -> EmojisChatInputContextPanelEntry {
return EmojisChatInputContextPanelEntry(index: self.index, theme: theme, symbol: self.symbol, text: self.text)
}
static func ==(lhs: EmojisChatInputContextPanelEntry, rhs: EmojisChatInputContextPanelEntry) -> Bool { static func ==(lhs: EmojisChatInputContextPanelEntry, rhs: EmojisChatInputContextPanelEntry) -> Bool {
return lhs.index == rhs.index && lhs.symbol == rhs.symbol && lhs.text == rhs.text && lhs.theme === rhs.theme return lhs.index == rhs.index && lhs.symbol == rhs.symbol && lhs.text == rhs.text && lhs.theme === rhs.theme
} }
@ -49,7 +53,6 @@ private func preparedTransition(from fromEntries: [EmojisChatInputContextPanelEn
} }
final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode { final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
private var theme: PresentationTheme
private let listView: ListView private let listView: ListView
private var currentEntries: [EmojisChatInputContextPanelEntry]? private var currentEntries: [EmojisChatInputContextPanelEntry]?
@ -58,7 +61,6 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
private var validLayout: (CGSize, CGFloat, CGFloat)? private var validLayout: (CGSize, CGFloat, CGFloat)?
override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme
self.listView = ListView() self.listView = ListView()
self.listView.isOpaque = false self.listView.isOpaque = false
@ -88,9 +90,13 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
entries.append(entry) entries.append(entry)
index += 1 index += 1
} }
prepareTransition(from: self.currentEntries, to: entries)
}
private func prepareTransition(from: [EmojisChatInputContextPanelEntry]? , to: [EmojisChatInputContextPanelEntry]) {
let firstTime = self.currentEntries == nil let firstTime = self.currentEntries == nil
let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, account: self.account, hashtagSelected: { [weak self] text in let transition = preparedTransition(from: from ?? [], to: to, account: self.account, hashtagSelected: { [weak self] text in
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
interfaceInteraction.updateTextInputStateAndMode { textInputState, inputMode in interfaceInteraction.updateTextInputStateAndMode { textInputState, inputMode in
var hashtagQueryRange: NSRange? var hashtagQueryRange: NSRange?
@ -119,7 +125,7 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
} }
} }
}) })
self.currentEntries = entries self.currentEntries = to
self.enqueueTransition(transition, firstTime: firstTime) self.enqueueTransition(transition, firstTime: firstTime)
} }
@ -219,6 +225,14 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
self.dequeueTransition() self.dequeueTransition()
} }
} }
if self.theme !== interfaceState.theme {
self.theme = interfaceState.theme
self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor
let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? []
prepareTransition(from: self.currentEntries, to: new)
}
} }
override func animateOut(completion: @escaping () -> Void) { override func animateOut(completion: @escaping () -> Void) {

View File

@ -65,6 +65,7 @@ final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDelegate {
} }
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 {
@ -59,7 +95,7 @@ final class GridMessageItemSection: GridSection {
func isEqual(to: GridSection) -> Bool { func isEqual(to: GridSection) -> Bool {
if let to = to as? GridMessageItemSection { if let to = to as? GridMessageItemSection {
return self.roundedTimestamp == to.roundedTimestamp return self.roundedTimestamp == to.roundedTimestamp && theme === to.theme
} else { } else {
return false return false
} }
@ -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,10 +180,11 @@ 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?
@ -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

@ -1639,11 +1639,11 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
var searchItem: ItemListControllerSearch? var searchItem: ItemListControllerSearch?
if state.searchingMembers { if state.searchingMembers {
searchItem = GroupInfoSearchItem(account: account, peerId: peerId, cancel: { searchItem = ChannelMembersSearchItem(account: account, peerId: peerId, cancel: {
updateState { state in updateState { state in
return state.withUpdatedSearchingMembers(false) return state.withUpdatedSearchingMembers(false)
} }
}, openPeer: { peer in }, openPeer: { peer, _ in
if let infoController = peerInfoController(account: account, peer: peer) { if let infoController = peerInfoController(account: account, peer: peer) {
arguments.pushController(infoController) arguments.pushController(infoController)
} }

View File

@ -5,21 +5,22 @@ import Postbox
import TelegramCore import TelegramCore
import SwiftSignalKit import SwiftSignalKit
final class GroupInfoSearchItem: ItemListControllerSearch { final class ChannelMembersSearchItem: ItemListControllerSearch {
let account: Account let account: Account
let peerId: PeerId let peerId: PeerId
let cancel: () -> Void let cancel: () -> Void
let openPeer: (Peer) -> Void let openPeer: (Peer, RenderedChannelParticipant?) -> Void
let searchMode: ChannelMembersSearchMode
init(account: Account, peerId: PeerId, cancel: @escaping () -> Void, openPeer: @escaping (Peer) -> Void) { init(account: Account, peerId: PeerId, searchMode: ChannelMembersSearchMode = .searchMembers, cancel: @escaping () -> Void, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) {
self.account = account self.account = account
self.peerId = peerId self.peerId = peerId
self.cancel = cancel self.cancel = cancel
self.openPeer = openPeer self.openPeer = openPeer
self.searchMode = searchMode
} }
func isEqual(to: ItemListControllerSearch) -> Bool { func isEqual(to: ItemListControllerSearch) -> Bool {
if let to = to as? GroupInfoSearchItem { if let to = to as? ChannelMembersSearchItem {
if self.account !== to.account { if self.account !== to.account {
return false return false
} }
@ -42,16 +43,16 @@ final class GroupInfoSearchItem: ItemListControllerSearch {
} }
func node(current: ItemListControllerSearchNode?) -> ItemListControllerSearchNode { func node(current: ItemListControllerSearchNode?) -> ItemListControllerSearchNode {
return GroupInfoSearchItemNode(account: self.account, peerId: self.peerId, openPeer: self.openPeer, cancel: self.cancel) return ChannelMembersSearchItemNode(account: self.account, peerId: self.peerId, searchMode: self.searchMode, openPeer: self.openPeer, cancel: self.cancel)
} }
} }
private final class GroupInfoSearchItemNode: ItemListControllerSearchNode { private final class ChannelMembersSearchItemNode: ItemListControllerSearchNode {
private let containerNode: ChannelMembersSearchContainerNode private let containerNode: ChannelMembersSearchContainerNode
init(account: Account, peerId: PeerId, openPeer: @escaping (Peer) -> 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: .searchMembers, openPeer: { peer, _ in self.containerNode = ChannelMembersSearchContainerNode(account: account, peerId: peerId, mode: searchMode, filters: [], openPeer: { peer, participant in
openPeer(peer) openPeer(peer, participant)
}) })
self.containerNode.cancel = { self.containerNode.cancel = {
cancel() cancel()

View File

@ -25,6 +25,10 @@ private struct HashtagChatInputContextPanelEntry: Comparable, Identifiable {
return HashtagChatInputContextPanelEntryStableId(text: self.text) return HashtagChatInputContextPanelEntryStableId(text: self.text)
} }
func withUpdatedTheme(_ theme: PresentationTheme) -> HashtagChatInputContextPanelEntry {
return HashtagChatInputContextPanelEntry(index: self.index, theme: theme, text: self.text)
}
static func ==(lhs: HashtagChatInputContextPanelEntry, rhs: HashtagChatInputContextPanelEntry) -> Bool { static func ==(lhs: HashtagChatInputContextPanelEntry, rhs: HashtagChatInputContextPanelEntry) -> Bool {
return lhs.index == rhs.index && lhs.text == rhs.text && lhs.theme === rhs.theme return lhs.index == rhs.index && lhs.text == rhs.text && lhs.theme === rhs.theme
} }
@ -55,7 +59,6 @@ private func preparedTransition(from fromEntries: [HashtagChatInputContextPanelE
} }
final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
private var theme: PresentationTheme
private let listView: ListView private let listView: ListView
private var currentEntries: [HashtagChatInputContextPanelEntry]? private var currentEntries: [HashtagChatInputContextPanelEntry]?
@ -64,7 +67,6 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
private var validLayout: (CGSize, CGFloat, CGFloat)? private var validLayout: (CGSize, CGFloat, CGFloat)?
override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme
self.listView = ListView() self.listView = ListView()
self.listView.isOpaque = false self.listView.isOpaque = false
@ -94,9 +96,12 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
entries.append(entry) entries.append(entry)
index += 1 index += 1
} }
prepareTransition(from: self.currentEntries ?? [], to: entries)
}
let firstTime = self.currentEntries == nil private func prepareTransition(from: [HashtagChatInputContextPanelEntry]? , to: [HashtagChatInputContextPanelEntry]) {
let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, account: self.account, hashtagSelected: { [weak self] text in let firstTime = from == nil
let transition = preparedTransition(from: from ?? [], to: to, account: self.account, hashtagSelected: { [weak self] text in
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
interfaceInteraction.updateTextInputStateAndMode { textInputState, inputMode in interfaceInteraction.updateTextInputStateAndMode { textInputState, inputMode in
var hashtagQueryRange: NSRange? var hashtagQueryRange: NSRange?
@ -122,7 +127,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
} }
} }
}) })
self.currentEntries = entries self.currentEntries = to
self.enqueueTransition(transition, firstTime: firstTime) self.enqueueTransition(transition, firstTime: firstTime)
} }
@ -222,6 +227,14 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
self.dequeueTransition() self.dequeueTransition()
} }
} }
if self.theme !== interfaceState.theme {
self.theme = interfaceState.theme
self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor
let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? []
prepareTransition(from: self.currentEntries, to: new)
}
} }
override func animateOut(completion: @escaping () -> Void) { override func animateOut(completion: @escaping () -> Void) {

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

@ -67,7 +67,6 @@ private func preparedTransition(from fromEntries: [HorizontalListContextResultsC
} }
final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputContextPanelNode { final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputContextPanelNode {
private var theme: PresentationTheme
private var strings: PresentationStrings private var strings: PresentationStrings
private let listView: ListView private let listView: ListView
@ -82,7 +81,6 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
private var hasValidLayout = false private var hasValidLayout = false
override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme
self.strings = strings self.strings = strings
self.separatorNode = ASDisplayNode() self.separatorNode = ASDisplayNode()
@ -326,6 +324,12 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
self.dequeueTransition() self.dequeueTransition()
} }
} }
if self.theme !== interfaceState.theme {
self.theme = interfaceState.theme
self.separatorNode.backgroundColor = theme.list.itemPlainSeparatorColor
self.listView.backgroundColor = theme.list.plainBackgroundColor
}
} }
override func animateOut(completion: @escaping () -> Void) { override func animateOut(completion: @escaping () -> Void) {

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,6 +193,9 @@ 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?
@ -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

@ -7,6 +7,38 @@ import Display
final class HorizontalStickersChatContextPanelInteraction { final class HorizontalStickersChatContextPanelInteraction {
var previewedStickerItem: StickerPackItem? var previewedStickerItem: StickerPackItem?
} }
private func backgroundCenterImage(_ theme: PresentationTheme) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 82.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(theme.list.itemPlainSeparatorColor.cgColor)
context.setFillColor(theme.list.plainBackgroundColor.cgColor)
let lineWidth = UIScreenPixel
context.setLineWidth(lineWidth)
context.translateBy(x: 460.5, y: 364)
let _ = try? drawSvgPath(context, path: "M-490.476836,-365 L-394.167708,-365 L-394.167708,-291.918214 C-394.167708,-291.918214 -383.538396,-291.918214 -397.691655,-291.918214 C-402.778486,-291.918214 -424.555168,-291.918214 -434.037301,-291.918214 C-440.297129,-291.918214 -440.780682,-283.5 -445.999879,-283.5 C-450.393041,-283.5 -452.491241,-291.918214 -456.502636,-291.918214 C-465.083339,-291.918214 -476.209155,-291.918214 -483.779021,-291.918214 C-503.033963,-291.918214 -490.476836,-291.918214 -490.476836,-291.918214 L-490.476836,-365 ")
context.fillPath()
context.translateBy(x: 0.0, y: lineWidth / 2.0)
let _ = try? drawSvgPath(context, path: "M-490.476836,-365 L-394.167708,-365 L-394.167708,-291.918214 C-394.167708,-291.918214 -383.538396,-291.918214 -397.691655,-291.918214 C-402.778486,-291.918214 -424.555168,-291.918214 -434.037301,-291.918214 C-440.297129,-291.918214 -440.780682,-283.5 -445.999879,-283.5 C-450.393041,-283.5 -452.491241,-291.918214 -456.502636,-291.918214 C-465.083339,-291.918214 -476.209155,-291.918214 -483.779021,-291.918214 C-503.033963,-291.918214 -490.476836,-291.918214 -490.476836,-291.918214 L-490.476836,-365 ")
context.strokePath()
context.translateBy(x: -460.5, y: -lineWidth / 2.0 - 364.0)
context.move(to: CGPoint(x: 0.0, y: lineWidth / 2.0))
context.addLine(to: CGPoint(x: size.width, y: lineWidth / 2.0))
context.strokePath()
})
}
private func backgroundLeftImage(_ theme: PresentationTheme) -> UIImage? {
return generateImage(CGSize(width: 8.0, height: 16.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(theme.list.itemPlainSeparatorColor.cgColor)
context.setFillColor(theme.list.plainBackgroundColor.cgColor)
let lineWidth = UIScreenPixel
context.setLineWidth(lineWidth)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.height, height: size.height)))
context.strokeEllipse(in: CGRect(origin: CGPoint(x: lineWidth / 2.0, y: lineWidth / 2.0), size: CGSize(width: size.height - lineWidth, height: size.height - lineWidth)))
})?.stretchableImage(withLeftCapWidth: 8, topCapHeight: 8)
}
private struct StickerEntry: Identifiable, Comparable { private struct StickerEntry: Identifiable, Comparable {
let index: Int let index: Int
@ -52,7 +84,6 @@ private func preparedGridEntryTransition(account: Account, from fromEntries: [St
} }
final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
private var theme: PresentationTheme
private let backgroundLeftNode: ASImageNode private let backgroundLeftNode: ASImageNode
private let backgroundNode: ASImageNode private let backgroundNode: ASImageNode
@ -69,52 +100,23 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
private var stickerPreviewController: StickerPreviewController? private var stickerPreviewController: StickerPreviewController?
override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme
let backgroundCenterImage = generateImage(CGSize(width: 30.0, height: 82.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(theme.list.itemPlainSeparatorColor.cgColor)
context.setFillColor(theme.list.plainBackgroundColor.cgColor)
let lineWidth = UIScreenPixel
context.setLineWidth(lineWidth)
context.translateBy(x: 460.5, y: 364)
let _ = try? drawSvgPath(context, path: "M-490.476836,-365 L-394.167708,-365 L-394.167708,-291.918214 C-394.167708,-291.918214 -383.538396,-291.918214 -397.691655,-291.918214 C-402.778486,-291.918214 -424.555168,-291.918214 -434.037301,-291.918214 C-440.297129,-291.918214 -440.780682,-283.5 -445.999879,-283.5 C-450.393041,-283.5 -452.491241,-291.918214 -456.502636,-291.918214 C-465.083339,-291.918214 -476.209155,-291.918214 -483.779021,-291.918214 C-503.033963,-291.918214 -490.476836,-291.918214 -490.476836,-291.918214 L-490.476836,-365 ")
context.fillPath()
context.translateBy(x: 0.0, y: lineWidth / 2.0)
let _ = try? drawSvgPath(context, path: "M-490.476836,-365 L-394.167708,-365 L-394.167708,-291.918214 C-394.167708,-291.918214 -383.538396,-291.918214 -397.691655,-291.918214 C-402.778486,-291.918214 -424.555168,-291.918214 -434.037301,-291.918214 C-440.297129,-291.918214 -440.780682,-283.5 -445.999879,-283.5 C-450.393041,-283.5 -452.491241,-291.918214 -456.502636,-291.918214 C-465.083339,-291.918214 -476.209155,-291.918214 -483.779021,-291.918214 C-503.033963,-291.918214 -490.476836,-291.918214 -490.476836,-291.918214 L-490.476836,-365 ")
context.strokePath()
context.translateBy(x: -460.5, y: -lineWidth / 2.0 - 364.0)
context.move(to: CGPoint(x: 0.0, y: lineWidth / 2.0))
context.addLine(to: CGPoint(x: size.width, y: lineWidth / 2.0))
context.strokePath()
})
let backgroundLeftImage = generateImage(CGSize(width: 8.0, height: 16.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(theme.list.itemPlainSeparatorColor.cgColor)
context.setFillColor(theme.list.plainBackgroundColor.cgColor)
let lineWidth = UIScreenPixel
context.setLineWidth(lineWidth)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.height, height: size.height)))
context.strokeEllipse(in: CGRect(origin: CGPoint(x: lineWidth / 2.0, y: lineWidth / 2.0), size: CGSize(width: size.height - lineWidth, height: size.height - lineWidth)))
})?.stretchableImage(withLeftCapWidth: 8, topCapHeight: 8)
self.backgroundNode = ASImageNode() self.backgroundNode = ASImageNode()
self.backgroundNode.displayWithoutProcessing = true self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.displaysAsynchronously = false self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.image = backgroundCenterImage self.backgroundNode.image = backgroundCenterImage(theme)
self.backgroundLeftNode = ASImageNode() self.backgroundLeftNode = ASImageNode()
self.backgroundLeftNode.displayWithoutProcessing = true self.backgroundLeftNode.displayWithoutProcessing = true
self.backgroundLeftNode.displaysAsynchronously = false self.backgroundLeftNode.displaysAsynchronously = false
self.backgroundLeftNode.image = backgroundLeftImage self.backgroundLeftNode.image = backgroundLeftImage(theme)
self.backgroundRightNode = ASImageNode() self.backgroundRightNode = ASImageNode()
self.backgroundRightNode.displayWithoutProcessing = true self.backgroundRightNode.displayWithoutProcessing = true
self.backgroundRightNode.displaysAsynchronously = false self.backgroundRightNode.displaysAsynchronously = false
self.backgroundRightNode.image = backgroundLeftImage self.backgroundRightNode.image = backgroundLeftImage(theme)
self.backgroundRightNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0) self.backgroundRightNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
self.clippingNode = ASDisplayNode() self.clippingNode = ASDisplayNode()
@ -218,6 +220,16 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.dequeueTransitions() self.dequeueTransitions()
} }
if self.theme !== interfaceState.theme {
self.theme = interfaceState.theme
self.backgroundNode.image = backgroundCenterImage(theme)
self.backgroundLeftNode.image = backgroundLeftImage(theme)
self.backgroundRightNode.image = backgroundLeftImage(theme)
// if let currentEntries = self.currentEntries {
// self.updateToEntries(entries: currentEntries, forceUpdate: true)
// }
}
} }
override func animateOut(completion: @escaping () -> Void) { override func animateOut(completion: @escaping () -> Void) {

View File

@ -75,6 +75,19 @@ final class ListMessageDateHeaderNode: ListViewItemHeaderNode {
self.titleNode.truncationMode = .byTruncatingTail self.titleNode.truncationMode = .byTruncatingTail
} }
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme
if let attributedString = self.titleNode.attributedText?.mutableCopy() as? NSMutableAttributedString {
attributedString.addAttribute(NSAttributedStringKey.foregroundColor, value: theme.list.itemPrimaryTextColor, range: NSMakeRange(0, attributedString.length))
self.titleNode.attributedText = attributedString
}
self.strings = strings
self.backgroundNode.backgroundColor = theme.list.plainBackgroundColor.withAlphaComponent(0.9)
self.setNeedsLayout()
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
let titleSize = self.titleNode.measure(CGSize(width: size.width - leftInset - rightInset - 24.0, height: CGFloat.greatestFiniteMagnitude)) let titleSize = self.titleNode.measure(CGSize(width: size.width - leftInset - rightInset - 24.0, height: CGFloat.greatestFiniteMagnitude))
self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset + 12.0, y: 8.0), size: titleSize) self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset + 12.0, y: 8.0), size: titleSize)

View File

@ -474,6 +474,7 @@ final class ListMessageFileItemNode: ListMessageNode {
strongSelf.progressNode.updateTheme(RadialProgressTheme(backgroundColor: item.theme.list.itemAccentColor, foregroundColor: item.theme.list.plainBackgroundColor, icon: nil)) strongSelf.progressNode.updateTheme(RadialProgressTheme(backgroundColor: item.theme.list.itemAccentColor, foregroundColor: item.theme.list.plainBackgroundColor, icon: nil))
strongSelf.linearProgressNode.backgroundColor = item.theme.list.itemAccentColor strongSelf.linearProgressNode.backgroundColor = item.theme.list.itemAccentColor
} }
if let (selectionWidth, selectionApply) = selectionNodeWidthAndApply { if let (selectionWidth, selectionApply) = selectionNodeWidthAndApply {

View File

@ -7,22 +7,21 @@ import Display
private struct MentionChatInputContextPanelEntry: Comparable, Identifiable { private struct MentionChatInputContextPanelEntry: Comparable, Identifiable {
let index: Int let index: Int
let peer: Peer let peer: Peer
let theme: PresentationTheme
var stableId: Int64 { var stableId: Int64 {
return self.peer.id.toInt64() return self.peer.id.toInt64()
} }
static func ==(lhs: MentionChatInputContextPanelEntry, rhs: MentionChatInputContextPanelEntry) -> Bool { static func ==(lhs: MentionChatInputContextPanelEntry, rhs: MentionChatInputContextPanelEntry) -> Bool {
return lhs.index == rhs.index && lhs.peer.isEqual(rhs.peer) && lhs.theme === rhs.theme return lhs.index == rhs.index && lhs.peer.isEqual(rhs.peer)
} }
static func <(lhs: MentionChatInputContextPanelEntry, rhs: MentionChatInputContextPanelEntry) -> Bool { static func <(lhs: MentionChatInputContextPanelEntry, rhs: MentionChatInputContextPanelEntry) -> Bool {
return lhs.index < rhs.index return lhs.index < rhs.index
} }
func item(account: Account, inverted: Bool, peerSelected: @escaping (Peer) -> Void) -> ListViewItem { func item(account: Account, theme: PresentationTheme, inverted: Bool, peerSelected: @escaping (Peer) -> Void) -> ListViewItem {
return MentionChatInputPanelItem(account: account, theme: self.theme, inverted: inverted, peer: self.peer, peerSelected: peerSelected) return MentionChatInputPanelItem(account: account, theme: theme, inverted: inverted, peer: self.peer, peerSelected: peerSelected)
} }
} }
@ -32,12 +31,12 @@ private struct CommandChatInputContextPanelTransition {
let updates: [ListViewUpdateItem] let updates: [ListViewUpdateItem]
} }
private func preparedTransition(from fromEntries: [MentionChatInputContextPanelEntry], to toEntries: [MentionChatInputContextPanelEntry], account: Account, inverted: Bool, forceUpdate: Bool, peerSelected: @escaping (Peer) -> Void) -> CommandChatInputContextPanelTransition { private func preparedTransition(from fromEntries: [MentionChatInputContextPanelEntry], to toEntries: [MentionChatInputContextPanelEntry], account: Account, theme: PresentationTheme, inverted: Bool, forceUpdate: Bool, peerSelected: @escaping (Peer) -> Void) -> CommandChatInputContextPanelTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries, allUpdated: forceUpdate) let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries, allUpdated: forceUpdate)
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(account: account, inverted: inverted, peerSelected: peerSelected), directionHint: nil) } let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, inverted: inverted, peerSelected: peerSelected), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, inverted: inverted, peerSelected: peerSelected), directionHint: nil) } let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, inverted: inverted, peerSelected: peerSelected), directionHint: nil) }
return CommandChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates) return CommandChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates)
} }
@ -50,8 +49,6 @@ enum MentionChatInputContextPanelMode {
final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
let mode: MentionChatInputContextPanelMode let mode: MentionChatInputContextPanelMode
private var theme: PresentationTheme
private let listView: ListView private let listView: ListView
private var currentEntries: [MentionChatInputContextPanelEntry]? private var currentEntries: [MentionChatInputContextPanelEntry]?
@ -59,7 +56,6 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
private var validLayout: (CGSize, CGFloat, CGFloat)? private var validLayout: (CGSize, CGFloat, CGFloat)?
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, mode: MentionChatInputContextPanelMode) { init(account: Account, theme: PresentationTheme, strings: PresentationStrings, mode: MentionChatInputContextPanelMode) {
self.theme = theme
self.mode = mode self.mode = mode
self.listView = ListView() self.listView = ListView()
@ -91,7 +87,7 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
continue continue
} }
peerIdSet.insert(peerId) peerIdSet.insert(peerId)
entries.append(MentionChatInputContextPanelEntry(index: index, peer: peer, theme: self.theme)) entries.append(MentionChatInputContextPanelEntry(index: index, peer: peer))
index += 1 index += 1
} }
self.updateToEntries(entries: entries, forceUpdate: false) self.updateToEntries(entries: entries, forceUpdate: false)
@ -99,7 +95,7 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
private func updateToEntries(entries: [MentionChatInputContextPanelEntry], forceUpdate: Bool) { private func updateToEntries(entries: [MentionChatInputContextPanelEntry], forceUpdate: Bool) {
let firstTime = self.currentEntries == nil let firstTime = self.currentEntries == nil
let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, account: self.account, inverted: self.mode == .search, forceUpdate: forceUpdate, peerSelected: { [weak self] peer in let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, account: self.account, theme: self.theme, inverted: self.mode == .search, forceUpdate: forceUpdate, peerSelected: { [weak self] peer in
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
switch strongSelf.mode { switch strongSelf.mode {
case .input: case .input:

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,13 @@ 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) {

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

@ -6,8 +6,8 @@ import SwiftSignalKit
enum PeerChannelMemberContextKey: Hashable { enum PeerChannelMemberContextKey: Hashable {
case recent case recent
case recentSearch(String) case recentSearch(String)
case admins case admins(String?)
case restrictedAndBanned case restrictedAndBanned(String?)
} }
private final class PeerChannelMemberCategoriesContextsManagerImpl { private final class PeerChannelMemberCategoriesContextsManagerImpl {
@ -89,12 +89,12 @@ final class PeerChannelMemberCategoriesContextsManager {
return self.getContext(postbox: postbox, network: network, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated) return self.getContext(postbox: postbox, network: network, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated)
} }
func admins(postbox: Postbox, network: Network, peerId: PeerId, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { func admins(postbox: Postbox, network: Network, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
return self.getContext(postbox: postbox, network: network, peerId: peerId, key: .admins, requestUpdate: true, updated: updated) return self.getContext(postbox: postbox, network: network, peerId: peerId, key: .admins(searchQuery), requestUpdate: true, updated: updated)
} }
func restrictedAndBanned(postbox: Postbox, network: Network, peerId: PeerId, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { func restrictedAndBanned(postbox: Postbox, network: Network, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
return self.getContext(postbox: postbox, network: network, peerId: peerId, key: .restrictedAndBanned, requestUpdate: true, updated: updated) return self.getContext(postbox: postbox, network: network, peerId: peerId, key: .restrictedAndBanned(searchQuery), requestUpdate: true, updated: updated)
} }
func updateMemberBannedRights(account: Account, peerId: PeerId, memberId: PeerId, bannedRights: TelegramChannelBannedRights?) -> Signal<Void, NoError> { func updateMemberBannedRights(account: Account, peerId: PeerId, memberId: PeerId, bannedRights: TelegramChannelBannedRights?) -> Signal<Void, NoError> {

View File

@ -28,6 +28,7 @@ public class PeerMediaCollectionController: TelegramController {
private var rightNavigationButton: PeerMediaCollectionNavigationButton? private var rightNavigationButton: PeerMediaCollectionNavigationButton?
private let galleryHiddenMesageAndMediaDisposable = MetaDisposable() private let galleryHiddenMesageAndMediaDisposable = MetaDisposable()
private var presentationDataDisposable:Disposable?
private var controllerInteraction: ChatControllerInteraction? private var controllerInteraction: ChatControllerInteraction?
private var interfaceInteraction: ChatPanelInterfaceInteraction? private var interfaceInteraction: ChatPanelInterfaceInteraction?
@ -62,6 +63,21 @@ public class PeerMediaCollectionController: TelegramController {
} }
} }
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme
let previousStrings = strongSelf.presentationData.strings
let previousChatWallpaper = strongSelf.presentationData.chatWallpaper
strongSelf.presentationData = presentationData
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings || presentationData.chatWallpaper != previousChatWallpaper {
strongSelf.themeAndStringsUpdated()
}
}
})
let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message in let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message in
if let strongSelf = self, strongSelf.isNodeLoaded, let galleryMessage = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id) { if let strongSelf = self, strongSelf.isNodeLoaded, let galleryMessage = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id) {
guard let navigationController = strongSelf.navigationController as? NavigationController else { guard let navigationController = strongSelf.navigationController as? NavigationController else {
@ -88,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
@ -412,6 +427,19 @@ public class PeerMediaCollectionController: TelegramController {
})) }))
} }
private func themeAndStringsUpdated() {
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
// self.chatTitleView?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings)
self.updateInterfaceState(animated: false, { state in
var state = state
state = state.updatedTheme(self.presentationData.theme)
state = state.updatedStrings(self.presentationData.strings)
return state
})
}
required public init(coder aDecoder: NSCoder) { required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@ -422,6 +450,7 @@ public class PeerMediaCollectionController: TelegramController {
self.galleryHiddenMesageAndMediaDisposable.dispose() self.galleryHiddenMesageAndMediaDisposable.dispose()
self.messageContextDisposable.dispose() self.messageContextDisposable.dispose()
self.resolveUrlDisposable?.dispose() self.resolveUrlDisposable?.dispose()
self.presentationDataDisposable?.dispose()
} }
var mediaCollectionDisplayNode: PeerMediaCollectionControllerNode { var mediaCollectionDisplayNode: PeerMediaCollectionControllerNode {
@ -495,6 +524,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)
} }
@ -523,6 +553,7 @@ public class PeerMediaCollectionController: TelegramController {
} }
self.mediaCollectionDisplayNode.selectedMessages = updatedInterfaceState.selectionState?.selectedIds self.mediaCollectionDisplayNode.selectedMessages = updatedInterfaceState.selectionState?.selectedIds
view.disablesInteractiveTransitionGestureRecognizer = updatedInterfaceState.selectionState != nil
} }
} }
} }

View File

@ -211,7 +211,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode {
} }
} }
let sectionsHeight = self.sectionsNode.updateLayout(width: layout.size.width, additionalInset: additionalInset, transition: transition) let sectionsHeight = self.sectionsNode.updateLayout(width: layout.size.width, additionalInset: additionalInset, transition: transition, interfaceState: self.mediaCollectionInterfaceState)
var sectionOffset: CGFloat = 0.0 var sectionOffset: CGFloat = 0.0
if navigationBarHeight.isZero { if navigationBarHeight.isZero {
sectionOffset = -sectionsHeight sectionOffset = -sectionsHeight
@ -243,7 +243,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode {
} else { } else {
let selectionPanelBackgroundNode = ASDisplayNode() let selectionPanelBackgroundNode = ASDisplayNode()
selectionPanelBackgroundNode.isLayerBacked = true selectionPanelBackgroundNode.isLayerBacked = true
selectionPanelBackgroundNode.backgroundColor = self.presentationData.theme.chat.inputPanel.panelBackgroundColor selectionPanelBackgroundNode.backgroundColor = self.mediaCollectionInterfaceState.theme.chat.inputPanel.panelBackgroundColor
self.addSubnode(selectionPanelBackgroundNode) self.addSubnode(selectionPanelBackgroundNode)
self.selectionPanelBackgroundNode = selectionPanelBackgroundNode self.selectionPanelBackgroundNode = selectionPanelBackgroundNode
@ -258,7 +258,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode {
let selectionPanelSeparatorNode = ASDisplayNode() let selectionPanelSeparatorNode = ASDisplayNode()
selectionPanelSeparatorNode.isLayerBacked = true selectionPanelSeparatorNode.isLayerBacked = true
selectionPanelSeparatorNode.backgroundColor = self.presentationData.theme.chat.inputPanel.panelStrokeColor selectionPanelSeparatorNode.backgroundColor = self.mediaCollectionInterfaceState.theme.chat.inputPanel.panelStrokeColor
self.addSubnode(selectionPanelSeparatorNode) self.addSubnode(selectionPanelSeparatorNode)
self.selectionPanelSeparatorNode = selectionPanelSeparatorNode self.selectionPanelSeparatorNode = selectionPanelSeparatorNode
@ -305,7 +305,10 @@ class PeerMediaCollectionControllerNode: ASDisplayNode {
self.historyNode.bounds = CGRect(x: previousBounds.origin.x, y: previousBounds.origin.y, width: layout.size.width, height: layout.size.height) self.historyNode.bounds = CGRect(x: previousBounds.origin.x, y: previousBounds.origin.y, width: layout.size.width, height: layout.size.height)
self.historyNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) self.historyNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
self.historyEmptyNode.updateLayout(size: layout.size, insets: vanillaInsets, transition: transition) self.historyNode.backgroundColor = self.mediaCollectionInterfaceState.theme.list.plainBackgroundColor
self.backgroundColor = self.mediaCollectionInterfaceState.theme.list.plainBackgroundColor
self.historyEmptyNode.updateLayout(size: layout.size, insets: vanillaInsets, transition: transition, interfaceState: mediaCollectionInterfaceState)
transition.updateFrame(node: self.historyEmptyNode, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(node: self.historyEmptyNode, frame: CGRect(origin: CGPoint(), size: layout.size))
let listViewCurve: ListViewAnimationCurve let listViewCurve: ListViewAnimationCurve
@ -352,7 +355,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode {
} }
if let placeholderNode = maybePlaceholderNode { if let placeholderNode = maybePlaceholderNode {
self.searchDisplayController = SearchDisplayController(theme: self.presentationData.theme, strings: self.presentationData.strings, contentNode: ChatHistorySearchContainerNode(account: self.account, peerId: self.peerId, tagMask: tagMaskForMode(self.mediaCollectionInterfaceState.mode), interfaceInteraction: self.controllerInteraction), cancel: { [weak self] in self.searchDisplayController = SearchDisplayController(theme: self.mediaCollectionInterfaceState.theme, strings: self.mediaCollectionInterfaceState.strings, contentNode: ChatHistorySearchContainerNode(account: self.account, peerId: self.peerId, tagMask: tagMaskForMode(self.mediaCollectionInterfaceState.mode), interfaceInteraction: self.controllerInteraction), cancel: { [weak self] in
self?.requestDeactivateSearch() self?.requestDeactivateSearch()
}) })
@ -361,6 +364,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode {
self.insertSubnode(subnode, belowSubnode: navigationBar) self.insertSubnode(subnode, belowSubnode: navigationBar)
}, placeholder: placeholderNode) }, placeholder: placeholderNode)
} }
} }
func deactivateSearch() { func deactivateSearch() {
@ -385,7 +389,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode {
let previousMode = self.mediaCollectionInterfaceState.mode let previousMode = self.mediaCollectionInterfaceState.mode
if let containerLayout = self.containerLayout, self.candidateHistoryNode == nil || self.candidateHistoryNode!.1 != mediaCollectionInterfaceState.mode { if let containerLayout = self.containerLayout, self.candidateHistoryNode == nil || self.candidateHistoryNode!.1 != mediaCollectionInterfaceState.mode {
let node = historyNodeImplForMode(mediaCollectionInterfaceState.mode, account: self.account, peerId: self.peerId, messageId: nil, controllerInteraction: self.controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) let node = historyNodeImplForMode(mediaCollectionInterfaceState.mode, account: self.account, peerId: self.peerId, messageId: nil, controllerInteraction: self.controllerInteraction, selectedMessages: self.selectedMessagesPromise.get())
node.backgroundColor = self.presentationData.theme.list.plainBackgroundColor node.backgroundColor = mediaCollectionInterfaceState.theme.list.plainBackgroundColor
self.candidateHistoryNode = (node, mediaCollectionInterfaceState.mode) self.candidateHistoryNode = (node, mediaCollectionInterfaceState.mode)
var vanillaInsets = containerLayout.0.insets(options: []) var vanillaInsets = containerLayout.0.insets(options: [])
@ -418,9 +422,9 @@ class PeerMediaCollectionControllerNode: ASDisplayNode {
node.updateLayout(transition: .immediate, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: containerLayout.0.size, insets: UIEdgeInsets(top: insets.top, left: insets.right + containerLayout.0.safeInsets.right, bottom: insets.bottom + additionalBottomInset, right: insets.left + containerLayout.0.safeInsets.left), duration: 0.0, curve: .Default)) node.updateLayout(transition: .immediate, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: containerLayout.0.size, insets: UIEdgeInsets(top: insets.top, left: insets.right + containerLayout.0.safeInsets.right, bottom: insets.bottom + additionalBottomInset, right: insets.left + containerLayout.0.safeInsets.left), duration: 0.0, curve: .Default))
let historyEmptyNode = PeerMediaCollectionEmptyNode(mode: mediaCollectionInterfaceState.mode, theme: self.presentationData.theme, strings: self.presentationData.strings) let historyEmptyNode = PeerMediaCollectionEmptyNode(mode: mediaCollectionInterfaceState.mode, theme: self.mediaCollectionInterfaceState.theme, strings: self.mediaCollectionInterfaceState.strings)
historyEmptyNode.isHidden = true historyEmptyNode.isHidden = true
historyEmptyNode.updateLayout(size: containerLayout.0.size, insets: vanillaInsets, transition: .immediate) historyEmptyNode.updateLayout(size: containerLayout.0.size, insets: vanillaInsets, transition: .immediate, interfaceState: self.mediaCollectionInterfaceState)
historyEmptyNode.frame = CGRect(origin: CGPoint(), size: containerLayout.0.size) historyEmptyNode.frame = CGRect(origin: CGPoint(), size: containerLayout.0.size)
self.candidateHistoryNodeReadyDisposable.set((node.historyState.get() self.candidateHistoryNodeReadyDisposable.set((node.historyState.get()

View File

@ -5,7 +5,7 @@ import Display
final class PeerMediaCollectionEmptyNode: ASDisplayNode { final class PeerMediaCollectionEmptyNode: ASDisplayNode {
private let mode: PeerMediaCollectionMode private let mode: PeerMediaCollectionMode
private let theme: PresentationTheme private var theme: PresentationTheme
private let strings: PresentationStrings private let strings: PresentationStrings
private let iconNode: ASImageNode private let iconNode: ASImageNode
@ -78,7 +78,7 @@ final class PeerMediaCollectionEmptyNode: ASDisplayNode {
self.addSubnode(self.textNode) self.addSubnode(self.textNode)
} }
func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition, interfaceState: PeerMediaCollectionInterfaceState) {
let displayRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) let displayRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
let textSize = self.textNode.measure(CGSize(width: size.width - 20.0, height: size.height)) let textSize = self.textNode.measure(CGSize(width: size.width - 20.0, height: size.height))
@ -94,5 +94,34 @@ final class PeerMediaCollectionEmptyNode: ASDisplayNode {
let activitySize = self.activityIndicator.measure(size) let activitySize = self.activityIndicator.measure(size)
transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: displayRect.minX + floor((displayRect.width - activitySize.width) / 2.0), y: displayRect.minY + floor((displayRect.height - activitySize.height) / 2.0)), size: activitySize)) transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: displayRect.minX + floor((displayRect.width - activitySize.width) / 2.0), y: displayRect.minY + floor((displayRect.height - activitySize.height) / 2.0)), size: activitySize))
if interfaceState.theme !== self.theme {
self.theme = interfaceState.theme
self.backgroundColor = theme.list.plainBackgroundColor
let icon: UIImage?
let text: NSAttributedString
switch mode {
case .photoOrVideo:
icon = UIImage(bundleImageName: "Media Grid/Empty List Placeholders/ImagesAndVideo")?.precomposed()
let string1 = NSAttributedString(string: strings.SharedMedia_EmptyTitle, font: Font.medium(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center)
let string2 = NSAttributedString(string: "\n\n\(strings.SharedMedia_EmptyText)", font: Font.regular(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center)
let string = NSMutableAttributedString()
string.append(string1)
string.append(string2)
text = string
case .file:
icon = UIImage(bundleImageName: "Media Grid/Empty List Placeholders/Files")?.precomposed()
text = NSAttributedString(string: strings.SharedMedia_EmptyFilesText, font: Font.regular(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center)
case .webpage:
icon = UIImage(bundleImageName: "Media Grid/Empty List Placeholders/Links")?.precomposed()
text = NSAttributedString(string: strings.SharedMedia_EmptyLinksText, font: Font.regular(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center)
case .music:
icon = UIImage(bundleImageName: "Media Grid/Empty List Placeholders/Music")?.precomposed()
text = NSAttributedString(string: strings.SharedMedia_EmptyMusicText, font: Font.regular(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center)
}
self.iconNode.image = icon
self.textNode.attributedText = text
activityIndicator.type = .custom(theme.list.itemSecondaryTextColor, 22.0, 2.0)
}
} }
} }

View File

@ -100,4 +100,11 @@ struct PeerMediaCollectionInterfaceState: Equatable {
func withMode(_ mode: PeerMediaCollectionMode) -> PeerMediaCollectionInterfaceState { func withMode(_ mode: PeerMediaCollectionMode) -> PeerMediaCollectionInterfaceState {
return PeerMediaCollectionInterfaceState(peer: self.peer, selectionState: self.selectionState, mode: mode, theme: self.theme, strings: self.strings) return PeerMediaCollectionInterfaceState(peer: self.peer, selectionState: self.selectionState, mode: mode, theme: self.theme, strings: self.strings)
} }
func updatedTheme(_ theme: PresentationTheme) -> PeerMediaCollectionInterfaceState {
return PeerMediaCollectionInterfaceState(peer: self.peer, selectionState: self.selectionState, mode: self.mode, theme: theme, strings: self.strings)
}
func updatedStrings(_ strings: PresentationStrings) -> PeerMediaCollectionInterfaceState {
return PeerMediaCollectionInterfaceState(peer: self.peer, selectionState: self.selectionState, mode: self.mode, theme: self.theme, strings: strings)
}
} }

View File

@ -40,7 +40,7 @@ final class PeerMediaCollectionSectionsNode: ASDisplayNode {
self.segmentedControl.addTarget(self, action: #selector(indexChanged), for: .valueChanged) self.segmentedControl.addTarget(self, action: #selector(indexChanged), for: .valueChanged)
} }
func updateLayout(width: CGFloat, additionalInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { func updateLayout(width: CGFloat, additionalInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: PeerMediaCollectionInterfaceState) -> CGFloat {
let panelHeight: CGFloat = 39.0 + additionalInset let panelHeight: CGFloat = 39.0 + additionalInset
let controlHeight: CGFloat = 29.0 let controlHeight: CGFloat = 29.0
@ -51,6 +51,13 @@ final class PeerMediaCollectionSectionsNode: ASDisplayNode {
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel))) transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel)))
if interfaceState.theme !== self.theme {
self.theme = interfaceState.theme
self.separatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor
self.backgroundColor = self.theme.rootController.navigationBar.backgroundColor
self.segmentedControl.tintColor = theme.rootController.navigationBar.accentTextColor
}
return panelHeight return panelHeight
} }

View File

@ -1391,6 +1391,18 @@ public final class PresentationStrings {
public func Channel_AdminLog_MessageKickedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { public func Channel_AdminLog_MessageKickedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(_Channel_AdminLog_MessageKickedNameUsername, self._Channel_AdminLog_MessageKickedNameUsername_r, [_1, _2]) return formatWithArgumentRanges(_Channel_AdminLog_MessageKickedNameUsername, self._Channel_AdminLog_MessageKickedNameUsername_r, [_1, _2])
} }
private let _Channel_AdminLog_MessageUnkickedNameUsername: String
private let _Channel_AdminLog_MessageUnkickedNameUsername_r: [(Int, NSRange)]
public func Channel_AdminLog_MessageUnkickedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(_Channel_AdminLog_MessageUnkickedNameUsername, self._Channel_AdminLog_MessageUnkickedNameUsername_r, [_1, _2])
}
private let _Channel_AdminLog_MessageUnkickedName: String
private let _Channel_AdminLog_MessageUnkickedName_r: [(Int, NSRange)]
public func Channel_AdminLog_MessageUnkickedName(_ _1: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(_Channel_AdminLog_MessageUnkickedName, self._Channel_AdminLog_MessageUnkickedName_r, [_1])
}
public let Coub_TapForSound: String public let Coub_TapForSound: String
public let Compose_NewEncryptedChat: String public let Compose_NewEncryptedChat: String
public let PhotoEditor_CropReset: String public let PhotoEditor_CropReset: String
@ -6407,6 +6419,10 @@ public final class PresentationStrings {
self._Time_PreciseDate_m5_r = extractArgumentRanges(self._Time_PreciseDate_m5) self._Time_PreciseDate_m5_r = extractArgumentRanges(self._Time_PreciseDate_m5)
self._Channel_AdminLog_MessageKickedNameUsername = getValue(dict, "Channel.AdminLog.MessageKickedNameUsername") self._Channel_AdminLog_MessageKickedNameUsername = getValue(dict, "Channel.AdminLog.MessageKickedNameUsername")
self._Channel_AdminLog_MessageKickedNameUsername_r = extractArgumentRanges(self._Channel_AdminLog_MessageKickedNameUsername) self._Channel_AdminLog_MessageKickedNameUsername_r = extractArgumentRanges(self._Channel_AdminLog_MessageKickedNameUsername)
self._Channel_AdminLog_MessageUnkickedName = getValue(dict, "Channel.AdminLog.MessageUnkickedName")
self._Channel_AdminLog_MessageUnkickedName_r = extractArgumentRanges(self._Channel_AdminLog_MessageUnkickedName)
self._Channel_AdminLog_MessageUnkickedNameUsername = getValue(dict, "Channel.AdminLog.MessageUnkickedNameUsername")
self._Channel_AdminLog_MessageUnkickedNameUsername_r = extractArgumentRanges(self._Channel_AdminLog_MessageUnkickedNameUsername)
self.Coub_TapForSound = getValue(dict, "Coub.TapForSound") self.Coub_TapForSound = getValue(dict, "Coub.TapForSound")
self.Compose_NewEncryptedChat = getValue(dict, "Compose.NewEncryptedChat") self.Compose_NewEncryptedChat = getValue(dict, "Compose.NewEncryptedChat")
self.PhotoEditor_CropReset = getValue(dict, "PhotoEditor.CropReset") self.PhotoEditor_CropReset = getValue(dict, "PhotoEditor.CropReset")

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})
})
} }
}) })
} }

View File

@ -39,6 +39,15 @@ private enum VerticalListContextResultsChatInputContextPanelEntry: Comparable, I
case action(PresentationTheme, String) case action(PresentationTheme, String)
case result(Int, PresentationTheme, ChatContextResult) case result(Int, PresentationTheme, ChatContextResult)
func withUpdatedTheme(_ theme: PresentationTheme) -> VerticalListContextResultsChatInputContextPanelEntry {
switch self {
case let .action(_, value):
return .action(theme, value)
case let .result(index, _, result):
return .result(index, theme, result)
}
}
var stableId: VerticalChatContextResultsEntryStableId { var stableId: VerticalChatContextResultsEntryStableId {
switch self { switch self {
case .action: case .action:
@ -113,10 +122,8 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex
private var enqueuedTransitions: [(VerticalListContextResultsChatInputContextPanelTransition, Bool)] = [] private var enqueuedTransitions: [(VerticalListContextResultsChatInputContextPanelTransition, Bool)] = []
private var validLayout: (CGSize, CGFloat, CGFloat)? private var validLayout: (CGSize, CGFloat, CGFloat)?
private var theme: PresentationTheme
override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme
self.listView = ListView() self.listView = ListView()
self.listView.isOpaque = false self.listView.isOpaque = false
@ -155,20 +162,26 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex
index += 1 index += 1
} }
prepareTransition(from: self.currentEntries, to: entries, results: results)
}
private func prepareTransition(from: [VerticalListContextResultsChatInputContextPanelEntry]?, to: [VerticalListContextResultsChatInputContextPanelEntry], results: ChatContextResultCollection) {
let firstTime = self.currentEntries == nil let firstTime = self.currentEntries == nil
let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, account: self.account, actionSelected: { [weak self] in let transition = preparedTransition(from: from ?? [], to: to, account: self.account, actionSelected: { [weak self] in
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction, let switchPeer = results.switchPeer { if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction, let switchPeer = results.switchPeer {
interfaceInteraction.botSwitchChatWithPayload(results.botId, switchPeer.startParam) interfaceInteraction.botSwitchChatWithPayload(results.botId, switchPeer.startParam)
} }
}, resultSelected: { [weak self] result in }, resultSelected: { [weak self] result in
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
interfaceInteraction.sendContextResult(results, result) interfaceInteraction.sendContextResult(results, result)
} }
}) })
self.currentEntries = entries self.currentEntries = to
self.enqueueTransition(transition, firstTime: firstTime) self.enqueueTransition(transition, firstTime: firstTime)
} }
private func enqueueTransition(_ transition: VerticalListContextResultsChatInputContextPanelTransition, firstTime: Bool) { private func enqueueTransition(_ transition: VerticalListContextResultsChatInputContextPanelTransition, firstTime: Bool) {
enqueuedTransitions.append((transition, firstTime)) enqueuedTransitions.append((transition, firstTime))
@ -270,6 +283,14 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex
self.dequeueTransition() self.dequeueTransition()
} }
} }
if self.theme !== interfaceState.theme, let currentResults = currentResults {
self.theme = interfaceState.theme
self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor
let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? []
prepareTransition(from: self.currentEntries, to: new, results: currentResults)
}
} }
override func animateOut(completion: @escaping () -> Void) { override func animateOut(completion: @escaping () -> Void) {