Merge commit '7e6cff6a16edf3b6a12b4625d7cbb734da7be559'

This commit is contained in:
Peter 2018-09-07 01:07:32 +03:00
commit 290105e637
33 changed files with 587 additions and 165 deletions

View File

@ -275,7 +275,8 @@ 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
self.editing = false self.editing = false
@ -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)
} }
} }
@ -593,6 +603,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,6 +617,22 @@ 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)
}
})
}
} }
} }
@ -617,8 +644,22 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon
isGroup = false isGroup = false
} }
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(isGroup ? presentationData.strings.ChatAdmins_Title : presentationData.strings.Channel_Management_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) var searchItem: ItemListControllerSearch?
let listState = ItemListNodeState(entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: account.peerId, view: view, state: state, participants: admins), style: .blocks, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count) if state.searchingMembers {
searchItem = ChannelMembersSearchItem(account: account, peerId: peerId, searchMode: .searchAdmins, cancel: {
updateState { state in
return state.withUpdatedSearchingMembers(false)
}
}, openPeer: { _, participant in
if let participant = participant?.participant, case .member = participant {
presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
})
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(isGroup ? presentationData.strings.ChatAdmins_Title : presentationData.strings.Channel_Management_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: account.peerId, view: view, state: state, participants: admins), style: .blocks, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} |> afterDisposed { } |> afterDisposed {

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,8 @@ import TelegramCore
enum ChannelMembersSearchMode { enum ChannelMembersSearchMode {
case searchMembers case searchMembers
case searchAdmins
case searchBanned
case banAndPromoteActions case banAndPromoteActions
case inviteActions case inviteActions
} }
@ -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(([], []))
} }
@ -206,7 +230,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
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 +243,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 +255,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 +291,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 +303,8 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
enabled = false enabled = false
} }
} }
entries.append(ChannelMembersSearchEntry(index: index, content: .participant(participant, label, enabled), section: section)) entries.append(ChannelMembersSearchEntry(index: index, content: .participant(participant, label, enabled), section: section))
index += 1 index += 1
} }

View File

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

View File

@ -191,10 +191,14 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode {
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
@ -414,6 +418,7 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode {
self.dequeuedInitialTransitionOnLayout = true self.dequeuedInitialTransitionOnLayout = true
self.dequeueHistoryViewTransition() self.dequeueHistoryViewTransition()
} }
} }
public func disconnect() { public func disconnect() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -59,7 +59,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
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -327,7 +327,6 @@ func openChatMessage(account: Account, message: Message, standalone: Bool, rever
return true return true
} }
case let .chatAvatars(controller, media): case let .chatAvatars(controller, media):
present(controller, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in present(controller, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in
if let selectedTransitionNode = transitionNode(message.id, media) { if let selectedTransitionNode = transitionNode(message.id, media) {

View File

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

View File

@ -28,6 +28,7 @@ public class PeerMediaCollectionController: TelegramController {
private var rightNavigationButton: PeerMediaCollectionNavigationButton? private var rightNavigationButton: PeerMediaCollectionNavigationButton?
private let galleryHiddenMesageAndMediaDisposable = MetaDisposable() private let galleryHiddenMesageAndMediaDisposable = MetaDisposable()
private var presentationDataDisposable:Disposable?
private var controllerInteraction: ChatControllerInteraction? private var controllerInteraction: ChatControllerInteraction?
private var interfaceInteraction: ChatPanelInterfaceInteraction? private var interfaceInteraction: ChatPanelInterfaceInteraction?
@ -62,6 +63,21 @@ public class PeerMediaCollectionController: TelegramController {
} }
} }
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme
let previousStrings = strongSelf.presentationData.strings
let previousChatWallpaper = strongSelf.presentationData.chatWallpaper
strongSelf.presentationData = presentationData
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings || presentationData.chatWallpaper != previousChatWallpaper {
strongSelf.themeAndStringsUpdated()
}
}
})
let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message in let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message in
if let strongSelf = self, strongSelf.isNodeLoaded, let galleryMessage = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id) { if let strongSelf = self, strongSelf.isNodeLoaded, let galleryMessage = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id) {
guard let navigationController = strongSelf.navigationController as? NavigationController else { guard let navigationController = strongSelf.navigationController as? NavigationController else {
@ -404,6 +420,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")
} }
@ -414,6 +443,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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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