Reaction list setup

This commit is contained in:
Ali
2021-12-17 00:20:30 +04:00
parent 321fe4e052
commit 1ac4c70dec
12 changed files with 1473 additions and 59 deletions

View File

@@ -0,0 +1,309 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import ItemListUI
import PresentationDataUtils
import AccountContext
import PresentationDataUtils
private final class PeerAllowedReactionListControllerArguments {
let context: AccountContext
let toggleAll: () -> Void
let toggleItem: (String) -> Void
init(
context: AccountContext,
toggleAll: @escaping () -> Void,
toggleItem: @escaping (String) -> Void
) {
self.context = context
self.toggleAll = toggleAll
self.toggleItem = toggleItem
}
}
private enum PeerAllowedReactionListControllerSection: Int32 {
case all
case items
}
private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry {
enum StableId: Hashable {
case allowAll
case allowAllInfo
case itemsHeader
case item(String)
}
case allowAll(text: String, isEnabled: Bool)
case allowAllInfo(String)
case itemsHeader(String)
case item(index: Int, value: String, file: TelegramMediaFile?, text: String, isEnabled: Bool)
var section: ItemListSectionId {
switch self {
case .allowAll, .allowAllInfo:
return PeerAllowedReactionListControllerSection.all.rawValue
case .itemsHeader, .item:
return PeerAllowedReactionListControllerSection.items.rawValue
}
}
var stableId: StableId {
switch self {
case .allowAll:
return .allowAll
case .allowAllInfo:
return .allowAllInfo
case .itemsHeader:
return .itemsHeader
case let .item(_, value, _, _, _):
return .item(value)
}
}
var sortId: Int {
switch self {
case .allowAll:
return 0
case .allowAllInfo:
return 1
case .itemsHeader:
return 2
case let .item(index, _, _, _, _):
return 100 + index
}
}
static func ==(lhs: PeerAllowedReactionListControllerEntry, rhs: PeerAllowedReactionListControllerEntry) -> Bool {
switch lhs {
case let .allowAll(text, isEnabled):
if case .allowAll(text, isEnabled) = rhs {
return true
} else {
return false
}
case let .allowAllInfo(text):
if case .allowAllInfo(text) = rhs {
return true
} else {
return false
}
case let .itemsHeader(text):
if case .itemsHeader(text) = rhs {
return true
} else {
return false
}
case let .item(index, value, file, text, isEnabled):
if case .item(index, value, file, text, isEnabled) = rhs {
return true
} else {
return false
}
}
}
static func <(lhs: PeerAllowedReactionListControllerEntry, rhs: PeerAllowedReactionListControllerEntry) -> Bool {
return lhs.sortId < rhs.sortId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! PeerAllowedReactionListControllerArguments
switch self {
case let .allowAll(text, isEnabled):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: isEnabled, sectionId: self.section, style: .blocks, updated: { _ in
arguments.toggleAll()
})
case let .allowAllInfo(text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .itemsHeader(text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .item(_, value, file, text, isEnabled):
let _ = file
return ItemListSwitchItem(presentationData: presentationData, title: "\(value) \(text)", value: isEnabled, sectionId: self.section, style: .blocks, updated: { _ in
arguments.toggleItem(value)
})
}
}
}
private struct PeerAllowedReactionListControllerState: Equatable {
var updatedAllowedReactions: Set<String>? = nil
}
private func peerAllowedReactionListControllerEntries(
presentationData: PresentationData,
availableReactions: AvailableReactions?,
cachedData: CachedPeerData?,
state: PeerAllowedReactionListControllerState
) -> [PeerAllowedReactionListControllerEntry] {
var entries: [PeerAllowedReactionListControllerEntry] = []
if let availableReactions = availableReactions, let allowedReactions = state.updatedAllowedReactions {
entries.append(.allowAll(text: "Allow Reactions", isEnabled: !allowedReactions.isEmpty))
entries.append(.allowAllInfo("Allow subscribers to reacts to channel posts."))
entries.append(.itemsHeader("AVAILABLE REACTIONS"))
var index = 0
for availableReaction in availableReactions.reactions {
entries.append(.item(index: index, value: availableReaction.value, file: availableReaction.staticIcon, text: availableReaction.title, isEnabled: allowedReactions.contains(availableReaction.value)))
index += 1
}
}
return entries
}
public func peerAllowedReactionListController(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
peerId: PeerId
) -> ViewController {
let statePromise = ValuePromise(PeerAllowedReactionListControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: PeerAllowedReactionListControllerState())
let updateState: ((PeerAllowedReactionListControllerState) -> PeerAllowedReactionListControllerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
var dismissImpl: (() -> Void)?
let _ = dismissImpl
let actionsDisposable = DisposableSet()
actionsDisposable.add((context.account.postbox.transaction { transaction -> Set<String>? in
let cachedData = transaction.getPeerCachedData(peerId: peerId)
if let cachedData = cachedData as? CachedChannelData {
return cachedData.allowedReactions.flatMap(Set.init)
} else if let cachedData = cachedData as? CachedGroupData {
return cachedData.allowedReactions.flatMap(Set.init)
} else {
return nil
}
}
|> deliverOnMainQueue).start(next: { allowedReactions in
updateState { state in
var state = state
state.updatedAllowedReactions = allowedReactions
return state
}
}))
let arguments = PeerAllowedReactionListControllerArguments(
context: context,
toggleAll: {
let _ = (context.engine.stickers.availableReactions()
|> take(1)
|> deliverOnMainQueue).start(next: { availableReactions in
guard let availableReactions = availableReactions else {
return
}
updateState { state in
var state = state
if var updatedAllowedReactions = state.updatedAllowedReactions {
if updatedAllowedReactions.isEmpty {
for availableReaction in availableReactions.reactions {
updatedAllowedReactions.insert(availableReaction.value)
}
} else {
updatedAllowedReactions.removeAll()
}
state.updatedAllowedReactions = updatedAllowedReactions
}
return state
}
})
},
toggleItem: { reaction in
updateState { state in
var state = state
if var updatedAllowedReactions = state.updatedAllowedReactions {
if updatedAllowedReactions.contains(reaction) {
updatedAllowedReactions.remove(reaction)
} else {
updatedAllowedReactions.insert(reaction)
}
state.updatedAllowedReactions = updatedAllowedReactions
}
return state
}
}
)
let peerView = context.account.viewTracker.peerView(peerId)
|> deliverOnMainQueue
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
let signal = combineLatest(queue: .mainQueue(),
presentationData,
statePromise.get(),
context.engine.stickers.availableReactions(),
peerView
)
|> deliverOnMainQueue
|> map { presentationData, state, availableReactions, peerView -> (ItemListControllerState, (ItemListNodeState, Any)) in
//TODO:localize
let title: String = "Reactions"
let entries = peerAllowedReactionListControllerEntries(
presentationData: presentationData,
availableReactions: availableReactions,
cachedData: peerView.cachedData,
state: state
)
let controllerState = ItemListControllerState(
presentationData: ItemListPresentationData(presentationData),
title: .text(title),
leftNavigationButton: nil,
rightNavigationButton: nil,
backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back),
animateChanges: false
)
let listState = ItemListNodeState(
presentationData: ItemListPresentationData(presentationData),
entries: entries,
style: .blocks,
animateChanges: true
)
return (controllerState, (listState, arguments))
}
|> afterDisposed {
actionsDisposable.dispose()
}
let controller = ItemListController(context: context, state: signal)
controller.willDisappear = { _ in
let _ = (context.account.postbox.transaction { transaction -> Set<String>? in
let cachedData = transaction.getPeerCachedData(peerId: peerId)
if let cachedData = cachedData as? CachedChannelData {
return cachedData.allowedReactions.flatMap(Set.init)
} else if let cachedData = cachedData as? CachedGroupData {
return cachedData.allowedReactions.flatMap(Set.init)
} else {
return nil
}
}
|> deliverOnMainQueue).start(next: { initialAllowedReactions in
let updatedAllowedReactions = stateValue.with({ $0 }).updatedAllowedReactions
if let updatedAllowedReactions = updatedAllowedReactions, initialAllowedReactions != updatedAllowedReactions {
let _ = context.engine.peers.updatePeerAllowedReactions(peerId: peerId, allowedReactions: Array(updatedAllowedReactions)).start()
}
})
}
dismissImpl = { [weak controller] in
guard let controller = controller else {
return
}
controller.dismiss()
}
return controller
}