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 WebPBinding import ReactionImageComponent private final class QuickReactionSetupControllerArguments { let context: AccountContext let selectItem: (String) -> Void init( context: AccountContext, selectItem: @escaping (String) -> Void ) { self.context = context self.selectItem = selectItem } } private enum QuickReactionSetupControllerSection: Int32 { case demo case items } private enum QuickReactionSetupControllerEntry: ItemListNodeEntry { enum StableId: Hashable { case demoHeader case demoMessage case demoDescription case itemsHeader case item(String) } case demoHeader(String) case demoMessage(wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, availableReactions: AvailableReactions?, reaction: String?) case demoDescription(String) case itemsHeader(String) case item(index: Int, value: String, image: UIImage?, text: String, isSelected: Bool) var section: ItemListSectionId { switch self { case .demoHeader, .demoMessage, .demoDescription: return QuickReactionSetupControllerSection.demo.rawValue case .itemsHeader, .item: return QuickReactionSetupControllerSection.items.rawValue } } var stableId: StableId { switch self { case .demoHeader: return .demoHeader case .demoMessage: return .demoMessage case .demoDescription: return .demoDescription case .itemsHeader: return .itemsHeader case let .item(_, value, _, _, _): return .item(value) } } var sortId: Int { switch self { case .demoHeader: return 0 case .demoMessage: return 1 case .demoDescription: return 2 case .itemsHeader: return 3 case let .item(index, _, _, _, _): return 100 + index } } static func ==(lhs: QuickReactionSetupControllerEntry, rhs: QuickReactionSetupControllerEntry) -> Bool { switch lhs { case let .demoHeader(text): if case .demoHeader(text) = rhs { return true } else { return false } case let .demoMessage(lhsWallpaper, lhsFontSize, lhsBubbleCorners, lhsDateTimeFormat, lhsNameDisplayOrder, lhsAvailableReactions, lhsReaction): if case let .demoMessage(rhsWallpaper, rhsFontSize, rhsBubbleCorners, rhsDateTimeFormat, rhsNameDisplayOrder, rhsAvailableReactions, rhsReaction) = rhs, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsBubbleCorners == rhsBubbleCorners, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameDisplayOrder == rhsNameDisplayOrder, lhsAvailableReactions == rhsAvailableReactions, lhsReaction == rhsReaction { return true } else { return false } case let .demoDescription(text): if case .demoDescription(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: QuickReactionSetupControllerEntry, rhs: QuickReactionSetupControllerEntry) -> Bool { return lhs.sortId < rhs.sortId } func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! QuickReactionSetupControllerArguments switch self { case let .demoHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .demoMessage(wallpaper, fontSize, chatBubbleCorners, dateTimeFormat, nameDisplayOrder, availableReactions, reaction): return ReactionChatPreviewItem( context: arguments.context, theme: presentationData.theme, strings: presentationData.strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, availableReactions: availableReactions, reaction: reaction ) case let .demoDescription(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, image, text, isSelected): return ItemListCheckboxItem( presentationData: presentationData, icon: image, iconSize: image?.size.aspectFitted(CGSize(width: 30.0, height: 30.0)), title: text, style: .right, color: .accent, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.selectItem(value) } ) } } } private struct QuickReactionSetupControllerState: Equatable { } private func quickReactionSetupControllerEntries( presentationData: PresentationData, availableReactions: AvailableReactions?, images: [String: UIImage], reactionSettings: ReactionSettings ) -> [QuickReactionSetupControllerEntry] { var entries: [QuickReactionSetupControllerEntry] = [] if let availableReactions = availableReactions { entries.append(.demoHeader(presentationData.strings.Settings_QuickReactionSetup_DemoHeader)) entries.append(.demoMessage( wallpaper: presentationData.chatWallpaper, fontSize: presentationData.chatFontSize, bubbleCorners: presentationData.chatBubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, availableReactions: availableReactions, reaction: reactionSettings.quickReaction )) entries.append(.demoDescription(presentationData.strings.Settings_QuickReactionSetup_DemoInfo)) entries.append(.itemsHeader(presentationData.strings.Settings_QuickReactionSetup_ReactionListHeader)) var index = 0 for availableReaction in availableReactions.reactions { if !availableReaction.isEnabled { continue } entries.append(.item( index: index, value: availableReaction.value, image: images[availableReaction.value], text: availableReaction.title, isSelected: reactionSettings.quickReaction == availableReaction.value )) index += 1 } } return entries } public func quickReactionSetupController( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil ) -> ViewController { let statePromise = ValuePromise(QuickReactionSetupControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: QuickReactionSetupControllerState()) let updateState: ((QuickReactionSetupControllerState) -> QuickReactionSetupControllerState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } var dismissImpl: (() -> Void)? let _ = dismissImpl let _ = updateState let actionsDisposable = DisposableSet() let arguments = QuickReactionSetupControllerArguments( context: context, selectItem: { reaction in let _ = context.engine.stickers.updateQuickReaction(reaction: reaction).start() } ) let settings = context.account.postbox.preferencesView(keys: [PreferencesKeys.reactionSettings]) |> map { preferencesView -> ReactionSettings in let reactionSettings: ReactionSettings if let entry = preferencesView.values[PreferencesKeys.reactionSettings], let value = entry.get(ReactionSettings.self) { reactionSettings = value } else { reactionSettings = .default } return reactionSettings } let images: Signal<[String: UIImage], NoError> = context.engine.stickers.availableReactions() |> mapToSignal { availableReactions -> Signal<[String: UIImage], NoError> in var signals: [Signal<(String, UIImage?), NoError>] = [] if let availableReactions = availableReactions { for availableReaction in availableReactions.reactions { if !availableReaction.isEnabled { continue } let signal: Signal<(String, UIImage?), NoError> = context.account.postbox.mediaBox.resourceData(availableReaction.staticIcon.resource) |> distinctUntilChanged(isEqual: { lhs, rhs in return lhs.complete == rhs.complete }) |> map { data -> (String, UIImage?) in guard data.complete else { return (availableReaction.value, nil) } guard let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else { return (availableReaction.value, nil) } guard let image = WebP.convert(fromWebP: dataValue) else { return (availableReaction.value, nil) } return (availableReaction.value, image) } signals.append(signal) } } return combineLatest(queue: .mainQueue(), signals) |> map { values -> [String: UIImage] in var dict: [String: UIImage] = [:] for (key, image) in values { if let image = image { dict[key] = image } } return dict } } let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData let signal = combineLatest(queue: .mainQueue(), presentationData, statePromise.get(), context.engine.stickers.availableReactions(), settings, images ) |> deliverOnMainQueue |> map { presentationData, _, availableReactions, settings, images -> (ItemListControllerState, (ItemListNodeState, Any)) in let title: String = presentationData.strings.Settings_QuickReactionSetup_Title let entries = quickReactionSetupControllerEntries( presentationData: presentationData, availableReactions: availableReactions, images: images, reactionSettings: settings ) 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.didScrollWithOffset = { [weak controller] offset, transition, _ in guard let controller = controller else { return } controller.forEachItemNode { itemNode in if let itemNode = itemNode as? ReactionChatPreviewItemNode { itemNode.standaloneReactionAnimation?.addRelativeContentOffset(CGPoint(x: 0.0, y: offset), transition: transition) } } } dismissImpl = { [weak controller] in guard let controller = controller else { return } controller.dismiss() } return controller }