mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-08 08:31:13 +00:00
Merge branch 'master' of github.com:peter-iakovlev/TelegramUI
This commit is contained in:
commit
9fc21e5b5f
@ -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";
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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))
|
||||||
|
@ -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())
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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)))
|
||||||
|
@ -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())
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
@ -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:
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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> {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user