import Foundation import Display import SwiftSignalKit import Postbox import TelegramCore private final class ChannelDiscussionGroupSetupControllerArguments { let account: Account let createGroup: () -> Void let selectGroup: (PeerId) -> Void let unlinkGroup: () -> Void init(account: Account, createGroup: @escaping () -> Void, selectGroup: @escaping (PeerId) -> Void, unlinkGroup: @escaping () -> Void) { self.account = account self.createGroup = createGroup self.selectGroup = selectGroup self.unlinkGroup = unlinkGroup } } private enum ChannelDiscussionGroupSetupControllerSection: Int32 { case header case groups case unlink } private enum ChannelDiscussionGroupSetupControllerEntryStableId: Hashable { case id(Int) case peer(PeerId) } private enum ChannelDiscussionGroupSetupControllerEntry: ItemListNodeEntry { case header(PresentationTheme, String, String) case create(PresentationTheme, String) case group(Int, PresentationTheme, PresentationStrings, Peer, PresentationPersonNameOrder) case groupsInfo(PresentationTheme, String) case unlink(PresentationTheme, String) var section: Int32 { switch self { case .header: return ChannelDiscussionGroupSetupControllerSection.header.rawValue case .create, .group, .groupsInfo: return ChannelDiscussionGroupSetupControllerSection.groups.rawValue case .unlink: return ChannelDiscussionGroupSetupControllerSection.unlink.rawValue } } var stableId: ChannelDiscussionGroupSetupControllerEntryStableId { switch self { case .header: return .id(0) case .create: return .id(1) case let .group(_, _, _, peer, _): return .peer(peer.id) case .groupsInfo: return .id(2) case .unlink: return .id(3) } } static func ==(lhs: ChannelDiscussionGroupSetupControllerEntry, rhs: ChannelDiscussionGroupSetupControllerEntry) -> Bool { switch lhs { case let .header(lhsTheme, lhsTitle, lhsLabel): if case let .header(rhsTheme, rhsTitle, rhsLabel) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsLabel == rhsLabel { return true } else { return false } case let .create(lhsTheme, lhsTitle): if case let .create(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle { return true } else { return false } case let .group(lhsIndex, lhsTheme, lhsStrings, lhsPeer, lhsNameOrder): if case let .group(rhsIndex, rhsTheme, rhsStrings, rhsPeer, rhsNameOrder) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings == rhsStrings, lhsPeer.isEqual(rhsPeer), lhsNameOrder == rhsNameOrder { return true } else { return false } case let .groupsInfo(lhsTheme, lhsTitle): if case let .groupsInfo(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle { return true } else { return false } case let .unlink(lhsTheme, lhsTitle): if case let .unlink(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle { return true } else { return false } } } private var sortIndex: Int { switch self { case .header: return 0 case .create: return 1 case let .group(index, _, _, _, _): return 10 + index case .groupsInfo: return 1000 case .unlink: return 1001 } } static func <(lhs: ChannelDiscussionGroupSetupControllerEntry, rhs: ChannelDiscussionGroupSetupControllerEntry) -> Bool { return lhs.sortIndex < rhs.sortIndex } func item(_ arguments: ChannelDiscussionGroupSetupControllerArguments) -> ListViewItem { switch self { case let .header(theme, title, label): return ChannelDiscussionGroupSetupHeaderItem(theme: theme, text: title, label: label, sectionId: self.section) case let .create(theme, text): return ItemListPeerActionItem(theme: theme, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, editing: false, action: { arguments.createGroup() }) case let .group(_, theme, strings, peer, nameOrder): let text: String if let peer = peer as? TelegramChannel, let addressName = peer.addressName, !addressName.isEmpty { text = "@\(addressName)" } else { text = strings.Channel_DiscussionGroup_PrivateGroup } return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: nameOrder, account: arguments.account, peer: peer, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .text(text), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { arguments.selectGroup(peer.id) }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) case let .groupsInfo(theme, title): return ItemListTextItem(theme: theme, text: .plain(title), sectionId: self.section) case let .unlink(theme, title): return ItemListActionItem(theme: theme, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: { arguments.unlinkGroup() }) } } } private func channelDiscussionGroupSetupControllerEntries(presentationData: PresentationData, view: PeerView, groups: [Peer]?) -> [ChannelDiscussionGroupSetupControllerEntry] { guard let peer = view.peers[view.peerId] as? TelegramChannel, let cachedData = view.cachedData as? CachedChannelData else { return [] } var entries: [ChannelDiscussionGroupSetupControllerEntry] = [] if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId { if let group = view.peers[linkedDiscussionPeerId] { if case .group = peer.info { entries.append(.header(presentationData.theme, presentationData.strings.Channel_DiscussionGroup_HeaderSet(group.displayTitle).0, presentationData.strings.Channel_DiscussionGroup_HeaderLabel)) } else { entries.append(.header(presentationData.theme, presentationData.strings.Channel_DiscussionGroup_HeaderSet(group.displayTitle).0, presentationData.strings.Channel_DiscussionGroup_HeaderLabel)) } entries.append(.group(0, presentationData.theme, presentationData.strings, group, presentationData.nameDisplayOrder)) entries.append(.groupsInfo(presentationData.theme, presentationData.strings.Channel_DiscussionGroup_Info)) let unlinkText: String if case .group = peer.info { unlinkText = presentationData.strings.Channel_DiscussionGroup_UnlinkChannel } else { unlinkText = presentationData.strings.Channel_DiscussionGroup_UnlinkGroup } entries.append(.unlink(presentationData.theme, unlinkText)) } } else if case .broadcast = peer.info { if let groups = groups { entries.append(.header(presentationData.theme, presentationData.strings.Channel_DiscussionGroup_Header, presentationData.strings.Channel_DiscussionGroup_HeaderLabel)) entries.append(.create(presentationData.theme, presentationData.strings.Channel_DiscussionGroup_Create)) var index = 0 for group in groups { entries.append(.group(index, presentationData.theme, presentationData.strings, group, presentationData.nameDisplayOrder)) index += 1 } entries.append(.groupsInfo(presentationData.theme, presentationData.strings.Channel_DiscussionGroup_Info)) } } return entries } private struct ChannelDiscussionGroupSetupControllerState: Equatable { } public func channelDiscussionGroupSetupController(context: AccountContext, peerId: PeerId) -> ViewController { let statePromise = ValuePromise(ChannelDiscussionGroupSetupControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: ChannelDiscussionGroupSetupControllerState()) let updateState: ((ChannelDiscussionGroupSetupControllerState) -> ChannelDiscussionGroupSetupControllerState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } let groupPeers = Promise<[Peer]?>() groupPeers.set(.single(nil) |> then( availableGroupsForChannelDiscussion(network: context.account.network) |> map(Optional.init) |> `catch` { _ -> Signal<[Peer]?, NoError> in return .single(nil) } )) let peerView = context.account.viewTracker.peerView(peerId) var dismissImpl: (() -> Void)? var pushControllerImpl: ((ViewController) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)? var navigateToGroupImpl: ((PeerId) -> Void)? let actionsDisposable = DisposableSet() let applyGroupDisposable = MetaDisposable() actionsDisposable.add(applyGroupDisposable) let arguments = ChannelDiscussionGroupSetupControllerArguments(account: context.account, createGroup: { let _ = (context.account.postbox.transaction { transaction -> Peer? in transaction.getPeer(peerId) } |> deliverOnMainQueue).start(next: { peer in guard let peer = peer else { return } pushControllerImpl?(createGroupController(context: context, peerIds: [], initialTitle: peer.displayTitle + " Chat", supergroup: true, completion: { groupId, dismiss in var applySignal = updateGroupDiscussionForChannel(network: context.account.network, postbox: context.account.postbox, channelId: peerId, groupId: groupId) var cancelImpl: (() -> Void)? let progressSignal = Signal { subscriber in let presentationData = context.sharedContext.currentPresentationData.with { $0 } let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: { cancelImpl?() })) presentControllerImpl?(controller, nil) return ActionDisposable { [weak controller] in Queue.mainQueue().async() { controller?.dismiss() } } } |> runOn(Queue.mainQueue()) |> delay(0.15, queue: Queue.mainQueue()) let progressDisposable = progressSignal.start() applySignal = applySignal |> afterDisposed { Queue.mainQueue().async { progressDisposable.dispose() } } cancelImpl = { applyGroupDisposable.set(nil) } applyGroupDisposable.set((applySignal |> deliverOnMainQueue).start(error: { _ in let presentationData = context.sharedContext.currentPresentationData.with { $0 } presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) dismiss() }, completed: { dismiss() /*let presentationData = context.sharedContext.currentPresentationData.with { $0 } let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .success) presentControllerImpl?(controller, nil)*/ })) })) }) }, selectGroup: { groupId in let _ = (context.account.postbox.transaction { transaction -> (CachedChannelData?, Peer?, Peer?) in return (transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData, transaction.getPeer(peerId), transaction.getPeer(groupId)) } |> deliverOnMainQueue).start(next: { cachedData, channelPeer, groupPeer in guard let cachedData = cachedData, let channelPeer = channelPeer, let groupPeer = groupPeer else { return } if groupId == cachedData.linkedDiscussionPeerId { navigateToGroupImpl?(groupId) return } let presentationData = context.sharedContext.currentPresentationData.with { $0 } let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ChannelDiscussionGroupActionSheetItem(context: context, channelPeer: channelPeer, groupPeer: groupPeer, strings: presentationData.strings), ActionSheetButtonItem(title: presentationData.strings.Channel_DiscussionGroup_LinkGroup, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() var applySignal = updateGroupDiscussionForChannel(network: context.account.network, postbox: context.account.postbox, channelId: peerId, groupId: groupId) var cancelImpl: (() -> Void)? let progressSignal = Signal { subscriber in let presentationData = context.sharedContext.currentPresentationData.with { $0 } let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: { cancelImpl?() })) presentControllerImpl?(controller, nil) return ActionDisposable { [weak controller] in Queue.mainQueue().async() { controller?.dismiss() } } } |> runOn(Queue.mainQueue()) |> delay(0.15, queue: Queue.mainQueue()) let progressDisposable = progressSignal.start() applySignal = applySignal |> afterDisposed { Queue.mainQueue().async { progressDisposable.dispose() } } cancelImpl = { applyGroupDisposable.set(nil) } applyGroupDisposable.set((applySignal |> deliverOnMainQueue).start(error: { _ in let presentationData = context.sharedContext.currentPresentationData.with { $0 } presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) }, completed: { /*let presentationData = context.sharedContext.currentPresentationData.with { $0 } let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .success) presentControllerImpl?(controller, nil)*/ })) }) ]), ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() }) ])]) presentControllerImpl?(actionSheet, nil) }) }, unlinkGroup: { let _ = (context.account.postbox.transaction { transaction -> (CachedChannelData?, Peer?) in return (transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData, transaction.getPeer(peerId)) } |> deliverOnMainQueue).start(next: { cachedData, peer in guard let cachedData = cachedData, let peer = peer as? TelegramChannel else { return } let applyPeerId: PeerId if case .broadcast = peer.info { applyPeerId = peerId } else if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId { applyPeerId = linkedDiscussionPeerId } else { return } var applySignal = updateGroupDiscussionForChannel(network: context.account.network, postbox: context.account.postbox, channelId: applyPeerId, groupId: nil) var cancelImpl: (() -> Void)? let progressSignal = Signal { subscriber in let presentationData = context.sharedContext.currentPresentationData.with { $0 } let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: { cancelImpl?() })) presentControllerImpl?(controller, nil) return ActionDisposable { [weak controller] in Queue.mainQueue().async() { controller?.dismiss() } } } |> runOn(Queue.mainQueue()) |> delay(0.15, queue: Queue.mainQueue()) let progressDisposable = progressSignal.start() applySignal = applySignal |> afterDisposed { Queue.mainQueue().async { progressDisposable.dispose() } } cancelImpl = { applyGroupDisposable.set(nil) } applyGroupDisposable.set((applySignal |> deliverOnMainQueue).start(error: { _ in let presentationData = context.sharedContext.currentPresentationData.with { $0 } presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) }, completed: { if let channel = peer as? TelegramChannel, case .group = channel.info { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .success) presentControllerImpl?(controller, nil) dismissImpl?() } })) }) }) var wasEmpty: Bool? let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), peerView, groupPeers.get()) |> deliverOnMainQueue |> map { presentationData, state, view, groups -> (ItemListControllerState, (ItemListNodeState, ChannelDiscussionGroupSetupControllerEntry.ItemGenerationArguments)) in let title: String if let peer = view.peers[view.peerId] as? TelegramChannel, case .broadcast = peer.info { title = presentationData.strings.Channel_DiscussionGroup } else { title = presentationData.strings.Group_LinkedChannel } var crossfade = false var isEmptyState = false if let cachedData = view.cachedData as? CachedChannelData { let isEmpty = cachedData.linkedDiscussionPeerId == nil if cachedData.linkedDiscussionPeerId == nil, let peer = view.peers[view.peerId] as? TelegramChannel, case .broadcast = peer.info { if groups == nil { isEmptyState = true } } if let wasEmpty = wasEmpty, wasEmpty != isEmpty { crossfade = true } wasEmpty = isEmpty } else { isEmptyState = true } var emptyStateItem: ItemListControllerEmptyStateItem? if isEmptyState { emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) } let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) let listState = ItemListNodeState(entries: channelDiscussionGroupSetupControllerEntries(presentationData: presentationData, view: view, groups: groups), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: crossfade, animateChanges: false) return (controllerState, (listState, arguments)) } |> afterDisposed { actionsDisposable.dispose() } let controller = ItemListController(context: context, state: signal) dismissImpl = { [weak controller] in if let controller = controller { (controller.navigationController as? NavigationController)?.filterController(controller, animated: true) } } pushControllerImpl = { [weak controller] c in (controller?.navigationController as? NavigationController)?.pushViewController(c) } presentControllerImpl = { [weak controller] c, a in controller?.present(c, in: .window(.root), with: a) } navigateToGroupImpl = { [weak controller] groupId in guard let navigationController = controller?.navigationController as? NavigationController else { return } navigateToChatController(navigationController: navigationController, context: context, chatLocation: .peer(groupId), keepStack: .always) } return controller }