mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
554 lines
22 KiB
Swift
554 lines
22 KiB
Swift
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 enum PeerReactionsMode {
|
|
case all
|
|
case some
|
|
case empty
|
|
}
|
|
|
|
private final class PeerAllowedReactionListControllerArguments {
|
|
let context: AccountContext
|
|
let setMode: (PeerReactionsMode, Bool) -> Void
|
|
let toggleItem: (MessageReaction.Reaction) -> Void
|
|
|
|
init(
|
|
context: AccountContext,
|
|
setMode: @escaping (PeerReactionsMode, Bool) -> Void,
|
|
toggleItem: @escaping (MessageReaction.Reaction) -> Void
|
|
) {
|
|
self.context = context
|
|
self.setMode = setMode
|
|
self.toggleItem = toggleItem
|
|
}
|
|
}
|
|
|
|
private enum PeerAllowedReactionListControllerSection: Int32 {
|
|
case all
|
|
case items
|
|
}
|
|
|
|
private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry {
|
|
enum StableId: Hashable {
|
|
case allowSwitch
|
|
case allowAllHeader
|
|
case allowAll
|
|
case allowSome
|
|
case allowNone
|
|
case allowAllInfo
|
|
case itemsHeader
|
|
case item(MessageReaction.Reaction)
|
|
}
|
|
|
|
case allowSwitch(text: String, value: Bool)
|
|
case allowAllHeader(String)
|
|
case allowAll(text: String, isEnabled: Bool)
|
|
case allowSome(text: String, isEnabled: Bool)
|
|
case allowNone(text: String, isEnabled: Bool)
|
|
case allowAllInfo(String)
|
|
|
|
case itemsHeader(String)
|
|
case item(index: Int, value: MessageReaction.Reaction, availableReactions: AvailableReactions?, reaction: MessageReaction.Reaction, text: String, isEnabled: Bool, allDisabled: Bool)
|
|
|
|
var section: ItemListSectionId {
|
|
switch self {
|
|
case .allowSwitch, .allowAllHeader, .allowAll, .allowSome, .allowNone, .allowAllInfo:
|
|
return PeerAllowedReactionListControllerSection.all.rawValue
|
|
case .itemsHeader, .item:
|
|
return PeerAllowedReactionListControllerSection.items.rawValue
|
|
}
|
|
}
|
|
|
|
var stableId: StableId {
|
|
switch self {
|
|
case .allowSwitch:
|
|
return .allowSwitch
|
|
case .allowAllHeader:
|
|
return .allowAllHeader
|
|
case .allowAll:
|
|
return .allowAll
|
|
case .allowSome:
|
|
return .allowSome
|
|
case .allowNone:
|
|
return .allowNone
|
|
case .allowAllInfo:
|
|
return .allowAllInfo
|
|
case .itemsHeader:
|
|
return .itemsHeader
|
|
case let .item(_, value, _, _, _, _, _):
|
|
return .item(value)
|
|
}
|
|
}
|
|
|
|
var sortId: Int {
|
|
switch self {
|
|
case .allowSwitch:
|
|
return 0
|
|
case .allowAllHeader:
|
|
return 1
|
|
case .allowAll:
|
|
return 2
|
|
case .allowSome:
|
|
return 3
|
|
case .allowNone:
|
|
return 4
|
|
case .allowAllInfo:
|
|
return 5
|
|
case .itemsHeader:
|
|
return 6
|
|
case let .item(index, _, _, _, _, _, _):
|
|
return 100 + index
|
|
}
|
|
}
|
|
|
|
static func ==(lhs: PeerAllowedReactionListControllerEntry, rhs: PeerAllowedReactionListControllerEntry) -> Bool {
|
|
switch lhs {
|
|
case let .allowSwitch(text, value):
|
|
if case .allowSwitch(text, value) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .allowAllHeader(text):
|
|
if case .allowAllHeader(text) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .allowAll(text, isEnabled):
|
|
if case .allowAll(text, isEnabled) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .allowSome(text, isEnabled):
|
|
if case .allowSome(text, isEnabled) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .allowNone(text, isEnabled):
|
|
if case .allowNone(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, availableReactions, reaction, text, isEnabled, allDisabled):
|
|
if case .item(index, value, availableReactions, reaction, text, isEnabled, allDisabled) = 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 .allowSwitch(text, value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
if value {
|
|
arguments.setMode(.some, false)
|
|
} else {
|
|
arguments.setMode(.empty, false)
|
|
}
|
|
})
|
|
case let .allowAllHeader(text):
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
|
case let .allowAll(text, isEnabled):
|
|
return ItemListCheckboxItem(
|
|
presentationData: presentationData,
|
|
icon: nil,
|
|
iconSize: nil,
|
|
iconPlacement: .default,
|
|
title: text,
|
|
subtitle: nil,
|
|
style: .right,
|
|
color: .accent,
|
|
textColor: .primary,
|
|
checked: isEnabled,
|
|
zeroSeparatorInsets: false,
|
|
sectionId: self.section,
|
|
action: {
|
|
arguments.setMode(.all, true)
|
|
},
|
|
deleteAction: nil
|
|
)
|
|
case let .allowSome(text, isEnabled):
|
|
return ItemListCheckboxItem(
|
|
presentationData: presentationData,
|
|
icon: nil,
|
|
iconSize: nil,
|
|
iconPlacement: .default,
|
|
title: text,
|
|
subtitle: nil,
|
|
style: .right,
|
|
color: .accent,
|
|
textColor: .primary,
|
|
checked: isEnabled,
|
|
zeroSeparatorInsets: false,
|
|
sectionId: self.section,
|
|
action: {
|
|
arguments.setMode(.some, true)
|
|
},
|
|
deleteAction: nil
|
|
)
|
|
case let .allowNone(text, isEnabled):
|
|
return ItemListCheckboxItem(
|
|
presentationData: presentationData,
|
|
icon: nil,
|
|
iconSize: nil,
|
|
iconPlacement: .default,
|
|
title: text,
|
|
subtitle: nil,
|
|
style: .right,
|
|
color: .accent,
|
|
textColor: .primary,
|
|
checked: isEnabled,
|
|
zeroSeparatorInsets: false,
|
|
sectionId: self.section,
|
|
action: {
|
|
arguments.setMode(.empty, true)
|
|
},
|
|
deleteAction: nil
|
|
)
|
|
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, availableReactions, reaction, text, isEnabled, allDisabled):
|
|
return ItemListReactionItem(
|
|
context: arguments.context,
|
|
presentationData: presentationData,
|
|
availableReactions: availableReactions,
|
|
reaction: reaction,
|
|
title: text,
|
|
value: isEnabled,
|
|
enabled: !allDisabled,
|
|
sectionId: self.section,
|
|
style: .blocks,
|
|
updated: { _ in
|
|
arguments.toggleItem(value)
|
|
}
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct PeerAllowedReactionListControllerState: Equatable {
|
|
var updatedMode: PeerReactionsMode?
|
|
var updatedAllowedReactions: Set<MessageReaction.Reaction>? = nil
|
|
}
|
|
|
|
private func peerAllowedReactionListControllerEntries(
|
|
presentationData: PresentationData,
|
|
availableReactions: AvailableReactions?,
|
|
peer: Peer?,
|
|
cachedData: CachedPeerData?,
|
|
state: PeerAllowedReactionListControllerState
|
|
) -> [PeerAllowedReactionListControllerEntry] {
|
|
var entries: [PeerAllowedReactionListControllerEntry] = []
|
|
|
|
if let peer = peer, let availableReactions = availableReactions, let allowedReactions = state.updatedAllowedReactions, let mode = state.updatedMode {
|
|
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
|
entries.append(.allowSwitch(text: presentationData.strings.PeerInfo_AllowedReactions_AllowAllText, value: mode != .empty))
|
|
|
|
entries.append(.itemsHeader(presentationData.strings.PeerInfo_AllowedReactions_ReactionListHeader))
|
|
var index = 0
|
|
for availableReaction in availableReactions.reactions {
|
|
if !availableReaction.isEnabled {
|
|
continue
|
|
}
|
|
entries.append(.item(index: index, value: availableReaction.value, availableReactions: availableReactions, reaction: availableReaction.value, text: availableReaction.title, isEnabled: allowedReactions.contains(availableReaction.value), allDisabled: mode == .empty))
|
|
index += 1
|
|
}
|
|
} else {
|
|
entries.append(.allowAllHeader(presentationData.strings.PeerInfo_AllowedReactions_ReactionListHeader))
|
|
|
|
entries.append(.allowAll(text: presentationData.strings.PeerInfo_AllowedReactions_OptionAllReactions, isEnabled: mode == .all))
|
|
entries.append(.allowSome(text: presentationData.strings.PeerInfo_AllowedReactions_OptionSomeReactions, isEnabled: mode == .some))
|
|
entries.append(.allowNone(text: presentationData.strings.PeerInfo_AllowedReactions_OptionNoReactions, isEnabled: mode == .empty))
|
|
|
|
let allInfoText: String
|
|
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
|
switch mode {
|
|
case .all:
|
|
allInfoText = presentationData.strings.PeerInfo_AllowedReactions_GroupOptionAllInfo
|
|
case .some:
|
|
allInfoText = presentationData.strings.PeerInfo_AllowedReactions_GroupOptionSomeInfo
|
|
case .empty:
|
|
allInfoText = presentationData.strings.PeerInfo_AllowedReactions_GroupOptionNoInfo
|
|
}
|
|
} else {
|
|
switch mode {
|
|
case .all:
|
|
allInfoText = presentationData.strings.PeerInfo_AllowedReactions_GroupOptionAllInfo
|
|
case .some:
|
|
allInfoText = presentationData.strings.PeerInfo_AllowedReactions_GroupOptionSomeInfo
|
|
case .empty:
|
|
allInfoText = presentationData.strings.PeerInfo_AllowedReactions_GroupOptionNoInfo
|
|
}
|
|
}
|
|
|
|
entries.append(.allowAllInfo(allInfoText))
|
|
|
|
if mode == .some {
|
|
entries.append(.itemsHeader(presentationData.strings.PeerInfo_AllowedReactions_ReactionListHeader))
|
|
var index = 0
|
|
for availableReaction in availableReactions.reactions {
|
|
if !availableReaction.isEnabled {
|
|
continue
|
|
}
|
|
entries.append(.item(index: index, value: availableReaction.value, availableReactions: availableReactions, reaction: availableReaction.value, text: availableReaction.title, isEnabled: allowedReactions.contains(availableReaction.value), allDisabled: false))
|
|
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((combineLatest(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.AllowedReactions(id: peerId)), context.engine.stickers.availableReactions() |> take(1))
|
|
|> deliverOnMainQueue).start(next: { allowedReactions, availableReactions in
|
|
updateState { state in
|
|
var state = state
|
|
|
|
switch allowedReactions {
|
|
case .unknown:
|
|
break
|
|
case let .known(value):
|
|
switch value {
|
|
case .all:
|
|
state.updatedMode = .all
|
|
if let availableReactions = availableReactions {
|
|
state.updatedAllowedReactions = Set(availableReactions.reactions.filter(\.isEnabled).map(\.value))
|
|
} else {
|
|
state.updatedAllowedReactions = Set()
|
|
}
|
|
case let .limited(reactions):
|
|
state.updatedMode = .some
|
|
state.updatedAllowedReactions = Set(reactions)
|
|
case .empty:
|
|
state.updatedMode = .empty
|
|
state.updatedAllowedReactions = Set()
|
|
}
|
|
}
|
|
|
|
return state
|
|
}
|
|
}))
|
|
|
|
let arguments = PeerAllowedReactionListControllerArguments(
|
|
context: context,
|
|
setMode: { mode, resetItems in
|
|
let _ = (context.engine.stickers.availableReactions()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { availableReactions in
|
|
guard let availableReactions = availableReactions else {
|
|
return
|
|
}
|
|
updateState { state in
|
|
var state = state
|
|
state.updatedMode = mode
|
|
|
|
if var updatedAllowedReactions = state.updatedAllowedReactions {
|
|
switch mode {
|
|
case .all:
|
|
if resetItems {
|
|
updatedAllowedReactions.removeAll()
|
|
for availableReaction in availableReactions.reactions {
|
|
if !availableReaction.isEnabled {
|
|
continue
|
|
}
|
|
updatedAllowedReactions.insert(availableReaction.value)
|
|
}
|
|
}
|
|
case .some:
|
|
if resetItems {
|
|
updatedAllowedReactions.removeAll()
|
|
if let thumbsUp = availableReactions.reactions.first(where: { $0.value == .builtin("👍") }) {
|
|
updatedAllowedReactions.insert(thumbsUp.value)
|
|
}
|
|
if let thumbsDown = availableReactions.reactions.first(where: { $0.value == .builtin("👎") }) {
|
|
updatedAllowedReactions.insert(thumbsDown.value)
|
|
}
|
|
} else {
|
|
updatedAllowedReactions.removeAll()
|
|
for availableReaction in availableReactions.reactions {
|
|
if !availableReaction.isEnabled {
|
|
continue
|
|
}
|
|
updatedAllowedReactions.insert(availableReaction.value)
|
|
}
|
|
}
|
|
case .empty:
|
|
if resetItems {
|
|
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)
|
|
if state.updatedMode == .all {
|
|
state.updatedMode = .some
|
|
}
|
|
} 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
|
|
let title: String = presentationData.strings.PeerInfo_AllowedReactions_Title
|
|
|
|
let entries = peerAllowedReactionListControllerEntries(
|
|
presentationData: presentationData,
|
|
availableReactions: availableReactions,
|
|
peer: peerView.peers[peerId],
|
|
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: false
|
|
)
|
|
|
|
return (controllerState, (listState, arguments))
|
|
}
|
|
|> afterDisposed {
|
|
actionsDisposable.dispose()
|
|
}
|
|
|
|
let controller = ItemListController(context: context, state: signal)
|
|
controller.willDisappear = { _ in
|
|
let _ = (combineLatest(
|
|
context.engine.data.get(
|
|
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
|
|
TelegramEngine.EngineData.Item.Peer.AllowedReactions(id: peerId)
|
|
),
|
|
context.engine.stickers.availableReactions() |> take(1)
|
|
)
|
|
|> deliverOnMainQueue).start(next: { data, availableReactions in
|
|
let (peer, initialAllowedReactions) = data
|
|
|
|
guard let peer = peer, let availableReactions = availableReactions else {
|
|
return
|
|
}
|
|
|
|
let state = stateValue.with({ $0 })
|
|
guard let updatedMode = state.updatedMode, let updatedAllowedReactions = state.updatedAllowedReactions else {
|
|
return
|
|
}
|
|
|
|
let updatedValue: PeerAllowedReactions
|
|
switch updatedMode {
|
|
case .all:
|
|
updatedValue = .all
|
|
case .some:
|
|
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
|
if updatedAllowedReactions == Set(availableReactions.reactions.filter(\.isEnabled).map(\.value)) {
|
|
updatedValue = .all
|
|
} else {
|
|
updatedValue = .limited(Array(updatedAllowedReactions))
|
|
}
|
|
} else {
|
|
updatedValue = .limited(Array(updatedAllowedReactions))
|
|
}
|
|
case .empty:
|
|
updatedValue = .empty
|
|
}
|
|
|
|
if initialAllowedReactions != .known(updatedValue) {
|
|
let _ = context.engine.peers.updatePeerReactionSettings(peerId: peerId, reactionSettings: PeerReactionSettings(allowedReactions: updatedValue, maxReactionCount: 11, starsAllowed: nil)).start()
|
|
}
|
|
})
|
|
}
|
|
dismissImpl = { [weak controller] in
|
|
guard let controller = controller else {
|
|
return
|
|
}
|
|
controller.dismiss()
|
|
}
|
|
|
|
return controller
|
|
}
|