Reaction improvements

This commit is contained in:
Ali
2021-12-21 03:46:43 +04:00
parent b4382b6fc0
commit 7021252dc6
51 changed files with 1602 additions and 351 deletions

View File

@@ -0,0 +1,338 @@
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
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 {
//TODO:localize
entries.append(.demoHeader("DOUBLE TAP ON MESSAGE TO REACT"))
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("You can double tap on message for a quick reaction."))
entries.append(.itemsHeader("QUICK REACTION"))
var index = 0
for availableReaction in availableReactions.reactions {
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<PresentationData, NoError>)? = 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 _ = updateReactionSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
settings.quickReaction = reaction
return settings
}).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 {
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
//TODO:localize
let title: String = "Quick Reaction"
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)
dismissImpl = { [weak controller] in
guard let controller = controller else {
return
}
controller.dismiss()
}
return controller
}