import Foundation import UIKit import Display import TelegramCore import SwiftSignalKit import TelegramPresentationData import TelegramBaseController import AccountContext import AlertUI import PresentationDataUtils import ChatPresentationInterfaceState import ChatNavigationButton import ChatTitleView import ComponentFlow import AdminUserActionsSheet public final class ChatRecentActionsController: TelegramBaseController { private var controllerNode: ChatRecentActionsControllerNode { return self.displayNode as! ChatRecentActionsControllerNode } private let context: AccountContext private let peer: EnginePeer private let initialAdminPeerId: EnginePeer.Id? let starsState: StarsRevenueStats? private var presentationData: PresentationData private var presentationDataPromise = Promise() override public var updatedPresentationData: (PresentationData, Signal) { return (self.presentationData, self.presentationDataPromise.get()) } private var presentationDataDisposable: Disposable? private var didSetPresentationData = false private var panelInteraction: ChatPanelInterfaceInteraction! private let titleView: ChatNavigationBarTitleView private var rightBarButton: ChatNavigationButton? private var preferredGlassType: ChatPresentationInterfaceState.GlassType = .default private var adminsDisposable: Disposable? public init(context: AccountContext, peer: EnginePeer, adminPeerId: EnginePeer.Id?, starsState: StarsRevenueStats?) { self.context = context self.peer = peer self.initialAdminPeerId = adminPeerId self.starsState = starsState self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.titleView = ChatNavigationBarTitleView(frame: CGRect()) super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: false, hideBadge: false, glassStyle: .default)) self._hasGlassStyle = true self.automaticallyControlPresentationContextLayout = false self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.panelInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _, _, _ in }, setupEditMessage: { _, _ in }, beginMessageSelection: { _, _ in }, cancelMessageSelection: { _ in }, deleteSelectedMessages: { _ in }, reportSelectedMessages: { }, reportMessages: { _, _ in }, blockMessageAuthor: { _, _ in }, deleteMessages: { _, _, f in f(.default) }, forwardSelectedMessages: { }, forwardCurrentForwardMessages: { }, forwardMessages: { _ in }, updateForwardOptionsState: { _ in }, presentForwardOptions: { _ in }, presentReplyOptions: { _ in }, presentLinkOptions: { _ in }, presentSuggestPostOptions: { }, shareSelectedMessages: { }, updateTextInputStateAndMode: { _ in }, updateInputModeAndDismissedButtonKeyboardMessageId: { _ in }, openStickers: { }, editMessage: { }, beginMessageSearch: { _, _ in }, dismissMessageSearch: { }, updateMessageSearch: { _ in }, openSearchResults: { }, navigateMessageSearch: { _ in }, openCalendarSearch: { }, toggleMembersSearch: { _ in }, navigateToMessage: { _, _, _, _ in }, navigateToChat: { _ in }, navigateToProfile: { _ in }, openPeerInfo: { }, togglePeerNotifications: { }, sendContextResult: { _, _, _, _ in return false }, sendBotCommand: { _, _ in }, sendShortcut: { _ in }, openEditShortcuts: { }, sendBotStart: { _ in }, botSwitchChatWithPayload: { _, _ in }, beginMediaRecording: { _ in }, finishMediaRecording: { _ in }, stopMediaRecording: { }, stopIncomingStreamingMessage: { }, lockMediaRecording: { }, resumeMediaRecording: { }, deleteRecordedMedia: { }, sendRecordedMedia: { _, _ in }, displayRestrictedInfo: { _, _ in }, displayVideoUnmuteTip: { _ in }, switchMediaRecordingMode: { }, setupMessageAutoremoveTimeout: { }, sendSticker: { _, _, _, _, _, _ in return false }, editSticker: { _ in }, unblockPeer: { }, pinMessage: { _, _ in }, unpinMessage: { _, _, _ in }, unpinAllMessages: { }, openPinnedList: { _ in }, shareAccountContact: { }, reportPeer: { }, presentPeerContact: { }, dismissReportPeer: { }, deleteChat: { }, beginCall: { _ in }, toggleMessageStickerStarred: { _ in }, presentController: { _, _ in }, presentControllerInCurrent: { _, _ in }, getNavigationController: { return nil }, presentGlobalOverlayController: { _, _ in }, navigateFeed: { }, openGrouping: { }, toggleSilentPost: { }, requestUnvoteInMessage: { _ in }, requestStopPollInMessage: { _ in }, updateInputLanguage: { _ in }, unarchiveChat: { }, openLinkEditing: { }, openDateEditing: { }, displaySlowmodeTooltip: { _, _ in }, displaySendMessageOptions: { _, _ in }, openScheduledMessages: { }, displaySearchResultsTooltip: { _, _ in }, unarchivePeer: { }, scrollToTop: { }, viewReplies: { _, _ in }, activatePinnedListPreview: { _, _ in }, joinGroupCall: { _ in }, presentInviteMembers: { }, presentGigagroupHelp: { }, openMonoforum: { }, editMessageMedia: { _, _ in }, updateShowCommands: { _ in }, updateShowSendAsPeers: { _ in }, openInviteRequests: { }, openSendAsPeer: { _, _ in }, presentChatRequestAdminInfo: { }, displayCopyProtectionTip: { _, _ in }, openWebView: { _, _, _, _ in }, updateShowWebView: { _ in }, insertText: { _ in }, backwardsDeleteText: { }, restartTopic: { }, toggleTranslation: { _ in }, changeTranslationLanguage: { _ in }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { }, openSuggestPost: { _, _ in }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { }, openBoostToUnrestrict: { }, updateRecordingTrimRange: { _, _, _, _ in }, dismissAllTooltips: { }, editTodoMessage: { _, _, _ in }, dismissUrlPreview: { }, dismissForwardMessages: { }, dismissSuggestPost: { }, displayUndo: { _ in }, presentInputTextTranslation: { _, _ in }, sendEmoji: { _, _, _ in }, openAICompose: { }, openExpandedInput: { }, openSetPeerAvatar: { }, updateHistoryFilter: { _ in }, updateChatLocationThread: { _, _ in }, toggleChatSidebarMode: { }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in }, chatController: { return nil }, statuses: nil) self.navigationItem.titleView = self.titleView let rightBarButton = ChatNavigationButton(action: .search(hasTags: false), buttonItem: UIBarButtonItem(image: PresentationResourcesRootController.navigationSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.activateSearch))) self.rightBarButton = rightBarButton self.updateTitle() let chatTheme = self.context.account.postbox.peerView(id: peer.id) |> map { view -> ChatTheme? in let cachedData = view.cachedData if let cachedData = cachedData as? CachedUserData { return cachedData.chatTheme } else if let cachedData = cachedData as? CachedGroupData { return cachedData.chatTheme } else if let cachedData = cachedData as? CachedChannelData { return cachedData.chatTheme } else { return nil } } |> distinctUntilChanged self.presentationDataDisposable = combineLatest( queue: Queue.mainQueue(), context.sharedContext.presentationData, context.engine.themes.getChatThemes(accountManager: context.sharedContext.accountManager, onlyCached: true), chatTheme ).startStrict(next: { [weak self] presentationData, chatThemes, chatTheme in if let strongSelf = self { let previousTheme = strongSelf.presentationData.theme let previousStrings = strongSelf.presentationData.strings var presentationData = presentationData if let chatTheme { switch chatTheme { case let .emoticon(emoticon): if let theme = chatThemes.first(where: { $0.emoticon == emoticon }), let theme = makePresentationTheme(cloudTheme: theme, dark: presentationData.theme.overallDarkAppearance) { presentationData = presentationData.withUpdated(theme: theme) presentationData = presentationData.withUpdated(chatWallpaper: theme.chat.defaultWallpaper) } case let .gift(gift, wallpaper): let _ = gift let _ = wallpaper //TODO:release } } let isFirstTime = !strongSelf.didSetPresentationData strongSelf.presentationData = presentationData strongSelf.presentationDataPromise.set(.single(presentationData)) strongSelf.didSetPresentationData = true if isFirstTime || previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { strongSelf.updateThemeAndStrings() } } }) } required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit { self.presentationDataDisposable?.dispose() self.adminsDisposable?.dispose() } private func updateThemeAndStrings() { self.updatePreferredGlassType() self.updateTitle() let rightButton = ChatNavigationButton(action: .search(hasTags: false), buttonItem: UIBarButtonItem(image: PresentationResourcesRootController.navigationSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.activateSearch))) self.rightBarButton = rightButton self.navigationItem.setRightBarButton(rightButton.buttonItem, animated: false) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.updateNavigationBarPresentation() if let searchNode = self.navigationBar?.contentNode as? ChatRecentActionsSearchNavigationContentNode { searchNode.updateThemeAndStrings(theme: self.presentationData.theme, preferClearGlass: self.preferredGlassType == .clear, strings: self.presentationData.strings) } if self.isNodeLoaded { self.controllerNode.updatePresentationData(self.presentationData) } } override public func loadDisplayNode() { self.displayNode = ChatRecentActionsControllerNode(context: self.context, controller: self, peer: self.peer._asPeer(), presentationData: self.presentationData, pushController: { [weak self] c in (self?.navigationController as? NavigationController)?.pushViewController(c) }, presentController: { [weak self] c, t, a in self?.present(c, in: t, with: a, blockInteraction: true) }, getNavigationController: { [weak self] in return self?.navigationController as? NavigationController }) self.controllerNode.isEmptyUpdated = { [weak self] isEmpty in guard let self, let rightBarButton = self.rightBarButton else { return } self.navigationItem.setRightBarButton(isEmpty ? nil : rightBarButton.buttonItem, animated: true) } self.controllerNode.contentStatsUpdated = { [weak self] in self?.updatePreferredGlassType() } self.controllerNode.updatePreferredGlassType(self.preferredGlassType, transition: .immediate) if let adminPeerId = self.initialAdminPeerId { self.controllerNode.updateFilter(events: .all, adminPeerIds: [adminPeerId]) self.updateTitle() } self.displayNodeDidLoad() self.updatePreferredGlassType() } override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) var childrenLayout = layout childrenLayout.intrinsicInsets.bottom += 49.0 self.presentationContext.containerLayoutUpdated(childrenLayout, transition: transition) self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) } @objc private func activateSearch() { if let navigationBar = self.navigationBar { if !(navigationBar.contentNode is ChatRecentActionsSearchNavigationContentNode) { let searchNavigationNode = ChatRecentActionsSearchNavigationContentNode(theme: self.presentationData.theme, preferClearGlass: self.preferredGlassType == .clear, strings: self.presentationData.strings, cancel: { [weak self] in self?.deactivateSearch() }) navigationBar.setContentNode(searchNavigationNode, animated: true) searchNavigationNode.setQueryUpdated({ [weak self] query in self?.controllerNode.updateSearchQuery(query) self?.updateTitle() }) searchNavigationNode.activate() } } } private func deactivateSearch() { self.controllerNode.updateSearchQuery("") self.navigationBar?.setContentNode(nil, animated: true) self.updateTitle() } private var adminsPromise: Promise<[RenderedChannelParticipant]?>? func openFilterSetup() { if self.adminsPromise == nil { self.adminsPromise = Promise() let (disposable, _) = self.context.peerChannelMemberCategoriesContextsManager.admins(engine: self.context.engine, accountPeerId: self.context.account.peerId, peerId: self.peer.id) { membersState in if case .loading = membersState.loadingState, membersState.list.isEmpty { self.adminsPromise?.set(.single(nil)) } else { self.adminsPromise?.set(.single(membersState.list)) } } self.adminsDisposable = disposable } guard let adminsPromise = self.adminsPromise else { return } let _ = (adminsPromise.get() |> filter { $0 != nil } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] result in guard let self else { return } var adminPeers: [EnginePeer] = [] if let result { for participant in result { adminPeers.append(participant.peer) } } let controller = RecentActionsSettingsSheet( context: self.context, peer: self.peer, adminPeers: adminPeers, initialValue: RecentActionsSettingsSheet.Value( events: self.controllerNode.filter.events, admins: self.controllerNode.filter.adminPeerIds ), completion: { [weak self] result in guard let self else { return } self.controllerNode.updateFilter(events: result.events, adminPeerIds: result.admins) self.updateTitle() } ) self.push(controller) }) } private func updateNavigationBarPresentation() { let navigationBarTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBadge: false, edgeEffectColor: .clear, glassStyle: self.preferredGlassType == .clear ? .clear : .default) self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)), transition: .immediate) } private func updatePreferredGlassType() { var preferredGlassType: ChatPresentationInterfaceState.GlassType = .default if self.isNodeLoaded && self.controllerNode.backgroundContentIsSaturated { preferredGlassType = .clear } if !self.presentationData.theme.overallDarkAppearance { preferredGlassType = .default } if self.context.sharedContext.immediateExperimentalUISettings.forceClearGlass { preferredGlassType = .clear } if self.preferredGlassType != preferredGlassType { self.preferredGlassType = preferredGlassType self.updateNavigationBarPresentation() self.updateTitle() if let searchNode = self.navigationBar?.contentNode as? ChatRecentActionsSearchNavigationContentNode { searchNode.updateThemeAndStrings(theme: self.presentationData.theme, preferClearGlass: self.preferredGlassType == .clear, strings: self.presentationData.strings) } if self.isNodeLoaded { self.controllerNode.updatePreferredGlassType(preferredGlassType, transition: .immediate) } } } private func updateTitle() { let title = self.peer.compactDisplayTitle let subtitle: String if !self.isNodeLoaded || self.controllerNode.filter.isEmpty { subtitle = self.presentationData.strings.Channel_AdminLog_TitleAllEvents } else { subtitle = self.presentationData.strings.Channel_AdminLog_TitleSelectedEvents } let _ = self.titleView.update( context: self.context, theme: self.presentationData.theme, preferClearGlass: self.preferredGlassType == .clear, wallpaper: self.presentationData.chatWallpaper, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, content: .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(title))], subtitle: subtitle, isEnabled: false), transition: .immediate ) } }