Stickers Import Improvements

This commit is contained in:
Ilya Laktyushin 2021-06-14 23:23:11 +03:00
parent 4136c049f6
commit 1c1e44833e
8 changed files with 648 additions and 170 deletions

View File

@ -274,6 +274,9 @@ final class PeekControllerNode: ViewControllerTracingNode {
}
func activateMenu() {
if self.content.menuItems().isEmpty {
return
}
if case .press = self.content.menuActivation() {
self.hapticFeedback.impact()
}

View File

@ -3,44 +3,81 @@ import UIKit
public class ImportStickerPack {
public class Sticker: Equatable {
public enum Content {
case image(Data)
case animation(Data)
}
public static func == (lhs: ImportStickerPack.Sticker, rhs: ImportStickerPack.Sticker) -> Bool {
return lhs.uuid == rhs.uuid
}
let image: UIImage
let content: Content
let emojis: [String]
let uuid: UUID
init(image: UIImage, emojis: [String], uuid: UUID = UUID()) {
self.image = image
init(content: Content, emojis: [String], uuid: UUID = UUID()) {
self.content = content
self.emojis = emojis
self.uuid = uuid
}
var data: Data {
switch self.content {
case let .image(data):
return data
case let .animation(data):
return data
}
}
}
public var identifier: String
public var name: String
public let software: String
public var thumbnail: String?
public let isAnimated: Bool
public var stickers: [Sticker]
public let thumbnail: Sticker?
public let stickers: [Sticker]
public init?(data: Data) {
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
return nil
}
self.name = json["name"] as? String ?? ""
self.identifier = json["identifier"] as? String ?? ""
self.software = json["software"] as? String ?? ""
self.isAnimated = json["isAnimated"] as? Bool ?? false
let isAnimated = json["isAnimated"] as? Bool ?? false
self.isAnimated = isAnimated
func parseSticker(_ sticker: [String: Any]) -> Sticker? {
if let dataString = sticker["data"] as? String, let mimeType = sticker["mimeType"] as? String, let data = Data(base64Encoded: dataString) {
var content: Sticker.Content?
switch mimeType.lowercased() {
case "image/png":
if !isAnimated {
content = .image(data)
}
case "application/x-tgsticker":
if isAnimated {
content = .animation(data)
}
default:
break
}
if let content = content {
return Sticker(content: content, emojis: sticker["emojis"] as? [String] ?? [])
}
}
return nil
}
if let thumbnail = json["thumbnail"] as? [String: Any], let parsedSticker = parseSticker(thumbnail) {
self.thumbnail = parsedSticker
} else {
self.thumbnail = nil
}
var stickers: [Sticker] = []
if let stickersArray = json["stickers"] as? [[String: Any]] {
for sticker in stickersArray {
if let dataString = sticker["data"] as? String, let data = Data(base64Encoded: dataString), let image = UIImage(data: data) {
stickers.append(Sticker(image: image, emojis: sticker["emojis"] as? [String] ?? []))
if let parsedSticker = parseSticker(sticker) {
stickers.append(parsedSticker)
}
}
}

View File

@ -68,6 +68,9 @@ public final class ImportStickerPackController: ViewController, StandalonePresen
self.controllerNode.cancel = { [weak self] in
self?.dismiss()
}
self.controllerNode.present = { [weak self] controller, arguments in
self?.present(controller, in: .window(.root), with: arguments)
}
self.controllerNode.presentInGlobalOverlay = { [weak self] controller, arguments in
self?.presentInGlobalOverlay(controller, with: arguments)
}

View File

@ -68,6 +68,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
private var interaction: StickerPackPreviewInteraction!
var present: ((ViewController, Any?) -> Void)?
var presentInGlobalOverlay: ((ViewController, Any?) -> Void)?
var dismiss: (() -> Void)?
var cancel: (() -> Void)?
@ -75,10 +76,13 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
let ready = Promise<Bool>()
private var didSetReady = false
private var pendingItems: [StickerPackPreviewGridEntry] = []
private var currentItems: [StickerPackPreviewGridEntry] = []
private var hapticFeedback: HapticFeedback?
private let disposable = MetaDisposable()
init(context: AccountContext) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -151,6 +155,10 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
}
}
deinit {
self.disposable.dispose()
}
override func didLoad() {
super.didLoad()
@ -160,31 +168,23 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
self.contentGridNode.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>? in
if let strongSelf = self {
if let itemNode = strongSelf.contentGridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, let item = itemNode.stickerPackItem {
// var menuItems: [PeekControllerMenuItem] = []
// if let stickerPack = strongSelf.stickerPack, case let .result(info, _, _) = stickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
// if strongSelf.sendSticker != nil {
// menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.ShareMenu_Send, color: .accent, font: .bold, action: { node, rect in
// if let strongSelf = self {
// return strongSelf.sendSticker?(.standalone(media: item.file), node, rect) ?? false
// } else {
// return false
// }
// }))
// }
// menuItems.append(PeekControllerMenuItem(title: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
// if let strongSelf = self {
// if isStarred {
// let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
// } else {
// let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
// }
// }
// return true
// }))
// menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { _, _ in return true }))
// }
// return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
return .single((itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: [])))
var menuItems: [ContextMenuItem] = []
if strongSelf.currentItems.count > 1 {
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ImportStickerPack_RemoveFromImport, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, f in
f(.dismissWithoutContent)
if let strongSelf = self {
var updatedItems = strongSelf.currentItems
updatedItems.removeAll(where: { $0.stickerItem.uuid == item.uuid })
strongSelf.pendingItems = updatedItems
if let (layout, navigationHeight) = strongSelf.containerLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
})))
}
return .single((itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: menuItems)))
}
}
return nil
@ -304,21 +304,16 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
var itemCount = 0
var animateIn = false
if let stickerPack = self.stickerPack {
var updatedItems: [StickerPackPreviewGridEntry] = []
for item in stickerPack.stickers {
updatedItems.append(StickerPackPreviewGridEntry(index: updatedItems.count, stickerItem: item))
}
if let _ = self.stickerPack, self.currentItems.isEmpty || self.currentItems.count != self.pendingItems.count {
let previousItems = self.currentItems
self.currentItems = self.pendingItems
if self.currentItems.isEmpty && !updatedItems.isEmpty {
let entities = generateTextEntities(stickerPack.name, enabledTypes: [.mention])
let font = Font.medium(20.0)
self.contentTitleNode.attributedText = stringWithAppliedEntities(stickerPack.name, entities: entities, baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: font, linkFont: font, boldFont: font, italicFont: font, boldItalicFont: font, fixedFont: font, blockQuoteFont: font)
animateIn = true
itemCount = updatedItems.count
}
transaction = StickerPackPreviewGridTransaction(previousList: self.currentItems, list: updatedItems, account: self.context.account, interaction: self.interaction, theme: self.presentationData.theme)
self.currentItems = updatedItems
let titleFont = Font.medium(20.0)
self.contentTitleNode.attributedText = stringWithAppliedEntities(self.presentationData.strings.ImportStickerPack_StickerCount(Int32(self.currentItems.count)), entities: [], baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: titleFont, linkFont: titleFont, boldFont: titleFont, italicFont: titleFont, boldItalicFont: titleFont, fixedFont: titleFont, blockQuoteFont: titleFont)
animateIn = true
itemCount = self.currentItems.count
transaction = StickerPackPreviewGridTransaction(previousList: previousItems, list: self.currentItems, account: self.context.account, interaction: self.interaction, theme: self.presentationData.theme)
}
let titleSize = self.contentTitleNode.updateLayout(CGSize(width: contentContainerFrame.size.width - 24.0, height: CGFloat.greatestFiniteMagnitude))
@ -421,7 +416,45 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
}
@objc func installActionButtonPressed() {
let controller = importStickerPackTitleController(sharedContext: self.context.sharedContext, account: self.context.account, title: self.presentationData.strings.ImportStickerPack_ChooseName, text: self.presentationData.strings.ImportStickerPack_ChooseNameDescription, placeholder: "", doneButtonTitle: nil, value: nil, maxLength: 128, apply: { [weak self] title in
if let strongSelf = self, let stickerPack = strongSelf.stickerPack, var title = title {
title = title.trimmingTrailingSpaces()
let shortName = title.replacingOccurrences(of: " ", with: "") + "_by_laktyushin"
var stickers: [ImportSticker] = []
for item in strongSelf.currentItems {
var dimensions = PixelDimensions(width: 512, height: 512)
if case let .image(data) = item.stickerItem.content, let image = UIImage(data: data) {
dimensions = PixelDimensions(image.size)
}
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: item.stickerItem.data)
stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions))
}
var thumbnailSticker: ImportSticker?
if let thumbnail = stickerPack.thumbnail {
var dimensions = PixelDimensions(width: 512, height: 512)
if case let .image(data) = thumbnail.content, let image = UIImage(data: data) {
dimensions = PixelDimensions(image.size)
}
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnail.data)
thumbnailSticker = ImportSticker(resource: resource, emojis: [], dimensions: dimensions)
}
strongSelf.disposable.set(createStickerSet(account: strongSelf.context.account, title: title, shortName: shortName, stickers: stickers, thumbnail: thumbnailSticker, isAnimated: stickerPack.isAnimated).start(next: { [weak self] status in
if let strongSelf = self {
if case let .complete(pack, items) = status {
print("done!")
}
}
}, error: { error in
if let strongSelf = self {
}
}))
}
})
self.present?(controller, nil)
}
func animateIn() {
@ -461,13 +494,18 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
func updateStickerPack(_ stickerPack: ImportStickerPack) {
self.stickerPack = stickerPack
var updatedItems: [StickerPackPreviewGridEntry] = []
for item in stickerPack.stickers {
updatedItems.append(StickerPackPreviewGridEntry(index: updatedItems.count, stickerItem: item))
}
self.pendingItems = updatedItems
// self.interaction.playAnimatedStickers = stickerSettings.loopAnimatedStickers
if let _ = self.containerLayout {
self.dequeueUpdateStickerPack()
}
self.installActionButtonNode.setTitle("Create Sticker Set", with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
self.installActionButtonNode.setTitle(self.presentationData.strings.ImportStickerPack_CreateStickerSet, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
// switch stickerPack {
// case .none, .fetching:
// self.installActionSeparatorNode.alpha = 0.0

View File

@ -0,0 +1,468 @@
import Foundation
import UIKit
import SwiftSignalKit
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
import SyncCore
import TelegramPresentationData
import AccountContext
import UrlEscaping
private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate {
private var theme: PresentationTheme
private let backgroundNode: ASImageNode
private let textInputNode: EditableTextNode
private let placeholderNode: ASTextNode
private let clearButton: HighlightableButtonNode
var updateHeight: (() -> Void)?
var complete: (() -> Void)?
var textChanged: ((String) -> Void)?
private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0)
private let inputInsets = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0)
var text: String {
get {
return self.textInputNode.attributedText?.string ?? ""
}
set {
self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputTextColor)
self.placeholderNode.isHidden = !newValue.isEmpty
if self.textInputNode.isFirstResponder() {
self.clearButton.isHidden = newValue.isEmpty
} else {
self.clearButton.isHidden = true
}
}
}
var placeholder: String = "" {
didSet {
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
}
}
private let maxLength: Int
init(theme: PresentationTheme, placeholder: String, maxLength: Int, returnKeyType: UIReturnKeyType = .done) {
self.theme = theme
self.maxLength = maxLength
self.backgroundNode = ASImageNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
self.textInputNode = EditableTextNode()
self.textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: theme.actionSheet.inputTextColor]
self.textInputNode.clipsToBounds = true
self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: 0.0, bottom: self.inputInsets.bottom, right: 0.0)
self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
self.textInputNode.keyboardType = .default
self.textInputNode.autocapitalizationType = .sentences
self.textInputNode.returnKeyType = returnKeyType
self.textInputNode.autocorrectionType = .default
self.textInputNode.tintColor = theme.actionSheet.controlAccentColor
self.placeholderNode = ASTextNode()
self.placeholderNode.isUserInteractionEnabled = false
self.placeholderNode.displaysAsynchronously = false
self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
self.clearButton = HighlightableButtonNode()
self.clearButton.imageNode.displaysAsynchronously = false
self.clearButton.imageNode.displayWithoutProcessing = true
self.clearButton.displaysAsynchronously = false
self.clearButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.actionSheet.inputClearButtonColor), for: [])
self.clearButton.isHidden = true
super.init()
self.textInputNode.delegate = self
self.addSubnode(self.backgroundNode)
self.addSubnode(self.textInputNode)
self.addSubnode(self.placeholderNode)
self.addSubnode(self.clearButton)
self.clearButton.addTarget(self, action: #selector(self.clearPressed), forControlEvents: .touchUpInside)
}
func updateTheme(_ theme: PresentationTheme) {
self.theme = theme
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor
self.clearButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.actionSheet.inputClearButtonColor), for: [])
}
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let backgroundInsets = self.backgroundInsets
let inputInsets = self.inputInsets
let textFieldHeight = self.calculateTextFieldMetrics(width: width)
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom))
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
let placeholderSize = self.placeholderNode.measure(backgroundFrame.size)
transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize))
transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right - 20.0, height: backgroundFrame.size.height)))
if let image = self.clearButton.image(for: []) {
transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX - 8.0 - image.size.width, y: backgroundFrame.minY + floor((backgroundFrame.size.height - image.size.height) / 2.0)), size: image.size))
}
return panelHeight
}
func activateInput() {
self.textInputNode.becomeFirstResponder()
}
func deactivateInput() {
self.textInputNode.resignFirstResponder()
}
@objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
self.updateTextNodeText(animated: true)
self.textChanged?(editableTextNode.textView.text)
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty
self.clearButton.isHidden = !self.placeholderNode.isHidden
}
func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
self.clearButton.isHidden = (editableTextNode.textView.text ?? "").isEmpty
}
func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
self.clearButton.isHidden = true
}
func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
let updatedText = (editableTextNode.textView.text as NSString).replacingCharacters(in: range, with: text)
if updatedText.count > maxLength {
self.textInputNode.layer.addShakeAnimation()
return false
}
if text == "\n" {
self.complete?()
return false
}
return true
}
private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat {
let backgroundInsets = self.backgroundInsets
let inputInsets = self.inputInsets
let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right - 20.0, height: CGFloat.greatestFiniteMagnitude)).height))
return min(61.0, max(33.0, unboundTextFieldHeight))
}
private func updateTextNodeText(animated: Bool) {
let backgroundInsets = self.backgroundInsets
let textFieldHeight = self.calculateTextFieldMetrics(width: self.bounds.size.width)
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
if !self.bounds.size.height.isEqual(to: panelHeight) {
self.updateHeight?()
}
}
@objc func clearPressed() {
self.placeholderNode.isHidden = false
self.clearButton.isHidden = true
self.textInputNode.attributedText = nil
self.updateHeight?()
}
}
private final class ImportStickerPackTitleAlertContentNode: AlertContentNode {
private let strings: PresentationStrings
private let title: String
private let text: String
private let titleNode: ASTextNode
private let textNode: ASTextNode
let inputFieldNode: ImportStickerPackTitleInputFieldNode
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [TextAlertContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode]
private let disposable = MetaDisposable()
private var validLayout: CGSize?
private let hapticFeedback = HapticFeedback()
var complete: (() -> Void)? {
didSet {
self.inputFieldNode.complete = self.complete
}
}
override var dismissOnOutsideTap: Bool {
return self.isUserInteractionEnabled
}
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String, placeholder: String, value: String?, maxLength: Int) {
self.strings = strings
self.title = title
self.text = text
self.titleNode = ASTextNode()
self.titleNode.maximumNumberOfLines = 2
self.textNode = ASTextNode()
self.textNode.maximumNumberOfLines = 8
self.inputFieldNode = ImportStickerPackTitleInputFieldNode(theme: ptheme, placeholder: placeholder, maxLength: maxLength)
self.inputFieldNode.text = value ?? ""
self.actionNodesSeparator = ASDisplayNode()
self.actionNodesSeparator.isLayerBacked = true
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
return TextAlertContentActionNode(theme: theme, action: action)
}
var actionVerticalSeparators: [ASDisplayNode] = []
if actions.count > 1 {
for _ in 0 ..< actions.count - 1 {
let separatorNode = ASDisplayNode()
separatorNode.isLayerBacked = true
actionVerticalSeparators.append(separatorNode)
}
}
self.actionVerticalSeparators = actionVerticalSeparators
super.init()
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
self.addSubnode(self.inputFieldNode)
self.addSubnode(self.actionNodesSeparator)
for actionNode in self.actionNodes {
self.addSubnode(actionNode)
}
for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode)
}
self.inputFieldNode.updateHeight = { [weak self] in
if let strongSelf = self {
if let _ = strongSelf.validLayout {
strongSelf.requestLayout?(.animated(duration: 0.15, curve: .spring))
}
}
}
self.updateTheme(theme)
}
deinit {
self.disposable.dispose()
}
var value: String {
return self.inputFieldNode.text
}
override func updateTheme(_ theme: AlertControllerTheme) {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
self.actionNodesSeparator.backgroundColor = theme.separatorColor
for actionNode in self.actionNodes {
actionNode.updateTheme(theme)
}
for separatorNode in self.actionVerticalSeparators {
separatorNode.backgroundColor = theme.separatorColor
}
if let size = self.validLayout {
_ = self.updateLayout(size: size, transition: .immediate)
}
}
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
var size = size
size.width = min(size.width, 270.0)
let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)
let hadValidLayout = self.validLayout != nil
self.validLayout = size
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
let spacing: CGFloat = 5.0
let titleSize = self.titleNode.measure(measureSize)
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
origin.y += titleSize.height + 4.0
let textSize = self.textNode.measure(measureSize)
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
origin.y += textSize.height + 6.0 + spacing
let actionButtonHeight: CGFloat = 44.0
var minActionsWidth: CGFloat = 0.0
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
let actionTitleInsets: CGFloat = 8.0
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
for actionNode in self.actionNodes {
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
effectiveActionLayout = .vertical
}
switch effectiveActionLayout {
case .horizontal:
minActionsWidth += actionTitleSize.width + actionTitleInsets
case .vertical:
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
}
}
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0)
var contentWidth = max(titleSize.width, minActionsWidth)
contentWidth = max(contentWidth, 234.0)
var actionsHeight: CGFloat = 0.0
switch effectiveActionLayout {
case .horizontal:
actionsHeight = actionButtonHeight
case .vertical:
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
}
let resultWidth = contentWidth + insets.left + insets.right
let inputFieldWidth = resultWidth
let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition)
let inputHeight = inputFieldHeight
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight))
transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0)
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + inputHeight + actionsHeight + insets.top + insets.bottom)
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
var actionOffset: CGFloat = 0.0
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
var separatorIndex = -1
var nodeIndex = 0
for actionNode in self.actionNodes {
if separatorIndex >= 0 {
let separatorNode = self.actionVerticalSeparators[separatorIndex]
switch effectiveActionLayout {
case .horizontal:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
case .vertical:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
}
}
separatorIndex += 1
let currentActionWidth: CGFloat
switch effectiveActionLayout {
case .horizontal:
if nodeIndex == self.actionNodes.count - 1 {
currentActionWidth = resultSize.width - actionOffset
} else {
currentActionWidth = actionWidth
}
case .vertical:
currentActionWidth = resultSize.width
}
let actionNodeFrame: CGRect
switch effectiveActionLayout {
case .horizontal:
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += currentActionWidth
case .vertical:
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += actionButtonHeight
}
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
nodeIndex += 1
}
if !hadValidLayout {
self.inputFieldNode.activateInput()
}
return resultSize
}
func animateError() {
self.inputFieldNode.layer.addShakeAnimation()
self.hapticFeedback.error()
}
}
func importStickerPackTitleController(sharedContext: SharedAccountContext, account: Account, title: String, text: String, placeholder: String, doneButtonTitle: String? = nil, value: String?, maxLength: Int, apply: @escaping (String?) -> Void) -> AlertController {
let presentationData = sharedContext.currentPresentationData.with { $0 }
var dismissImpl: ((Bool) -> Void)?
var applyImpl: (() -> Void)?
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?(true)
}), TextAlertAction(type: .defaultAction, title: doneButtonTitle ?? presentationData.strings.Common_Done, action: {
applyImpl?()
})]
let contentNode = ImportStickerPackTitleAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value, maxLength: maxLength)
contentNode.complete = {
applyImpl?()
}
applyImpl = { [weak contentNode] in
guard let contentNode = contentNode else {
return
}
dismissImpl?(true)
let previousValue = value ?? ""
let newValue = contentNode.value.trimmingCharacters(in: .whitespacesAndNewlines)
apply(previousValue != newValue || value == nil ? newValue : nil)
}
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
let presentationDataDisposable = sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
controller?.theme = AlertControllerTheme(presentationData: presentationData)
contentNode?.inputFieldNode.updateTheme(presentationData.theme)
})
controller.dismissed = {
presentationDataDisposable.dispose()
}
dismissImpl = { [weak controller, weak contentNode] animated in
contentNode?.inputFieldNode.deactivateInput()
if animated {
controller?.dismissAnimated()
} else {
controller?.dismiss()
}
}
return controller
}

View File

@ -57,12 +57,10 @@ final class StickerPackPreviewGridItem: GridItem {
private let textFont = Font.regular(20.0)
final class StickerPackPreviewGridItemNode: GridItemNode {
private var currentState: (Account, ImportStickerPack.Sticker?)?
private var currentState: (Account, ImportStickerPack.Sticker?, CGSize)?
private var isEmpty: Bool?
// private let imageNode: TransformImageNode
private let imageNode: ASImageNode
private var animationNode: AnimatedStickerNode?
private var placeholderNode: StickerShimmerEffectNode?
private var theme: PresentationTheme?
@ -86,50 +84,16 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
override init() {
self.imageNode = ASImageNode()
// self.imageNode = TransformImageNode()
// self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
self.placeholderNode = StickerShimmerEffectNode()
self.placeholderNode?.isUserInteractionEnabled = false
super.init()
self.addSubnode(self.imageNode)
if let placeholderNode = self.placeholderNode {
// self.addSubnode(placeholderNode)
}
var firstTime = true
// self.imageNode.imageUpdated = { [weak self] image in
// guard let strongSelf = self else {
// return
// }
// if image != nil {
// strongSelf.removePlaceholder(animated: !firstTime)
// }
// firstTime = false
// }
}
deinit {
self.stickerFetchedDisposable.dispose()
}
private func removePlaceholder(animated: Bool) {
if let placeholderNode = self.placeholderNode {
self.placeholderNode = nil
if !animated {
placeholderNode.removeFromSupernode()
} else {
placeholderNode.allowsGroupOpacity = true
placeholderNode.alpha = 0.0
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in
placeholderNode?.removeFromSupernode()
placeholderNode?.allowsGroupOpacity = false
})
}
}
}
override func didLoad() {
super.didLoad()
@ -141,47 +105,34 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
self.theme = theme
if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 !== stickerItem || self.isEmpty != isEmpty {
var dimensions = CGSize(width: 512.0, height: 512.0)
if let stickerItem = stickerItem {
self.imageNode.image = stickerItem.image
// if stickerItem.file.isAnimatedSticker {
// let dimensions = stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512)
// self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: account.postbox, file: stickerItem.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))))
//
// if self.animationNode == nil {
// let animationNode = AnimatedStickerNode()
// self.animationNode = animationNode
// self.addSubnode(animationNode)
// animationNode.started = { [weak self] in
// self?.imageNode.isHidden = true
// self?.removePlaceholder(animated: false)
// }
// }
// let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))
// self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached)
// self.animationNode?.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true
// self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: stickerItem.file.resource).start())
// } else {
// if let animationNode = self.animationNode {
// animationNode.visibility = false
// self.animationNode = nil
// animationNode.removeFromSupernode()
// }
// self.imageNode.setSignal(chatMessageSticker(account: account, file: stickerItem.file, small: true))
// self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: chatMessageStickerResource(file: stickerItem.file, small: true)).start())
// }
} else {
if let placeholderNode = self.placeholderNode {
if isEmpty {
if !placeholderNode.alpha.isZero {
placeholderNode.alpha = 0.0
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
switch stickerItem.content {
case let .image(data):
if let animationNode = self.animationNode {
animationNode.visibility = false
self.animationNode = nil
animationNode.removeFromSupernode()
}
} else {
placeholderNode.alpha = 1.0
}
self.imageNode.isHidden = false
if let image = UIImage(data: data) {
self.imageNode.image = image
dimensions = image.size
}
case let .animation(data):
self.imageNode.isHidden = true
let animationNode = AnimatedStickerNode()
self.animationNode = animationNode
self.addSubnode(animationNode)
let fittedDimensions = dimensions.aspectFitted(CGSize(width: 160.0, height: 160.0))
// animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached)
animationNode.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true
}
} else {
dimensions = CGSize()
}
self.currentState = (account, stickerItem)
self.currentState = (account, stickerItem, dimensions)
self.setNeedsLayout()
}
self.isEmpty = isEmpty
@ -194,32 +145,13 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
let boundsSide = min(bounds.size.width - 14.0, bounds.size.height - 14.0)
let boundingSize = CGSize(width: boundsSide, height: boundsSide)
if let placeholderNode = self.placeholderNode {
let placeholderFrame = CGRect(origin: CGPoint(x: floor((bounds.width - boundingSize.width) / 2.0), y: floor((bounds.height - boundingSize.height) / 2.0)), size: boundingSize)
placeholderNode.frame = bounds
// if let theme = self.theme, let (_, stickerItem) = self.currentState, let item = stickerItem {
// placeholderNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), data: item.file.immediateThumbnailData, size: placeholderFrame.size)
// }
}
self.imageNode.frame = bounds
// if let (_, item) = self.currentState {
// if let item = item, let dimensions = item.file.dimensions?.cgSize {
// let imageSize = dimensions.aspectFitted(boundingSize)
// self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
// self.imageNode.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
// if let animationNode = self.animationNode {
// animationNode.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
// animationNode.updateLayout(size: imageSize)
// }
// }
// }
}
override func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {
if let placeholderNode = self.placeholderNode {
placeholderNode.updateAbsoluteRect(absoluteRect, within: containerSize)
if let (_, _, dimensions) = self.currentState {
let imageSize = dimensions.aspectFitted(boundingSize)
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
if let animationNode = self.animationNode {
animationNode.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
animationNode.updateLayout(size: imageSize)
}
}
}
@ -232,7 +164,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
func updatePreviewing(animated: Bool) {
var isPreviewing = false
if let (_, maybeItem) = self.currentState, let interaction = self.interaction, let item = maybeItem {
if let (_, maybeItem, _) = self.currentState, let interaction = self.interaction, let item = maybeItem {
isPreviewing = interaction.previewedItem === item
}
if self.currentIsPreviewing != isPreviewing {

View File

@ -68,15 +68,11 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
self.textNode = ASTextNode()
self.imageNode = ASImageNode()
self.imageNode.displaysAsynchronously = false
self.imageNode.image = item.image
if case let .image(data) = item.content, let image = UIImage(data: data) {
self.imageNode.image = image
}
self.textNode.attributedText = NSAttributedString(string: item.emojis.joined(separator: " "), font: Font.regular(32.0), textColor: .black)
// for case let .Sticker(text, _, _) in item.file.attributes {
// self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(32.0), textColor: .black)
// break
// }
// if item.file.isAnimatedSticker {
// let animationNode = AnimatedStickerNode()
// self.animationNode = animationNode
@ -92,10 +88,6 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
// self.animationNode = nil
// }
// self.imageNode.setSignal(chatMessageSticker(account: account, file: item.file, small: false, fetched: true))
super.init()
self.isUserInteractionEnabled = false

View File

@ -330,8 +330,13 @@
- (void)updateWithFetchResult:(TGMediaAssetFetchResult *)fetchResult
{
TGMediaAsset *currentAsset = ((TGMediaPickerGalleryItem *)_galleryController.currentItem).asset;
bool exists = ([fetchResult indexOfAsset:currentAsset] != NSNotFound);
bool exists;
if ([currentAsset isKindOfClass:[TGCameraCapturedVideo class]]) {
exists = [fetchResult indexOfAsset:((TGCameraCapturedVideo *)currentAsset).originalAsset] != NSNotFound;
} else {
exists = ([fetchResult indexOfAsset:currentAsset] != NSNotFound);
}
if (!exists)
{
_galleryModel.dismiss(true, false);