diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index da8ab078dd..472f4d278d 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -5813,6 +5813,7 @@ PRODUCT_NAME = TelegramUI; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = singlefile; SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; @@ -6040,7 +6041,9 @@ PRODUCT_NAME = TelegramUI; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = singlefile; SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; }; @@ -6117,6 +6120,7 @@ PRODUCT_NAME = TelegramUI; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = singlefile; SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; @@ -6153,6 +6157,7 @@ PRODUCT_NAME = TelegramUI; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = singlefile; SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; diff --git a/TelegramUI/ChannelAdminsController.swift b/TelegramUI/ChannelAdminsController.swift index af55c32279..26c36ee619 100644 --- a/TelegramUI/ChannelAdminsController.swift +++ b/TelegramUI/ChannelAdminsController.swift @@ -275,7 +275,8 @@ private struct ChannelAdminsControllerState: Equatable { let removingPeerId: PeerId? let removedPeerIds: Set let temporaryAdmins: [RenderedChannelParticipant] - + let searchingMembers: Bool + init() { self.selectedType = nil self.editing = false @@ -283,15 +284,17 @@ private struct ChannelAdminsControllerState: Equatable { self.removingPeerId = nil self.removedPeerIds = Set() self.temporaryAdmins = [] + self.searchingMembers = false } - init(selectedType: CurrentAdministrationType?, editing: Bool, peerIdWithRevealedOptions: PeerId?, removingPeerId: PeerId?, removedPeerIds: Set, temporaryAdmins: [RenderedChannelParticipant]) { + init(selectedType: CurrentAdministrationType?, editing: Bool, peerIdWithRevealedOptions: PeerId?, removingPeerId: PeerId?, removedPeerIds: Set, temporaryAdmins: [RenderedChannelParticipant], searchingMembers: Bool) { self.selectedType = selectedType self.editing = editing self.peerIdWithRevealedOptions = peerIdWithRevealedOptions self.removingPeerId = removingPeerId self.removedPeerIds = removedPeerIds self.temporaryAdmins = temporaryAdmins + self.searchingMembers = searchingMembers } static func ==(lhs: ChannelAdminsControllerState, rhs: ChannelAdminsControllerState) -> Bool { @@ -313,32 +316,39 @@ private struct ChannelAdminsControllerState: Equatable { if lhs.temporaryAdmins != rhs.temporaryAdmins { return false } + if lhs.searchingMembers != rhs.searchingMembers { + return false + } 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 { - 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 { - 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 { - 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 { - 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) -> 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 { - return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: temporaryAdmins) + return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: temporaryAdmins, searchingMembers: self.searchingMembers) } } @@ -550,13 +560,16 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon } })) }, addAdmin: { - presentControllerImpl?(ChannelMembersSearchController(account: account, peerId: peerId, mode: .promote, openPeer: { peer, participant in - let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - if peer.id == account.peerId { - return - } - if let participant = participant { - switch participant.participant { + updateState { current in + + + presentControllerImpl?(ChannelMembersSearchController(account: account, peerId: peerId, mode: .promote, filters: [.exclude(current.temporaryAdmins.map({$0.peer.id}))], openPeer: { peer, participant in + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + if peer.id == account.peerId { + return + } + if let participant = participant { + switch participant.participant { case .creator: return case let .member(_, _, _, banInfo): @@ -564,11 +577,15 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Channel_Members_AddAdminErrorBlacklisted, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) return } + } } - } - presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: peer.id, initialParticipant: participant?.participant, updated: { _ in + presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: peer.id, initialParticipant: participant?.participant, updated: { _ in + }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + + return current + } + }, openAdmin: { participant in if case let .member(adminId, _, _, _) = participant { presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in @@ -593,6 +610,7 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon |> deliverOnMainQueue |> map { presentationData, state, view, admins -> (ItemListControllerState, (ItemListNodeState, ChannelAdminsEntry.ItemGenerationArguments)) in var rightNavigationButton: ItemListNavigationButton? + var secondaryRightNavigationButton: ItemListNavigationButton? if let admins = admins, admins.count > 1 { if state.editing { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { @@ -606,7 +624,27 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon return state.withUpdatedEditing(true) } }) + } + + + if !state.editing { + if rightNavigationButton == nil { + rightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: { + updateState { state in + return state.withUpdatedSearchingMembers(true) + } + }) + } else { + secondaryRightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: { + updateState { state in + return state.withUpdatedSearchingMembers(true) + } + }) + } + } + + _ = stateValue.swap(state.withUpdatedTemporaryAdmins(admins)) } let previous = previousPeers @@ -617,8 +655,22 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon 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) - 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) + var searchItem: ItemListControllerSearch? + 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)) } |> afterDisposed { diff --git a/TelegramUI/ChannelBlacklistController.swift b/TelegramUI/ChannelBlacklistController.swift index e905fb0472..0406126ffe 100644 --- a/TelegramUI/ChannelBlacklistController.swift +++ b/TelegramUI/ChannelBlacklistController.swift @@ -210,17 +210,20 @@ private struct ChannelBlacklistControllerState: Equatable { let editing: Bool let peerIdWithRevealedOptions: PeerId? let removingPeerId: PeerId? - + let searchingMembers: Bool + init() { self.editing = false self.peerIdWithRevealedOptions = 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.peerIdWithRevealedOptions = peerIdWithRevealedOptions self.removingPeerId = removingPeerId + self.searchingMembers = searchingMembers } static func ==(lhs: ChannelBlacklistControllerState, rhs: ChannelBlacklistControllerState) -> Bool { @@ -233,20 +236,28 @@ private struct ChannelBlacklistControllerState: Equatable { if lhs.removingPeerId != rhs.removingPeerId { return false } + if lhs.searchingMembers != rhs.searchingMembers { + return false + } return true } - func withUpdatedEditing(_ editing: Bool) -> ChannelBlacklistControllerState { - return ChannelBlacklistControllerState(editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId) + func withUpdatedSearchingMembers(_ searchingMembers: Bool) -> ChannelBlacklistControllerState { + 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 { - 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 { - 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 |> map { presentationData, state, view, blacklist -> (ItemListControllerState, (ItemListNodeState, ChannelBlacklistEntry.ItemGenerationArguments)) in var rightNavigationButton: ItemListNavigationButton? + var secondaryRightNavigationButton: ItemListNavigationButton? if let blacklist = blacklist, !blacklist.isEmpty { if state.editing { 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 { - 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 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? @@ -430,8 +458,22 @@ public func channelBlacklistController(account: Account, peerId: PeerId) -> View let previous = previousBlacklist 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) - 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)) + var searchItem: ItemListControllerSearch? + 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)) } |> afterDisposed { diff --git a/TelegramUI/ChannelInfoController.swift b/TelegramUI/ChannelInfoController.swift index cace3d1627..5433479f9b 100644 --- a/TelegramUI/ChannelInfoController.swift +++ b/TelegramUI/ChannelInfoController.swift @@ -105,11 +105,11 @@ private enum ChannelInfoEntry: ItemListNodeEntry { switch self { case .info: return 0 - case .addressName: - return 1 - case .about: - return 2 case .channelPhotoSetup: + return 1 + case .addressName: + return 2 + case .about: return 3 case .channelTypeSetup: 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)) - } 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)) } @@ -524,7 +524,7 @@ private func channelInfoEntries(account: Account, presentationData: Presentation //if state.editingState != nil { 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)) if peer.participationStatus == .member { entries.append(ChannelInfoEntry.leave(theme: presentationData.theme, text: presentationData.strings.Channel_LeaveChannel)) diff --git a/TelegramUI/ChannelMemberCategoryListContext.swift b/TelegramUI/ChannelMemberCategoryListContext.swift index 86bb94e0b6..9d63557a0f 100644 --- a/TelegramUI/ChannelMemberCategoryListContext.swift +++ b/TelegramUI/ChannelMemberCategoryListContext.swift @@ -49,9 +49,9 @@ struct ChannelMemberListState { enum ChannelMemberListCategory { case recent case recentSearch(String) - case admins - case restricted - case banned + case admins(String?) + case restricted(String?) + case banned(String?) } private protocol ChannelMemberCategoryListContext { @@ -153,19 +153,31 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor private func loadSignal(offset: Int32, count: Int32, hash: Int32) -> Signal<[RenderedChannelParticipant]?, NoError> { let requestCategory: ChannelMembersCategory + var adminQuery: String? = nil switch self.category { case .recent: requestCategory = .recent(.all) case let .recentSearch(query): requestCategory = .recent(.search(query)) - case .admins: + case let .admins(query): requestCategory = .admins - case .restricted: - requestCategory = .restricted(.all) - case .banned: - requestCategory = .banned(.all) + adminQuery = query + case let .restricted(query): + requestCategory = .restricted(query != nil ? .search(query!) : .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> { @@ -561,14 +573,14 @@ final class PeerChannelMemberCategoriesContext { mappedCategory = .recent case let .recentSearch(query): mappedCategory = .recentSearch(query) - case .admins: - mappedCategory = .admins + case let .admins(query): + mappedCategory = .admins(query) default: mappedCategory = .recent } context = ChannelMemberSingleCategoryListContext(postbox: self.postbox, network: self.network, peerId: self.peerId, category: mappedCategory) - case .restrictedAndBanned: - context = ChannelMemberMultiCategoryListContext(postbox: self.postbox, network: self.network, peerId: self.peerId, categories: [.restricted, .banned]) + case let .restrictedAndBanned(query): + 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 assert(Queue.mainQueue().isCurrent()) diff --git a/TelegramUI/ChannelMembersController.swift b/TelegramUI/ChannelMembersController.swift index 4f56839626..40ece32655 100644 --- a/TelegramUI/ChannelMembersController.swift +++ b/TelegramUI/ChannelMembersController.swift @@ -190,17 +190,20 @@ private struct ChannelMembersControllerState: Equatable { let editing: Bool let peerIdWithRevealedOptions: PeerId? let removingPeerId: PeerId? - + let searchingMembers: Bool + init() { self.editing = false self.peerIdWithRevealedOptions = 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.peerIdWithRevealedOptions = peerIdWithRevealedOptions self.removingPeerId = removingPeerId + self.searchingMembers = searchingMembers } static func ==(lhs: ChannelMembersControllerState, rhs: ChannelMembersControllerState) -> Bool { @@ -213,20 +216,26 @@ private struct ChannelMembersControllerState: Equatable { if lhs.removingPeerId != rhs.removingPeerId { return false } - + if lhs.searchingMembers != rhs.searchingMembers { + return false + } 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 { - 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 { - 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 { - 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 |> map { presentationData, state, view, peers -> (ItemListControllerState, (ItemListNodeState, ChannelMembersEntry.ItemGenerationArguments)) in var rightNavigationButton: ItemListNavigationButton? + var secondaryRightNavigationButton: ItemListNavigationButton? if let peers = peers, !peers.isEmpty { if state.editing { 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) } }) + 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? if peers == nil { emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) @@ -482,8 +514,8 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo let previous = previousPeers 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 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 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, searchItem: searchItem, animateChanges: previous != nil && peers != nil && previous!.count >= peers!.count) return (controllerState, (listState, arguments)) } |> afterDisposed { diff --git a/TelegramUI/ChannelMembersSearchContainerNode.swift b/TelegramUI/ChannelMembersSearchContainerNode.swift index 2dfc5b3958..9992708b53 100644 --- a/TelegramUI/ChannelMembersSearchContainerNode.swift +++ b/TelegramUI/ChannelMembersSearchContainerNode.swift @@ -7,6 +7,8 @@ import TelegramCore enum ChannelMembersSearchMode { case searchMembers + case searchAdmins + case searchBanned case banAndPromoteActions case inviteActions } @@ -140,7 +142,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings)> - init(account: Account, peerId: PeerId, mode: ChannelMembersSearchMode, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) { + init(account: Account, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) { self.account = account self.openPeer = openPeer self.mode = mode @@ -184,6 +186,28 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod foundGroupMembers = .single([]) foundMembers = channelMembers(postbox: account.postbox, network: account.network, peerId: peerId, category: .recent(.search(query))) |> 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> @@ -193,7 +217,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod foundContacts = account.postbox.searchContacts(query: query.lowercased()) foundRemotePeers = .single(([], [])) |> then(searchPeers(account: account, query: query) |> delay(0.2, queue: Queue.concurrentDefaultQueue())) - case .searchMembers: + case .searchMembers, .searchBanned, .searchAdmins: foundContacts = .single([]) foundRemotePeers = .single(([], [])) } @@ -203,10 +227,16 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod var entries: [ChannelMembersSearchEntry] = [] var existingPeerIds = Set() + for filter in filters { + switch filter { + case let .exclude(ids): + existingPeerIds = existingPeerIds.union(ids) + } + } switch mode { case .inviteActions, .banAndPromoteActions: existingPeerIds.insert(account.peerId) - case .searchMembers: + case .searchMembers, .searchAdmins, .searchBanned: break } @@ -219,7 +249,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod switch mode { case .inviteActions, .banAndPromoteActions: section = .members - case .searchMembers: + case .searchMembers, .searchBanned, .searchAdmins: section = .none } @@ -231,6 +261,30 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod 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)) index += 1 } @@ -243,7 +297,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod switch mode { case .inviteActions, .banAndPromoteActions: section = .members - case .searchMembers: + case .searchMembers, .searchBanned, .searchAdmins: section = .none } @@ -255,6 +309,8 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod enabled = false } } + + entries.append(ChannelMembersSearchEntry(index: index, content: .participant(participant, label, enabled), section: section)) index += 1 } diff --git a/TelegramUI/ChannelMembersSearchController.swift b/TelegramUI/ChannelMembersSearchController.swift index 4fd788a395..280951c153 100644 --- a/TelegramUI/ChannelMembersSearchController.swift +++ b/TelegramUI/ChannelMembersSearchController.swift @@ -9,12 +9,17 @@ enum ChannelMembersSearchControllerMode { case ban } +enum ChannelMembersSearchFilter { + case exclude([PeerId]) +} + final class ChannelMembersSearchController: ViewController { private let queue = Queue() private let account: Account private let peerId: PeerId private let mode: ChannelMembersSearchControllerMode + private let filters: [ChannelMembersSearchFilter] private let openPeer: (Peer, RenderedChannelParticipant?) -> Void private var presentationData: PresentationData @@ -25,12 +30,12 @@ final class ChannelMembersSearchController: ViewController { return self.displayNode as! ChannelMembersSearchControllerNode } - init(account: Account, peerId: PeerId, mode: ChannelMembersSearchControllerMode, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) { + init(account: Account, peerId: PeerId, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter] = [], openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) { self.account = account self.peerId = peerId self.mode = mode self.openPeer = openPeer - + self.filters = filters self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) @@ -52,7 +57,7 @@ final class ChannelMembersSearchController: ViewController { } override func loadDisplayNode() { - self.displayNode = ChannelMembersSearchControllerNode(account: self.account, theme: self.presentationData.theme, strings: self.presentationData.strings, peerId: self.peerId, mode: self.mode) + self.displayNode = ChannelMembersSearchControllerNode(account: self.account, theme: self.presentationData.theme, strings: self.presentationData.strings, peerId: self.peerId, mode: self.mode, filters: self.filters) self.controllerNode.navigationBar = self.navigationBar self.controllerNode.requestActivateSearch = { [weak self] in self?.activateSearch() diff --git a/TelegramUI/ChannelMembersSearchControllerNode.swift b/TelegramUI/ChannelMembersSearchControllerNode.swift index d066426f22..500acb7939 100644 --- a/TelegramUI/ChannelMembersSearchControllerNode.swift +++ b/TelegramUI/ChannelMembersSearchControllerNode.swift @@ -108,7 +108,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { private let account: Account private let peerId: PeerId private let mode: ChannelMembersSearchControllerMode - + private let filters: [ChannelMembersSearchFilter] let listNode: ListView var navigationBar: NavigationBar? @@ -127,12 +127,12 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { private var disposable: Disposable? private var listControl: PeerChannelMemberCategoryControl? - init(account: Account, theme: PresentationTheme, strings: PresentationStrings, peerId: PeerId, mode: ChannelMembersSearchControllerMode) { + init(account: Account, theme: PresentationTheme, strings: PresentationStrings, peerId: PeerId, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter]) { self.account = account self.listNode = ListView() self.peerId = peerId self.mode = mode - + self.filters = filters self.themeAndStrings = (theme, strings) super.init() @@ -168,10 +168,26 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { if participant.peer.id == account.peerId { continue } + for filter in filters { + switch filter { + case let .exclude(ids): + if ids.contains(participant.peer.id) { + continue + } + } + } case .promote: if participant.peer.id == account.peerId { continue } + for filter in filters { + switch filter { + case let .exclude(ids): + if ids.contains(participant.peer.id) { + continue + } + } + } if case .creator = participant.participant { label = strings.Channel_Management_LabelCreator enabled = false @@ -268,7 +284,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { } if let placeholderNode = maybePlaceholderNode { - self.searchDisplayController = SearchDisplayController(theme: self.themeAndStrings.0, strings: self.themeAndStrings.1, contentNode: ChannelMembersSearchContainerNode(account: self.account, peerId: self.peerId, mode: .banAndPromoteActions, openPeer: { [weak self] peer, participant in + self.searchDisplayController = SearchDisplayController(theme: self.themeAndStrings.0, strings: self.themeAndStrings.1, contentNode: ChannelMembersSearchContainerNode(account: self.account, peerId: self.peerId, mode: .banAndPromoteActions, filters: self.filters, openPeer: { [weak self] peer, participant in self?.requestOpenPeerFromSearch?(peer, participant) }), cancel: { [weak self] in if let requestDeactivateSearch = self?.requestDeactivateSearch { diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index cdf68a51c0..8cc76af17e 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -309,6 +309,26 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin controllerInteraction.hiddenMedia = messageIdAndMedia + strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView { + itemNode.updateHiddenMedia() + } + } + } + })) + } + }, chatAvatarHiddenMedia: { signal, media in + if let strongSelf = self { + strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { messageId in + if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { + var messageIdAndMedia: [MessageId: [Media]] = [:] + + if let messageId = messageId { + messageIdAndMedia[messageId] = [media] + } + + controllerInteraction.hiddenMedia = messageIdAndMedia + strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemView { itemNode.updateHiddenMedia() @@ -3828,7 +3848,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin searchDisposable = MetaDisposable() self.searchDisposable = searchDisposable } - searchDisposable.set((searchMessages(account: self.account, location: location, query: query) + searchDisposable.set((searchMessages(account: self.account, location: location, query: query) |> map {$0.0} |> delay(0.2, queue: Queue.mainQueue()) |> deliverOnMainQueue).start(next: { [weak self] results in if let strongSelf = self { diff --git a/TelegramUI/ChatControllerNode.swift b/TelegramUI/ChatControllerNode.swift index d2dfddc974..ed445d6e7e 100644 --- a/TelegramUI/ChatControllerNode.swift +++ b/TelegramUI/ChatControllerNode.swift @@ -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) } - if !inputContextPanelNode.frame.equalTo(panelFrame) { + if !inputContextPanelNode.frame.equalTo(panelFrame) || inputContextPanelNode.theme !== self.chatPresentationInterfaceState.theme { transition.updateFrame(node: inputContextPanelNode, frame: panelFrame) inputContextPanelNode.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition, interfaceState: self.chatPresentationInterfaceState) } diff --git a/TelegramUI/ChatHistoryGridNode.swift b/TelegramUI/ChatHistoryGridNode.swift index 1f9f6b86cb..9b9f405ccb 100644 --- a/TelegramUI/ChatHistoryGridNode.swift +++ b/TelegramUI/ChatHistoryGridNode.swift @@ -5,6 +5,65 @@ import Display import AsyncDisplayKit import TelegramCore + +private class ChatGridLiveSelectorRecognizer: UIPanGestureRecognizer { + + private let selectionGestureActivationThreshold: CGFloat = 2.0 + private let selectionGestureVerticalFailureThreshold: CGFloat = 5.0 + + var validatedGesture: Bool? = nil + var firstLocation: CGPoint = CGPoint() + + var shouldBegin: (() -> Bool)? + + override init(target: Any?, action: Selector?) { + super.init(target: target, action: action) + + self.maximumNumberOfTouches = 1 + } + + override func reset() { + super.reset() + + self.validatedGesture = nil + } + + override func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) + + if let shouldBegin = self.shouldBegin, !shouldBegin() { + self.state = .failed + } else { + let touch = touches.first! + self.firstLocation = touch.location(in: self.view) + } + } + + + override func touchesMoved(_ touches: Set, with event: UIEvent) { + let location = touches.first!.location(in: self.view) + let translation = CGPoint(x: location.x - firstLocation.x, y: location.y - firstLocation.y) + + if validatedGesture == nil { + if (fabs(translation.y) >= selectionGestureVerticalFailureThreshold) + { + validatedGesture = false + } + else if (fabs(translation.x) >= selectionGestureActivationThreshold) { + validatedGesture = true + } + } + + if let validatedGesture = validatedGesture { + if validatedGesture { + super.touchesMoved(touches, with: event) + } + } + + + } +} + struct ChatHistoryGridViewTransition { let historyView: ChatHistoryView let topOffsetWithinMonth: Int @@ -180,21 +239,25 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode { public private(set) var loadState: ChatHistoryNodeLoadState? private var loadStateUpdated: ((ChatHistoryNodeLoadState, Bool) -> Void)? - + private let controllerInteraction: ChatControllerInteraction public init(account: Account, peerId: PeerId, messageId: MessageId?, tagMask: MessageTags?, controllerInteraction: ChatControllerInteraction) { self.account = account self.peerId = peerId self.messageId = messageId self.tagMask = tagMask - + self.controllerInteraction = controllerInteraction self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } 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.preloadPages = false let messageViewQueue = self.messageViewQueue @@ -279,6 +342,39 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode { } } } + + let selectorRecogizner = ChatGridLiveSelectorRecognizer(target: self, action: #selector(self.panGesture(_:))) + selectorRecogizner.shouldBegin = { [weak controllerInteraction] in + return controllerInteraction?.selectionState != nil + } + self.view.addGestureRecognizer(selectorRecogizner) + } + + public override func didLoad() { + super.didLoad() + } + + private var liveSelectingState: (selecting: Bool, currentMessageId: MessageId)? + + @objc private func panGesture(_ recognizer: UIGestureRecognizer) -> Void { + guard let selectionState = controllerInteraction.selectionState else {return} + + switch recognizer.state { + case .began: + if let itemNode = self.itemNodeAtPoint(recognizer.location(in: self.view)) as? GridMessageItemNode, let messageId = itemNode.messageId { + liveSelectingState = (selecting: !selectionState.selectedIds.contains(messageId), currentMessageId: messageId) + controllerInteraction.toggleMessagesSelection([messageId], !selectionState.selectedIds.contains(messageId)) + } + case .changed: + if let liveSelectingState = liveSelectingState, let itemNode = self.itemNodeAtPoint(recognizer.location(in: self.view)) as? GridMessageItemNode, let messageId = itemNode.messageId, messageId != liveSelectingState.currentMessageId { + controllerInteraction.toggleMessagesSelection([messageId], liveSelectingState.selecting) + self.liveSelectingState?.currentMessageId = messageId + } + case .ended, .failed, .cancelled: + liveSelectingState = nil + case .possible: + break + } } required public init?(coder aDecoder: NSCoder) { @@ -414,6 +510,7 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode { self.dequeuedInitialTransitionOnLayout = true self.dequeueHistoryViewTransition() } + } public func disconnect() { diff --git a/TelegramUI/ChatHistoryListNode.swift b/TelegramUI/ChatHistoryListNode.swift index 44faf0b9bd..5b44bddcdb 100644 --- a/TelegramUI/ChatHistoryListNode.swift +++ b/TelegramUI/ChatHistoryListNode.swift @@ -680,6 +680,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { strongSelf.forEachItemHeaderNode { itemHeaderNode in if let dateNode = itemHeaderNode as? ChatMessageDateHeaderNode { 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))) diff --git a/TelegramUI/ChatHistorySearchContainerNode.swift b/TelegramUI/ChatHistorySearchContainerNode.swift index 1372c3b3a9..c9ec89b1bd 100644 --- a/TelegramUI/ChatHistorySearchContainerNode.swift +++ b/TelegramUI/ChatHistorySearchContainerNode.swift @@ -151,7 +151,7 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode { if let strongSelf = self { let signal: Signal<[ChatHistorySearchEntry]?, NoError> if let query = query, !query.isEmpty { - let foundRemoteMessages: Signal<[Message], NoError> = searchMessages(account: account, location: .peer(peerId: peerId, fromId: nil, tags: tagMask), query: query) + let foundRemoteMessages: Signal<[Message], NoError> = searchMessages(account: account, location: .peer(peerId: peerId, fromId: nil, tags: tagMask), query: query) |> map {$0.0} |> delay(0.2, queue: Queue.concurrentDefaultQueue()) signal = combineLatest(foundRemoteMessages, themeAndStringsPromise.get()) diff --git a/TelegramUI/ChatInputContextPanelNode.swift b/TelegramUI/ChatInputContextPanelNode.swift index 1d678498c0..6611c02472 100644 --- a/TelegramUI/ChatInputContextPanelNode.swift +++ b/TelegramUI/ChatInputContextPanelNode.swift @@ -12,10 +12,10 @@ class ChatInputContextPanelNode: ASDisplayNode { let account: Account var interfaceInteraction: ChatPanelInterfaceInteraction? var placement: ChatInputContextPanelPlacement = .overPanels - + var theme: PresentationTheme init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { self.account = account - + self.theme = theme super.init() } diff --git a/TelegramUI/ChatInterfaceStateContextMenus.swift b/TelegramUI/ChatInterfaceStateContextMenus.swift index b48d5355ee..29261525bc 100644 --- a/TelegramUI/ChatInterfaceStateContextMenus.swift +++ b/TelegramUI/ChatInterfaceStateContextMenus.swift @@ -268,14 +268,16 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } + var hasUneditableAttributes = false + + if let peer = message.peers[message.id.peerId] as? TelegramChannel { if peer.hasBannedRights(.banSendMessages) { - restrictEdit = true + hasUneditableAttributes = true } } if hasEditRights { - var hasUneditableAttributes = false for attribute in message.attributes { if let _ = attribute as? InlineBotMessageAttribute { hasUneditableAttributes = true @@ -298,13 +300,16 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } else if let _ = media as? TelegramMediaExpiredContent { hasUneditableAttributes = true break + } else if let _ = media as? TelegramMediaMap { + hasUneditableAttributes = true + break } } if !hasUneditableAttributes { let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) if canPerformEditingActions(limits: limitsConfiguration, accountPeerId: account.peerId, message: message) { - canEdit = !restrictEdit + canEdit = true } } } diff --git a/TelegramUI/ChatListSearchContainerNode.swift b/TelegramUI/ChatListSearchContainerNode.swift index 571715d15a..cfa94360f9 100644 --- a/TelegramUI/ChatListSearchContainerNode.swift +++ b/TelegramUI/ChatListSearchContainerNode.swift @@ -235,7 +235,7 @@ enum ChatListSearchEntryStableId: Hashable { enum ChatListSearchEntry: Comparable, Identifiable { case localPeer(Peer, Peer?, UnreadSearchBadge?, Int, PresentationTheme, PresentationStrings) case globalPeer(FoundPeer, UnreadSearchBadge?, Int, PresentationTheme, PresentationStrings) - case message(Message, ChatListPresentationData) + case message(Message, CombinedPeerReadState?, ChatListPresentationData) var stableId: ChatListSearchEntryStableId { switch self { @@ -243,7 +243,7 @@ enum ChatListSearchEntry: Comparable, Identifiable { return .localPeerId(peer.id) case let .globalPeer(peer, _, _, _, _): return .globalPeerId(peer.peer.id) - case let .message(message, _): + case let .message(message, _, _): return .messageId(message.id) } } @@ -262,8 +262,8 @@ enum ChatListSearchEntry: Comparable, Identifiable { } else { return false } - case let .message(lhsMessage, lhsPresentationData): - if case let .message(rhsMessage, rhsPresentationData) = rhs { + case let .message(lhsMessage, lhsCombinedPeerReadState, lhsPresentationData): + if case let .message(rhsMessage, rhsCombinedPeerReadState, rhsPresentationData) = rhs { if lhsMessage.id != rhsMessage.id { return false } @@ -273,6 +273,9 @@ enum ChatListSearchEntry: Comparable, Identifiable { if lhsPresentationData !== rhsPresentationData { return false } + if lhsCombinedPeerReadState != rhsCombinedPeerReadState { + return false + } return true } else { return false @@ -297,8 +300,8 @@ enum ChatListSearchEntry: Comparable, Identifiable { case .message: return true } - case let .message(lhsMessage, _): - if case let .message(rhsMessage, _) = rhs { + case let .message(lhsMessage, _, _): + if case let .message(rhsMessage, _, _) = rhs { return MessageIndex(lhsMessage) < MessageIndex(rhsMessage) } else { return false @@ -393,8 +396,8 @@ enum ChatListSearchEntry: Comparable, Identifiable { return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .generalSearch, peer: .peer(peer: peer.peer, chatPeer: peer.peer), status: .addressName(suffixString), badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .globalPeers, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { _ in interaction.peerSelected(peer.peer) }) - case let .message(message, presentationData): - return ChatListItem(presentationData: presentationData, account: account, peerGroupId: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(message)), content: .peer(message: message, peer: RenderedPeer(message: message), combinedReadState: nil, notificationSettings: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false), editing: false, hasActiveRevealControls: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, interaction: interaction) + case let .message(message, readState, presentationData): + return ChatListItem(presentationData: presentationData, account: account, peerGroupId: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(message)), content: .peer(message: message, peer: RenderedPeer(message: message), combinedReadState: readState, notificationSettings: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false), editing: false, hasActiveRevealControls: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, interaction: interaction) } } } @@ -566,7 +569,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { } else { location = .general } - let foundRemoteMessages: Signal<([Message], Bool), NoError> = .single(([], true)) |> then(searchMessages(account: account, location: location, query: query) + let foundRemoteMessages: Signal<(([Message], [PeerId : CombinedPeerReadState]), Bool), NoError> = .single((([], [:]), true)) |> then(searchMessages(account: account, location: location, query: query) |> map { ($0, false) } |> delay(0.2, queue: Queue.concurrentDefaultQueue())) @@ -619,8 +622,8 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { if !foundRemotePeers.2 { index = 0 - for message in foundRemoteMessages.0 { - entries.append(.message(message, presentationData)) + for message in foundRemoteMessages.0.0 { + entries.append(.message(message, foundRemoteMessages.0.1[message.id.peerId], presentationData)) index += 1 } } diff --git a/TelegramUI/ChatMessageActionItemNode.swift b/TelegramUI/ChatMessageActionItemNode.swift index 43cac96be6..44d82e5502 100644 --- a/TelegramUI/ChatMessageActionItemNode.swift +++ b/TelegramUI/ChatMessageActionItemNode.swift @@ -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))) { let makeLabelLayout = TextNode.asyncLayout(self.labelNode) @@ -517,6 +546,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { if j != 0 && index + j >= 0 && index + j < sortedIndices.count { if abs(labelRects[index + j].width - labelRects[index].width) < 40.0 { labelRects[index + j].size.width = max(labelRects[index + j].width, labelRects[index].width) + labelRects[index].size.width = labelRects[index + j].size.width } } } @@ -528,6 +558,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { labelRects[i].origin.x = floor((labelLayout.size.width - labelRects[i].width) / 2.0) } + let backgroundApply = backgroundLayout(item.presentationData.theme.theme.chat.serviceMessage.serviceMessageFillColor, labelRects, 10.0, 10.0, 0.0) var backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + 4.0) @@ -555,9 +586,9 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { let apply = imageNode.asyncLayout()(arguments) apply() - strongSelf.fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: item.account, photoReference: .message(message: MessageReference(item.message), media: image)).start()) } - let updateImageSignal = chatMessagePhoto(postbox: item.account.postbox, photoReference: ImageMediaReference.message(message: MessageReference(item.message), media: image)) + strongSelf.fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: item.account, photoReference: .message(message: MessageReference(item.message), media: image)).start()) + let updateImageSignal = chatMessagePhoto(postbox: item.account.postbox, photoReference: .message(message: MessageReference(item.message), media: image)) imageNode.setSignal(updateImageSignal) diff --git a/TelegramUI/ChatMessageBubbleItemNode.swift b/TelegramUI/ChatMessageBubbleItemNode.swift index d1213cea85..cd20c8a525 100644 --- a/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/TelegramUI/ChatMessageBubbleItemNode.swift @@ -264,7 +264,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { } for media in item.content.firstMessage.media { if let media = media as? TelegramMediaAction, case .phoneCall(_, _, _) = media.action { - } else { return false } } diff --git a/TelegramUI/ChatPinnedMessageTitlePanelNode.swift b/TelegramUI/ChatPinnedMessageTitlePanelNode.swift index 2b0aea6a01..106130d4df 100644 --- a/TelegramUI/ChatPinnedMessageTitlePanelNode.swift +++ b/TelegramUI/ChatPinnedMessageTitlePanelNode.swift @@ -214,7 +214,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: strings.Conversation_PinnedMessage, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0))) - let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: descriptionStringForMessage(message, strings: strings, accountPeerId: accountPeerId).0, font: Font.regular(15.0), textColor: theme.chat.inputPanel.primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0))) + let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: descriptionStringForMessage(message, strings: strings, accountPeerId: accountPeerId).0, font: Font.regular(15.0), textColor: !message.media.isEmpty ? theme.chat.inputPanel.secondaryTextColor : theme.chat.inputPanel.primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0))) Queue.mainQueue().async { if let strongSelf = self { diff --git a/TelegramUI/ChatRecentActionsControllerNode.swift b/TelegramUI/ChatRecentActionsControllerNode.swift index 1fc3e532b9..a8711e24d8 100644 --- a/TelegramUI/ChatRecentActionsControllerNode.swift +++ b/TelegramUI/ChatRecentActionsControllerNode.swift @@ -115,7 +115,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { 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.adminsDisposable = adminsDisposable @@ -167,27 +167,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, callPeer: { peerId in self?.controllerInteraction?.callPeer(peerId) }, enqueueMessage: { _ in - }, sendSticker: nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in - if let strongSelf = self { - /*strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { entry in - if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { - var messageIdAndMedia: [MessageId: [Media]] = [:] - - if let entry = entry, entry.index == centralIndex { - messageIdAndMedia[message.id] = [galleryMedia] - } - - controllerInteraction.hiddenMedia = messageIdAndMedia - - strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView { - itemNode.updateHiddenMedia() - } - } - } - }))*/ - } - }) + }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in}) } return false }, openPeer: { [weak self] peerId, _, message in diff --git a/TelegramUI/ChatRecentActionsHistoryTransition.swift b/TelegramUI/ChatRecentActionsHistoryTransition.swift index 0f1bb6a08a..3025ffaec6 100644 --- a/TelegramUI/ChatRecentActionsHistoryTransition.swift +++ b/TelegramUI/ChatRecentActionsHistoryTransition.swift @@ -495,10 +495,29 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { var text: String = "" 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(_, _, _, newBanInfo) = new.participant { 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) { 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] = [] @@ -510,6 +529,16 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return result }, to: &text, entities: &entities) 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 { 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] = [] @@ -555,13 +584,12 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { ] for (flag, string) in order { - if prevFlags.contains(flag) != newFlags.contains(flag) { - text += "\n" - if prevFlags.contains(flag) { - text += "+" - } else { - text += "-" - } + if addedRights.contains(flag) { + text += "\n-" + appendAttributedText(text: string, withEntities: [.Italic], to: &text, entities: &entities) + } + if removedRights.contains(flag) { + text += "\n+" appendAttributedText(text: string, withEntities: [.Italic], to: &text, entities: &entities) } } diff --git a/TelegramUI/CommandChatInputContextPanelNode.swift b/TelegramUI/CommandChatInputContextPanelNode.swift index 51d1867e22..226c0cd3f1 100644 --- a/TelegramUI/CommandChatInputContextPanelNode.swift +++ b/TelegramUI/CommandChatInputContextPanelNode.swift @@ -25,6 +25,10 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable { 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 { 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 { - private var theme: PresentationTheme private let listView: ListView private var currentEntries: [CommandChatInputContextPanelEntry]? @@ -64,7 +67,6 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { private var validLayout: (CGSize, CGFloat, CGFloat)? override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { - self.theme = theme self.listView = ListView() self.listView.isOpaque = false @@ -94,9 +96,13 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { entries.append(entry) index += 1 } + prepareTransition(from: self.currentEntries ?? [], to: entries) + } + + private func prepareTransition(from: [CommandChatInputContextPanelEntry]? , to: [CommandChatInputContextPanelEntry]) { 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 sendImmediately { interfaceInteraction.sendBotCommand(command.peer, "/" + command.command.text) @@ -112,7 +118,7 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { if let range = commandQueryRange { let inputText = NSMutableAttributedString(attributedString: textInputState.inputText) - + let replacementText = command.command.text + " " 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) } @@ -226,6 +232,14 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { 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) { diff --git a/TelegramUI/EmojisChatInputContextPanelNode.swift b/TelegramUI/EmojisChatInputContextPanelNode.swift index 0ac3d79e0b..887efc1edd 100644 --- a/TelegramUI/EmojisChatInputContextPanelNode.swift +++ b/TelegramUI/EmojisChatInputContextPanelNode.swift @@ -19,6 +19,10 @@ private struct EmojisChatInputContextPanelEntry: Comparable, Identifiable { 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 { 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 { - private var theme: PresentationTheme private let listView: ListView private var currentEntries: [EmojisChatInputContextPanelEntry]? @@ -58,7 +61,6 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode { private var validLayout: (CGSize, CGFloat, CGFloat)? override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { - self.theme = theme self.listView = ListView() self.listView.isOpaque = false @@ -88,9 +90,13 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode { entries.append(entry) index += 1 } + prepareTransition(from: self.currentEntries, to: entries) + } + + private func prepareTransition(from: [EmojisChatInputContextPanelEntry]? , to: [EmojisChatInputContextPanelEntry]) { 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 { interfaceInteraction.updateTextInputStateAndMode { textInputState, inputMode in var hashtagQueryRange: NSRange? @@ -119,7 +125,7 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode { } } }) - self.currentEntries = entries + self.currentEntries = to self.enqueueTransition(transition, firstTime: firstTime) } @@ -219,6 +225,14 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode { 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) { diff --git a/TelegramUI/GalleryThumbnailContainerNode.swift b/TelegramUI/GalleryThumbnailContainerNode.swift index 59ac12f7c8..bbd2e6e812 100644 --- a/TelegramUI/GalleryThumbnailContainerNode.swift +++ b/TelegramUI/GalleryThumbnailContainerNode.swift @@ -65,6 +65,7 @@ final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDelegate { } func updateItems(_ items: [GalleryThumbnailItem], centralIndex: Int, progress: CGFloat) { + var items: [GalleryThumbnailItem] = items.count <= 1 ? [] : items var updated = false if self.items.count == items.count { for i in 0 ..< self.items.count { diff --git a/TelegramUI/GridMessageItem.swift b/TelegramUI/GridMessageItem.swift index c1728ea84e..d7f30fd379 100644 --- a/TelegramUI/GridMessageItem.swift +++ b/TelegramUI/GridMessageItem.swift @@ -5,6 +5,42 @@ import TelegramCore import Postbox import SwiftSignalKit +private let videoAccessoryFont: UIFont = Font.regular(11) + +private final class GridMessageVideoAccessoryNode : ASDisplayNode { + + private let textNode: ImmediateTextNode = ImmediateTextNode() + + override init() { + super.init() + self.textNode.displaysAsynchronously = false + self.textNode.maximumNumberOfLines = 1 + self.textNode.isLayerBacked = true + self.textNode.textAlignment = .left + self.textNode.lineSpacing = 0.1 + addSubnode(self.textNode) + backgroundColor = UIColor(white: 0.0, alpha: 0.6) + } + + var contentSize: CGSize { + return CGSize(width: textSize.width + 10, height: 16) + } + private var textSize: CGSize = CGSize() + + func setup(_ duration: String) { + textNode.attributedText = NSAttributedString(string: duration, font: videoAccessoryFont, textColor: .white, paragraphAlignment: nil) + textSize = self.textNode.updateLayout(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)) + } + + override func layout() { + if let _ = self.textNode.attributedText { + self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((frame.width - textSize.width) / 2.0), y: floorToScreenPixels((frame.height - textSize.height) / 2.0) + 0.5), size: textSize) + } + } + + +} + private func mediaForMessage(_ message: Message) -> Media? { for media in message.media { if let media = media as? TelegramMediaImage { @@ -59,7 +95,7 @@ final class GridMessageItemSection: GridSection { func isEqual(to: GridSection) -> Bool { if let to = to as? GridMessageItemSection { - return self.roundedTimestamp == to.roundedTimestamp + return self.roundedTimestamp == to.roundedTimestamp && theme === to.theme } else { return false } @@ -111,7 +147,6 @@ final class GridMessageItem: GridItem { private let account: Account fileprivate let message: Message private let controllerInteraction: ChatControllerInteraction - let section: GridSection? init(theme: PresentationTheme, strings: PresentationStrings, account: Account, message: Message, controllerInteraction: ChatControllerInteraction) { @@ -145,11 +180,12 @@ final class GridMessageItem: GridItem { final class GridMessageItemNode: GridItemNode { private var currentState: (Account, Media, CGSize)? private let imageNode: TransformImageNode - private var messageId: MessageId? + private(set) var messageId: MessageId? private var item: GridMessageItem? private var controllerInteraction: ChatControllerInteraction? private var statusNode: RadialStatusNode - + private let videoAccessoryNode = GridMessageVideoAccessoryNode() + private var selectionNode: GridMessageSelectionNode? private let fetchStatusDisposable = MetaDisposable() @@ -164,6 +200,7 @@ final class GridMessageItemNode: GridItemNode { super.init() self.addSubnode(self.imageNode) + self.imageNode.addSubnode(videoAccessoryNode) } deinit { @@ -193,11 +230,20 @@ final class GridMessageItemNode: GridItemNode { self.statusNode.transitionToState(.none, completion: { [weak self] in self?.statusNode.isHidden = true }) + videoAccessoryNode.isHidden = true self.resourceStatus = nil } else if let file = media as? TelegramMediaFile, file.isVideo { mediaDimensions = file.dimensions self.imageNode.setSignal(mediaGridMessageVideo(postbox: account.postbox, videoReference: .message(message: MessageReference(item.message), media: file))) + if let duration = file.duration { + videoAccessoryNode.setup(String(format: "%d:%02d", duration / 60, duration % 60)) + videoAccessoryNode.isHidden = false + } else { + videoAccessoryNode.isHidden = true + } + + self.resourceStatus = nil self.fetchStatusDisposable.set((messageMediaFileStatus(account: account, messageId: messageId, file: file) |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self { @@ -233,6 +279,8 @@ final class GridMessageItemNode: GridItemNode { if self.statusNode.supernode == nil { self.imageNode.addSubnode(self.statusNode) } + } else { + videoAccessoryNode.isHidden = true } if let mediaDimensions = mediaDimensions { @@ -263,6 +311,8 @@ final class GridMessageItemNode: GridItemNode { self.selectionNode?.frame = CGRect(origin: CGPoint(), size: self.bounds.size) let progressDiameter: CGFloat = 40.0 self.statusNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - progressDiameter) / 2.0), y: floor((imageFrame.size.height - progressDiameter) / 2.0)), size: CGSize(width: progressDiameter, height: progressDiameter)) + + videoAccessoryNode.frame = CGRect(origin: CGPoint(x: imageFrame.maxX - videoAccessoryNode.contentSize.width - 5, y: imageFrame.maxY - videoAccessoryNode.contentSize.height - 5), size: videoAccessoryNode.contentSize) } func updateSelectionState(animated: Bool) { diff --git a/TelegramUI/GroupInfoController.swift b/TelegramUI/GroupInfoController.swift index 52e920765b..299f3e59a6 100644 --- a/TelegramUI/GroupInfoController.swift +++ b/TelegramUI/GroupInfoController.swift @@ -1639,11 +1639,11 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl var searchItem: ItemListControllerSearch? if state.searchingMembers { - searchItem = GroupInfoSearchItem(account: account, peerId: peerId, cancel: { + searchItem = ChannelMembersSearchItem(account: account, peerId: peerId, cancel: { updateState { state in return state.withUpdatedSearchingMembers(false) } - }, openPeer: { peer in + }, openPeer: { peer, _ in if let infoController = peerInfoController(account: account, peer: peer) { arguments.pushController(infoController) } diff --git a/TelegramUI/GroupInfoSearchItem.swift b/TelegramUI/GroupInfoSearchItem.swift index fa67cc3d51..164ec05504 100644 --- a/TelegramUI/GroupInfoSearchItem.swift +++ b/TelegramUI/GroupInfoSearchItem.swift @@ -5,21 +5,22 @@ import Postbox import TelegramCore import SwiftSignalKit -final class GroupInfoSearchItem: ItemListControllerSearch { +final class ChannelMembersSearchItem: ItemListControllerSearch { let account: Account let peerId: PeerId let cancel: () -> Void - let openPeer: (Peer) -> Void - - init(account: Account, peerId: PeerId, cancel: @escaping () -> Void, openPeer: @escaping (Peer) -> Void) { + let openPeer: (Peer, RenderedChannelParticipant?) -> Void + let searchMode: ChannelMembersSearchMode + init(account: Account, peerId: PeerId, searchMode: ChannelMembersSearchMode = .searchMembers, cancel: @escaping () -> Void, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) { self.account = account self.peerId = peerId self.cancel = cancel self.openPeer = openPeer + self.searchMode = searchMode } func isEqual(to: ItemListControllerSearch) -> Bool { - if let to = to as? GroupInfoSearchItem { + if let to = to as? ChannelMembersSearchItem { if self.account !== to.account { return false } @@ -42,16 +43,16 @@ final class GroupInfoSearchItem: ItemListControllerSearch { } 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 - init(account: Account, peerId: PeerId, openPeer: @escaping (Peer) -> Void, cancel: @escaping () -> Void) { - self.containerNode = ChannelMembersSearchContainerNode(account: account, peerId: peerId, mode: .searchMembers, openPeer: { peer, _ in - openPeer(peer) + init(account: Account, peerId: PeerId, searchMode: ChannelMembersSearchMode, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, cancel: @escaping () -> Void) { + self.containerNode = ChannelMembersSearchContainerNode(account: account, peerId: peerId, mode: searchMode, filters: [], openPeer: { peer, participant in + openPeer(peer, participant) }) self.containerNode.cancel = { cancel() diff --git a/TelegramUI/HashtagChatInputContextPanelNode.swift b/TelegramUI/HashtagChatInputContextPanelNode.swift index 1a5585e6f3..80c9f84046 100644 --- a/TelegramUI/HashtagChatInputContextPanelNode.swift +++ b/TelegramUI/HashtagChatInputContextPanelNode.swift @@ -25,6 +25,10 @@ private struct HashtagChatInputContextPanelEntry: Comparable, Identifiable { 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 { 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 { - private var theme: PresentationTheme private let listView: ListView private var currentEntries: [HashtagChatInputContextPanelEntry]? @@ -64,7 +67,6 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { private var validLayout: (CGSize, CGFloat, CGFloat)? override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { - self.theme = theme self.listView = ListView() self.listView.isOpaque = false @@ -94,9 +96,12 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { entries.append(entry) index += 1 } - - let firstTime = self.currentEntries == nil - let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, account: self.account, hashtagSelected: { [weak self] text in + prepareTransition(from: self.currentEntries ?? [], to: entries) + } + + 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 { interfaceInteraction.updateTextInputStateAndMode { textInputState, inputMode in var hashtagQueryRange: NSRange? @@ -122,7 +127,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { } } }) - self.currentEntries = entries + self.currentEntries = to self.enqueueTransition(transition, firstTime: firstTime) } @@ -222,6 +227,14 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { 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) { diff --git a/TelegramUI/HashtagSearchController.swift b/TelegramUI/HashtagSearchController.swift index 9769687c1e..c9ca216d94 100644 --- a/TelegramUI/HashtagSearchController.swift +++ b/TelegramUI/HashtagSearchController.swift @@ -38,7 +38,9 @@ final class HashtagSearchController: TelegramController { let location: SearchMessagesLocation = .general let search = searchMessages(account: account, location: location, query: query) let foundMessages: Signal<[ChatListSearchEntry], NoError> = search - |> map { return $0.map({ .message($0, chatListPresentationData) }) } + |> map { result in + return result.0.map({ .message($0, result.1[$0.id.peerId], chatListPresentationData) }) + } let interaction = ChatListNodeInteraction(activateSearch: { }, peerSelected: { peer in diff --git a/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift b/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift index a5a80e235a..be13815cec 100644 --- a/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift +++ b/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift @@ -67,7 +67,6 @@ private func preparedTransition(from fromEntries: [HorizontalListContextResultsC } final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputContextPanelNode { - private var theme: PresentationTheme private var strings: PresentationStrings private let listView: ListView @@ -82,7 +81,6 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont private var hasValidLayout = false override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { - self.theme = theme self.strings = strings self.separatorNode = ASDisplayNode() @@ -326,6 +324,12 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont 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) { diff --git a/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift b/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift index bd06acd81e..9e2486892a 100644 --- a/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift +++ b/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift @@ -77,11 +77,14 @@ private let iconTextBackgroundImage = generateStretchableFilledCircleImage(radiu final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode { private let imageNodeBackground: ASDisplayNode private let imageNode: TransformImageNode + private var statusNode: RadialStatusNode? private var videoLayer: (SoftwareVideoThumbnailLayer, SoftwareVideoLayerFrameManager, SampleBufferLayer)? private var currentImageResource: TelegramMediaResource? private var currentVideoFile: TelegramMediaFile? - + private var resourceStatus: MediaResourceStatus? private(set) var item: HorizontalListContextResultsChatInputPanelItem? + private var statusDisposable = MetaDisposable() + override var visibility: ListViewItemNodeVisibility { didSet { @@ -165,6 +168,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode displayLink.isPaused = true displayLink.invalidate() } + statusDisposable.dispose() } override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { @@ -189,7 +193,10 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode let sideInset: CGFloat = 4.0 var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - + var updatedStatusSignal: Signal? +//messageFileMediaResourceStatus(account: account, file: file, message: message, isRecentActions: isRecentActions) + + var imageResource: TelegramMediaResource? var stickerFile: TelegramMediaFile? var videoFile: TelegramMediaFile? @@ -206,6 +213,12 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode videoFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource)], mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])]) imageResource = nil } + + if let file = videoFile { + updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(file.resource) + } else if let imageResource = imageResource { + updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource) + } case let .internalReference(_, _, _, title, _, image, file, _): if let image = image { if let largestRepresentation = largestImageRepresentation(image.representations) { @@ -230,7 +243,12 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode if file.isVideo && file.isAnimated { videoFile = file imageResource = nil + updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(file.resource) + } else if let imageResource = imageResource { + updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource) } + } else if let imageResource = imageResource { + updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource) } } @@ -282,6 +300,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode } } + let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: height, height: croppedImageDimensions.width + sideInset), insets: UIEdgeInsets()) return (nodeLayout, { _ in @@ -333,6 +352,52 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode } } + let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((nodeLayout.contentSize.width - 37) / 2), y: floorToScreenPixels((nodeLayout.contentSize.height - 37) / 2)), size: CGSize(width: 37, height: 37)) + + + if let updatedStatusSignal = updatedStatusSignal { + strongSelf.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in + displayLinkDispatcher.dispatch { + if let strongSelf = strongSelf { + strongSelf.resourceStatus = status + + if strongSelf.statusNode == nil { + let statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5)) + strongSelf.statusNode = statusNode + strongSelf.addSubnode(statusNode) + } + + strongSelf.statusNode?.frame = progressFrame + + + let state: RadialStatusNodeState + let statusForegroundColor: UIColor = .white + + switch status { + case let .Fetching(_, progress): + state = RadialStatusNodeState.progress(color: statusForegroundColor, lineWidth: nil, value: CGFloat(progress), cancelEnabled: false) + case .Remote: + state = .download(statusForegroundColor) + case .Local: + state = .none + } + + + if let statusNode = strongSelf.statusNode { + if state == .none { + strongSelf.statusNode = nil + } + statusNode.transitionToState(state, completion: { [weak statusNode] in + if state == .none { + statusNode?.removeFromSupernode() + } + }) + } + } + } + })) + } + if let (thumbnailLayer, _, layer) = strongSelf.videoLayer { thumbnailLayer.bounds = CGRect(origin: CGPoint(), size: CGSize(width: croppedImageDimensions.width, height: croppedImageDimensions.height)) thumbnailLayer.position = CGPoint(x: height / 2.0, y: (nodeLayout.contentSize.height - sideInset) / 2.0 + sideInset) diff --git a/TelegramUI/HorizontalStickersChatContextPanelNode.swift b/TelegramUI/HorizontalStickersChatContextPanelNode.swift index 20528a6cda..058ad04d50 100644 --- a/TelegramUI/HorizontalStickersChatContextPanelNode.swift +++ b/TelegramUI/HorizontalStickersChatContextPanelNode.swift @@ -7,6 +7,38 @@ import Display final class HorizontalStickersChatContextPanelInteraction { 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 { let index: Int @@ -52,7 +84,6 @@ private func preparedGridEntryTransition(account: Account, from fromEntries: [St } final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { - private var theme: PresentationTheme private let backgroundLeftNode: ASImageNode private let backgroundNode: ASImageNode @@ -69,52 +100,23 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { private var stickerPreviewController: StickerPreviewController? 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.displayWithoutProcessing = true self.backgroundNode.displaysAsynchronously = false - self.backgroundNode.image = backgroundCenterImage + self.backgroundNode.image = backgroundCenterImage(theme) self.backgroundLeftNode = ASImageNode() self.backgroundLeftNode.displayWithoutProcessing = true self.backgroundLeftNode.displaysAsynchronously = false - self.backgroundLeftNode.image = backgroundLeftImage + self.backgroundLeftNode.image = backgroundLeftImage(theme) self.backgroundRightNode = ASImageNode() self.backgroundRightNode.displayWithoutProcessing = true self.backgroundRightNode.displaysAsynchronously = false - self.backgroundRightNode.image = backgroundLeftImage + self.backgroundRightNode.image = backgroundLeftImage(theme) self.backgroundRightNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0) self.clippingNode = ASDisplayNode() @@ -218,6 +220,16 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) 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) { diff --git a/TelegramUI/ListMessageDateHeader.swift b/TelegramUI/ListMessageDateHeader.swift index 9742888480..27c50e9fba 100644 --- a/TelegramUI/ListMessageDateHeader.swift +++ b/TelegramUI/ListMessageDateHeader.swift @@ -75,6 +75,19 @@ final class ListMessageDateHeaderNode: ListViewItemHeaderNode { 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) { 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) diff --git a/TelegramUI/ListMessageFileItemNode.swift b/TelegramUI/ListMessageFileItemNode.swift index 5f3ab77bb6..c789d70eb4 100644 --- a/TelegramUI/ListMessageFileItemNode.swift +++ b/TelegramUI/ListMessageFileItemNode.swift @@ -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.linearProgressNode.backgroundColor = item.theme.list.itemAccentColor + } if let (selectionWidth, selectionApply) = selectionNodeWidthAndApply { diff --git a/TelegramUI/MentionChatInputContextPanelNode.swift b/TelegramUI/MentionChatInputContextPanelNode.swift index f19e217f79..6d7a3562ca 100644 --- a/TelegramUI/MentionChatInputContextPanelNode.swift +++ b/TelegramUI/MentionChatInputContextPanelNode.swift @@ -7,22 +7,21 @@ import Display private struct MentionChatInputContextPanelEntry: Comparable, Identifiable { let index: Int let peer: Peer - let theme: PresentationTheme var stableId: Int64 { return self.peer.id.toInt64() } 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 { return lhs.index < rhs.index } - func item(account: Account, inverted: Bool, peerSelected: @escaping (Peer) -> Void) -> ListViewItem { - return MentionChatInputPanelItem(account: account, theme: self.theme, inverted: inverted, peer: self.peer, peerSelected: peerSelected) + func item(account: Account, theme: PresentationTheme, inverted: Bool, peerSelected: @escaping (Peer) -> Void) -> ListViewItem { + return MentionChatInputPanelItem(account: account, theme: theme, inverted: inverted, peer: self.peer, peerSelected: peerSelected) } } @@ -32,12 +31,12 @@ private struct CommandChatInputContextPanelTransition { 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 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 updates = updateIndices.map { ListViewUpdateItem(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, theme: theme, inverted: inverted, peerSelected: peerSelected), directionHint: nil) } return CommandChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates) } @@ -50,8 +49,6 @@ enum MentionChatInputContextPanelMode { final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { let mode: MentionChatInputContextPanelMode - private var theme: PresentationTheme - private let listView: ListView private var currentEntries: [MentionChatInputContextPanelEntry]? @@ -59,7 +56,6 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { private var validLayout: (CGSize, CGFloat, CGFloat)? init(account: Account, theme: PresentationTheme, strings: PresentationStrings, mode: MentionChatInputContextPanelMode) { - self.theme = theme self.mode = mode self.listView = ListView() @@ -91,7 +87,7 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { continue } peerIdSet.insert(peerId) - entries.append(MentionChatInputContextPanelEntry(index: index, peer: peer, theme: self.theme)) + entries.append(MentionChatInputContextPanelEntry(index: index, peer: peer)) index += 1 } self.updateToEntries(entries: entries, forceUpdate: false) @@ -99,7 +95,7 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { private func updateToEntries(entries: [MentionChatInputContextPanelEntry], forceUpdate: Bool) { 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 { switch strongSelf.mode { case .input: diff --git a/TelegramUI/OpenChatMessage.swift b/TelegramUI/OpenChatMessage.swift index e9135367b3..5da0f02b66 100644 --- a/TelegramUI/OpenChatMessage.swift +++ b/TelegramUI/OpenChatMessage.swift @@ -151,7 +151,7 @@ func chatMessagePreviewControllerData(account: Account, message: Message, standa return nil } -func openChatMessage(account: Account, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?, modal: Bool = false, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, transitionNode: @escaping (MessageId, Media) -> (ASDisplayNode, () -> UIView?)?, addToTransitionSurface: @escaping (UIView) -> Void, openUrl: @escaping (String) -> Void, openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, callPeer: @escaping (PeerId) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void, sendSticker: ((FileMediaReference) -> Void)?, setupTemporaryHiddenMedia: @escaping (Signal, Int, Media) -> Void) -> Bool { +func openChatMessage(account: Account, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?, modal: Bool = false, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, transitionNode: @escaping (MessageId, Media) -> (ASDisplayNode, () -> UIView?)?, addToTransitionSurface: @escaping (UIView) -> Void, openUrl: @escaping (String) -> Void, openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, callPeer: @escaping (PeerId) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void, sendSticker: ((FileMediaReference) -> Void)?, setupTemporaryHiddenMedia: @escaping (Signal, Int, Media) -> Void, chatAvatarHiddenMedia: @escaping (Signal, Media) -> Void) -> Bool { if let mediaData = chatMessageGalleryControllerData(account: account, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, synchronousLoad: false) { switch mediaData { case let .url(url): @@ -327,8 +327,14 @@ func openChatMessage(account: Account, message: Message, standalone: Bool, rever return true } case let .chatAvatars(controller, media): + chatAvatarHiddenMedia(controller.hiddenMedia |> map { value -> MessageId? in + if value != nil { + return message.id + } else { + return nil + } + }, media) - present(controller, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in if let selectedTransitionNode = transitionNode(message.id, media) { return GalleryTransitionArguments(transitionNode: selectedTransitionNode, addToTransitionSurface: addToTransitionSurface) diff --git a/TelegramUI/OverlayPlayerControllerNode.swift b/TelegramUI/OverlayPlayerControllerNode.swift index 6a6d628d77..694ae67f08 100644 --- a/TelegramUI/OverlayPlayerControllerNode.swift +++ b/TelegramUI/OverlayPlayerControllerNode.swift @@ -156,7 +156,7 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec openMessageImpl = { [weak self] id in if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.historyNode.messageInCurrentHistoryView(id) { - return openChatMessage(account: strongSelf.account, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _ in }, enqueueMessage: { _ in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }) + return openChatMessage(account: strongSelf.account, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _ in }, enqueueMessage: { _ in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in}) } return false } diff --git a/TelegramUI/PeerChannelMemberCategoriesContextsManager.swift b/TelegramUI/PeerChannelMemberCategoriesContextsManager.swift index b2af6b60bb..f90827aea2 100644 --- a/TelegramUI/PeerChannelMemberCategoriesContextsManager.swift +++ b/TelegramUI/PeerChannelMemberCategoriesContextsManager.swift @@ -6,8 +6,8 @@ import SwiftSignalKit enum PeerChannelMemberContextKey: Hashable { case recent case recentSearch(String) - case admins - case restrictedAndBanned + case admins(String?) + case restrictedAndBanned(String?) } 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) } - func admins(postbox: Postbox, network: Network, peerId: PeerId, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { - return self.getContext(postbox: postbox, network: network, peerId: peerId, key: .admins, requestUpdate: true, updated: updated) + 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(searchQuery), requestUpdate: true, updated: updated) } - func restrictedAndBanned(postbox: Postbox, network: Network, peerId: PeerId, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { - return self.getContext(postbox: postbox, network: network, peerId: peerId, key: .restrictedAndBanned, requestUpdate: true, updated: updated) + 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(searchQuery), requestUpdate: true, updated: updated) } func updateMemberBannedRights(account: Account, peerId: PeerId, memberId: PeerId, bannedRights: TelegramChannelBannedRights?) -> Signal { diff --git a/TelegramUI/PeerMediaCollectionController.swift b/TelegramUI/PeerMediaCollectionController.swift index a48fa0153b..9278e38c50 100644 --- a/TelegramUI/PeerMediaCollectionController.swift +++ b/TelegramUI/PeerMediaCollectionController.swift @@ -28,6 +28,7 @@ public class PeerMediaCollectionController: TelegramController { private var rightNavigationButton: PeerMediaCollectionNavigationButton? private let galleryHiddenMesageAndMediaDisposable = MetaDisposable() + private var presentationDataDisposable:Disposable? private var controllerInteraction: ChatControllerInteraction? 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 if let strongSelf = self, strongSelf.isNodeLoaded, let galleryMessage = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id) { guard let navigationController = strongSelf.navigationController as? NavigationController else { @@ -88,8 +104,7 @@ public class PeerMediaCollectionController: TelegramController { }, callPeer: { peerId in self?.controllerInteraction?.callPeer(peerId) }, enqueueMessage: { _ in - }, sendSticker: nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in - }) + }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in}) } return false }, openPeer: { [weak self] id, navigation, _ in @@ -412,6 +427,19 @@ public class PeerMediaCollectionController: TelegramController { })) } + private func themeAndStringsUpdated() { + self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + // self.chatTitleView?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) + self.updateInterfaceState(animated: false, { state in + var state = state + state = state.updatedTheme(self.presentationData.theme) + state = state.updatedStrings(self.presentationData.strings) + return state + }) + } + required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -422,6 +450,7 @@ public class PeerMediaCollectionController: TelegramController { self.galleryHiddenMesageAndMediaDisposable.dispose() self.messageContextDisposable.dispose() self.resolveUrlDisposable?.dispose() + self.presentationDataDisposable?.dispose() } var mediaCollectionDisplayNode: PeerMediaCollectionControllerNode { @@ -495,6 +524,7 @@ public class PeerMediaCollectionController: TelegramController { func updateInterfaceState(animated: Bool = true, _ f: (PeerMediaCollectionInterfaceState) -> PeerMediaCollectionInterfaceState) { let updatedInterfaceState = f(self.interfaceState) + if self.isNodeLoaded { self.mediaCollectionDisplayNode.updateMediaCollectionInterfaceState(updatedInterfaceState, animated: animated) } @@ -523,6 +553,7 @@ public class PeerMediaCollectionController: TelegramController { } self.mediaCollectionDisplayNode.selectedMessages = updatedInterfaceState.selectionState?.selectedIds + view.disablesInteractiveTransitionGestureRecognizer = updatedInterfaceState.selectionState != nil } } } diff --git a/TelegramUI/PeerMediaCollectionControllerNode.swift b/TelegramUI/PeerMediaCollectionControllerNode.swift index ba4e32d8b8..58deb12761 100644 --- a/TelegramUI/PeerMediaCollectionControllerNode.swift +++ b/TelegramUI/PeerMediaCollectionControllerNode.swift @@ -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 if navigationBarHeight.isZero { sectionOffset = -sectionsHeight @@ -243,7 +243,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode { } else { let selectionPanelBackgroundNode = ASDisplayNode() selectionPanelBackgroundNode.isLayerBacked = true - selectionPanelBackgroundNode.backgroundColor = self.presentationData.theme.chat.inputPanel.panelBackgroundColor + selectionPanelBackgroundNode.backgroundColor = self.mediaCollectionInterfaceState.theme.chat.inputPanel.panelBackgroundColor self.addSubnode(selectionPanelBackgroundNode) self.selectionPanelBackgroundNode = selectionPanelBackgroundNode @@ -258,7 +258,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode { let selectionPanelSeparatorNode = ASDisplayNode() selectionPanelSeparatorNode.isLayerBacked = true - selectionPanelSeparatorNode.backgroundColor = self.presentationData.theme.chat.inputPanel.panelStrokeColor + selectionPanelSeparatorNode.backgroundColor = self.mediaCollectionInterfaceState.theme.chat.inputPanel.panelStrokeColor self.addSubnode(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.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)) let listViewCurve: ListViewAnimationCurve @@ -352,7 +355,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode { } 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() }) @@ -361,6 +364,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode { self.insertSubnode(subnode, belowSubnode: navigationBar) }, placeholder: placeholderNode) } + } func deactivateSearch() { @@ -385,7 +389,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode { let previousMode = self.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()) - node.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + node.backgroundColor = mediaCollectionInterfaceState.theme.list.plainBackgroundColor self.candidateHistoryNode = (node, mediaCollectionInterfaceState.mode) 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)) - 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.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) self.candidateHistoryNodeReadyDisposable.set((node.historyState.get() diff --git a/TelegramUI/PeerMediaCollectionEmptyNode.swift b/TelegramUI/PeerMediaCollectionEmptyNode.swift index 2e3d2508f2..739fddf809 100644 --- a/TelegramUI/PeerMediaCollectionEmptyNode.swift +++ b/TelegramUI/PeerMediaCollectionEmptyNode.swift @@ -5,7 +5,7 @@ import Display final class PeerMediaCollectionEmptyNode: ASDisplayNode { private let mode: PeerMediaCollectionMode - private let theme: PresentationTheme + private var theme: PresentationTheme private let strings: PresentationStrings private let iconNode: ASImageNode @@ -78,7 +78,7 @@ final class PeerMediaCollectionEmptyNode: ASDisplayNode { 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 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) 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) + } } } diff --git a/TelegramUI/PeerMediaCollectionInterfaceState.swift b/TelegramUI/PeerMediaCollectionInterfaceState.swift index 7c428e0e81..c1387f3a31 100644 --- a/TelegramUI/PeerMediaCollectionInterfaceState.swift +++ b/TelegramUI/PeerMediaCollectionInterfaceState.swift @@ -100,4 +100,11 @@ struct PeerMediaCollectionInterfaceState: Equatable { func withMode(_ mode: PeerMediaCollectionMode) -> PeerMediaCollectionInterfaceState { 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) + } } diff --git a/TelegramUI/PeerMediaCollectionSectionsNode.swift b/TelegramUI/PeerMediaCollectionSectionsNode.swift index ea5f646307..26d5d24319 100644 --- a/TelegramUI/PeerMediaCollectionSectionsNode.swift +++ b/TelegramUI/PeerMediaCollectionSectionsNode.swift @@ -40,7 +40,7 @@ final class PeerMediaCollectionSectionsNode: ASDisplayNode { 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 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))) + 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 } diff --git a/TelegramUI/PresentationStrings.swift b/TelegramUI/PresentationStrings.swift index 6dbfe25578..b4ddcfb646 100644 --- a/TelegramUI/PresentationStrings.swift +++ b/TelegramUI/PresentationStrings.swift @@ -1391,6 +1391,18 @@ public final class PresentationStrings { public func Channel_AdminLog_MessageKickedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { 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 Compose_NewEncryptedChat: 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._Channel_AdminLog_MessageKickedNameUsername = getValue(dict, "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.Compose_NewEncryptedChat = getValue(dict, "Compose.NewEncryptedChat") self.PhotoEditor_CropReset = getValue(dict, "PhotoEditor.CropReset") diff --git a/TelegramUI/TelegramController.swift b/TelegramUI/TelegramController.swift index cd16dcbdb6..3109252df1 100644 --- a/TelegramUI/TelegramController.swift +++ b/TelegramUI/TelegramController.swift @@ -27,8 +27,7 @@ private func presentLiveLocationController(account: Account, peerId: PeerId, con }, openPeer: { peer, navigation in }, callPeer: { _ in }, enqueueMessage: { _ in - }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in - }) + }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in}) } }) } diff --git a/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift b/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift index 53c88ca11a..d1e392d93e 100644 --- a/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift +++ b/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift @@ -39,6 +39,15 @@ private enum VerticalListContextResultsChatInputContextPanelEntry: Comparable, I case action(PresentationTheme, String) 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 { switch self { case .action: @@ -113,10 +122,8 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex private var enqueuedTransitions: [(VerticalListContextResultsChatInputContextPanelTransition, Bool)] = [] private var validLayout: (CGSize, CGFloat, CGFloat)? - private var theme: PresentationTheme override init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { - self.theme = theme self.listView = ListView() self.listView.isOpaque = false @@ -155,20 +162,26 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex 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 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 { interfaceInteraction.botSwitchChatWithPayload(results.botId, switchPeer.startParam) } - }, resultSelected: { [weak self] result in - if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { - interfaceInteraction.sendContextResult(results, result) - } + }, resultSelected: { [weak self] result in + if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { + interfaceInteraction.sendContextResult(results, result) + } }) - self.currentEntries = entries + self.currentEntries = to self.enqueueTransition(transition, firstTime: firstTime) } + + private func enqueueTransition(_ transition: VerticalListContextResultsChatInputContextPanelTransition, firstTime: Bool) { enqueuedTransitions.append((transition, firstTime)) @@ -270,6 +283,14 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex 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) {