Merge commit '140d58bdd67d3f2601c8c58cfa16acf01f4e64ae'

This commit is contained in:
Ali 2021-06-15 11:35:02 +04:00
commit c06ed7062f
27 changed files with 5486 additions and 4752 deletions

View File

@ -6495,6 +6495,18 @@ Sorry for the inconvenience.";
"VoiceChat.Unpin" = "Unpin";
"VoiceChat.VideoParticipantsLimitExceeded" = "Video is only available\nfor the first 30 members";
"VoiceChat.VideoParticipantsLimitExceeded" = "Video is only available\nfor the first %@ members";
"ImportStickerPack.StickerCount_1" = "1 Sticker";
"ImportStickerPack.StickerCount_2" = "2 Stickers";
"ImportStickerPack.StickerCount_3_10" = "%@ Stickers";
"ImportStickerPack.StickerCount_any" = "%@ Stickers";
"ImportStickerPack.StickerCount_many" = "%@ Stickers";
"ImportStickerPack.StickerCount_0" = "%@ Stickers";
"ImportStickerPack.CreateStickerSet" = "Create Sticker Set";
"ImportStickerPack.RemoveFromImport" = "Remove From Import";
"ImportStickerPack.ChooseName" = "Choose Name";
"ImportStickerPack.ChooseNameDescription" = "Please choose a name for your set.";
"ImportStickerPack.ImportingStickers" = "Importing Stickers";
"WallpaperPreview.PreviewBottomTextAnimatable" = "Tap the play button to view the background animation.";

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);

View File

@ -239,6 +239,15 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
}))
}
options.append(OpenInOption(identifier: "2gis", application: .other(title: "2GIS", identifier: 481627348, scheme: "dgis", store: nil), action: {
let coordinates = "\(lon),\(lat)"
if withDirections {
return .openUrl(url: "dgis://2gis.ru/routeSearch/to/\(coordinates)/go")
} else {
return .openUrl(url: "dgis://2gis.ru/geo/\(coordinates)")
}
}))
options.append(OpenInOption(identifier: "moovit", application: .other(title: "Moovit", identifier: 498477945, scheme: "moovit", store: nil), action: {
if withDirections {
let destName: String

View File

@ -44,6 +44,26 @@ let bottomAreaHeight: CGFloat = 206.0
private let fullscreenBottomAreaHeight: CGFloat = 80.0
private let bottomGradientHeight: CGFloat = 70.0
public struct VoiceChatConfiguration {
static var defaultValue: VoiceChatConfiguration {
return VoiceChatConfiguration(videoParticipantsMaxCount: 30)
}
public let videoParticipantsMaxCount: Int32
fileprivate init(videoParticipantsMaxCount: Int32) {
self.videoParticipantsMaxCount = videoParticipantsMaxCount
}
static func with(appConfiguration: AppConfiguration) -> VoiceChatConfiguration {
if let data = appConfiguration.data, let value = data["groupcall_video_participants_max"] as? Double {
return VoiceChatConfiguration(videoParticipantsMaxCount: Int32(value))
} else {
return .defaultValue
}
}
}
func decorationCornersImage(top: Bool, bottom: Bool, dark: Bool) -> UIImage? {
if !top && !bottom {
return nil
@ -88,8 +108,6 @@ func decorationTopCornersImage(dark: Bool) -> UIImage? {
})?.stretchableImage(withLeftCapWidth: 25, topCapHeight: 32)
}
func decorationBottomCornersImage(dark: Bool) -> UIImage? {
return generateImage(CGSize(width: 50.0, height: 110.0), rotatedContext: { (size, context) in
let bounds = CGRect(origin: CGPoint(), size: size)
@ -491,7 +509,7 @@ public final class VoiceChatController: ViewController {
text = .text(about, textIcon, .generic)
}
return VoiceChatTileItem(account: context.account, peer: peerEntry.peer, videoEndpointId: videoEndpointId, videoReady: videoReady, videoTimeouted: videoTimeouted, isVideoLimit: false, isPaused: videoIsPaused, isOwnScreencast: peerEntry.presentationEndpointId == videoEndpointId && peerEntry.isMyPeer, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, speaking: speaking, secondary: secondary, isTablet: isTablet, icon: showAsPresentation ? .presentation : icon, text: text, additionalText: additionalText, action: {
return VoiceChatTileItem(account: context.account, peer: peerEntry.peer, videoEndpointId: videoEndpointId, videoReady: videoReady, videoTimeouted: videoTimeouted, isVideoLimit: false, videoLimit: 0, isPaused: videoIsPaused, isOwnScreencast: peerEntry.presentationEndpointId == videoEndpointId && peerEntry.isMyPeer, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, speaking: speaking, secondary: secondary, isTablet: isTablet, icon: showAsPresentation ? .presentation : icon, text: text, additionalText: additionalText, action: {
interaction.switchToPeer(peer.id, videoEndpointId, !secondary)
}, contextAction: { node, gesture in
interaction.peerContextAction(peerEntry, node, gesture, false)
@ -749,6 +767,8 @@ public final class VoiceChatController: ViewController {
private let currentAvatarMixin = Atomic<TGMediaAvatarMenuMixin?>(value: nil)
private var configuration: VoiceChatConfiguration?
private weak var controller: VoiceChatController?
private let sharedContext: SharedAccountContext
private let context: AccountContext
@ -1863,16 +1883,21 @@ public final class VoiceChatController: ViewController {
self.call.state,
self.call.members,
invitedPeers,
self.displayAsPeersPromise.get()
self.displayAsPeersPromise.get(),
self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
)
|> mapToThrottled { values in
return .single(values)
|> then(.complete() |> delay(0.1, queue: Queue.mainQueue()))
}).start(next: { [weak self] state, callMembers, invitedPeers, displayAsPeers in
}).start(next: { [weak self] state, callMembers, invitedPeers, displayAsPeers, preferencesView in
guard let strongSelf = self else {
return
}
let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue
let configuration = VoiceChatConfiguration.with(appConfiguration: appConfiguration)
strongSelf.configuration = configuration
var animate = false
if strongSelf.callState != state {
if let previousCallState = strongSelf.callState {
@ -2461,14 +2486,18 @@ public final class VoiceChatController: ViewController {
})))
}
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_NoiseSuppression, textColor: .primary, textLayout: .secondLineWithValue(strongSelf.isNoiseSuppressionEnabled ? strongSelf.presentationData.strings.VoiceChat_NoiseSuppressionEnabled : strongSelf.presentationData.strings.VoiceChat_NoiseSuppressionDisabled), icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Noise"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.dismissWithoutContent)
if let strongSelf = self {
strongSelf.call.setIsNoiseSuppressionEnabled(!strongSelf.isNoiseSuppressionEnabled)
}
})))
let isScheduled = strongSelf.isScheduled
if !isScheduled {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_NoiseSuppression, textColor: .primary, textLayout: .secondLineWithValue(strongSelf.isNoiseSuppressionEnabled ? strongSelf.presentationData.strings.VoiceChat_NoiseSuppressionEnabled : strongSelf.presentationData.strings.VoiceChat_NoiseSuppressionDisabled), icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Noise"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.dismissWithoutContent)
if let strongSelf = self {
strongSelf.call.setIsNoiseSuppressionEnabled(!strongSelf.isNoiseSuppressionEnabled)
}
})))
}
if let callState = strongSelf.callState, callState.isVideoEnabled && (callState.muteState?.canUnmute ?? true) {
if #available(iOS 12.0, *) {
@ -2544,7 +2573,6 @@ public final class VoiceChatController: ViewController {
}
if canManageCall {
let isScheduled = strongSelf.isScheduled
items.append(.action(ContextMenuActionItem(text: isScheduled ? strongSelf.presentationData.strings.VoiceChat_CancelVoiceChat : strongSelf.presentationData.strings.VoiceChat_EndVoiceChat, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { _, f in
@ -4945,7 +4973,8 @@ public final class VoiceChatController: ViewController {
tileItems.removeAll()
gridTileItems.removeAll()
tileItems.append(VoiceChatTileItem(account: self.context.account, peer: peer, videoEndpointId: "", videoReady: false, videoTimeouted: true, isVideoLimit: true, isPaused: false, isOwnScreencast: false, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, speaking: false, secondary: false, isTablet: false, icon: .none, text: .none, additionalText: nil, action: {}, contextAction: nil, getVideo: { _ in return nil }, getAudioLevel: nil))
let configuration = self.configuration ?? VoiceChatConfiguration.defaultValue
tileItems.append(VoiceChatTileItem(account: self.context.account, peer: peer, videoEndpointId: "", videoReady: false, videoTimeouted: true, isVideoLimit: true, videoLimit: configuration.videoParticipantsMaxCount, isPaused: false, isOwnScreencast: false, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, speaking: false, secondary: false, isTablet: false, icon: .none, text: .none, additionalText: nil, action: {}, contextAction: nil, getVideo: { _ in return nil }, getAudioLevel: nil))
}
for member in callMembers.0 {

View File

@ -32,9 +32,8 @@ private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30)
private let borderLineWidth: CGFloat = 2.0
private let fadeColor = UIColor(rgb: 0x000000, alpha: 0.5)
private let fadeHeight: CGFloat = 50.0
let fadeHeight: CGFloat = 50.0
private var fadeImage: UIImage? = {
return generateImage(CGSize(width: fadeHeight, height: fadeHeight), rotatedContext: { size, context in
@ -185,7 +184,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
private var wavesColor: UIColor?
let videoContainerNode: ASDisplayNode
private let videoFadeNode: ASDisplayNode
let videoFadeNode: ASDisplayNode
var videoNode: GroupVideoNode?
private var profileNode: VoiceChatPeerProfileNode?
@ -844,7 +843,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
node.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2)
}
if !strongSelf.isExtracted && !strongSelf.animatingExtraction {
if !strongSelf.isExtracted && !strongSelf.animatingExtraction && strongSelf.videoContainerNode.supernode == strongSelf.offsetContainerNode {
strongSelf.videoFadeNode.frame = CGRect(x: 0.0, y: videoSize.height - fadeHeight, width: videoSize.width, height: fadeHeight)
strongSelf.videoContainerNode.bounds = CGRect(origin: CGPoint(), size: videoSize)

View File

@ -23,7 +23,6 @@ import ContextUI
private let backArrowImage = NavigationBarTheme.generateBackArrowImage(color: .white)
private let backgroundCornerRadius: CGFloat = 11.0
private let fadeColor = UIColor(rgb: 0x000000, alpha: 0.5)
private let fadeHeight: CGFloat = 50.0
private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30)
private class VoiceChatPinButtonNode: HighlightTrackingButtonNode {

View File

@ -35,8 +35,6 @@ final class VoiceChatPeerProfileNode: ASDisplayNode {
private let titleNode: ImmediateTextNode
private let statusNode: VoiceChatParticipantStatusNode
private var videoNode: GroupVideoNode?
private var appeared = false
init(context: AccountContext, size: CGSize, sourceSize: CGSize, peer: Peer, text: VoiceChatParticipantItem.ParticipantText, customNode: ASDisplayNode? = nil, additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError>, requestDismiss: (() -> Void)?) {
@ -300,10 +298,13 @@ final class VoiceChatPeerProfileNode: ASDisplayNode {
transition.updateCornerRadius(node: self.backgroundImageNode, cornerRadius: 0.0)
let initialRect: CGRect
let hasVideo: Bool
if let videoNode = sourceNode.videoNode, videoNode.supernode == sourceNode.videoContainerNode, !videoNode.alpha.isZero {
initialRect = sourceRect
hasVideo = true
} else {
initialRect = sourceNode.avatarNode.frame
hasVideo = false
}
let initialScale = initialRect.width / targetRect.width
@ -320,41 +321,29 @@ final class VoiceChatPeerProfileNode: ASDisplayNode {
appearanceTransition = .animated(duration: springDuration, curve: .customSpring(damping: springDamping, initialVelocity: 0.0))
}
if let videoNode = sourceNode.videoNode {
if let videoNode = sourceNode.videoNode, hasVideo {
videoNode.updateLayout(size: targetSize, layoutMode: .fillOrFitToSquare, transition: appearanceTransition)
appearanceTransition.updateFrame(node: videoNode, frame: CGRect(origin: CGPoint(), size: targetSize))
appearanceTransition.updateFrame(node: sourceNode.videoFadeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: targetSize.height - fadeHeight), size: CGSize(width: targetSize.width, height: fadeHeight)))
appearanceTransition.updateTransformScale(node: sourceNode.videoContainerNode, scale: 1.0)
appearanceTransition.updateFrame(node: sourceNode.videoContainerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: targetSize.width, height: targetSize.height + backgroundCornerRadius)))
sourceNode.videoContainerNode.cornerRadius = backgroundCornerRadius
appearanceTransition.updateAlpha(node: sourceNode.videoFadeNode, alpha: 0.0)
} else {
let transitionNode = ASImageNode()
transitionNode.clipsToBounds = true
transitionNode.displaysAsynchronously = false
transitionNode.displayWithoutProcessing = true
transitionNode.image = sourceNode.avatarNode.unroundedImage
transitionNode.frame = CGRect(origin: CGPoint(), size: targetSize)
transitionNode.cornerRadius = targetRect.width / 2.0
radiusTransition.updateCornerRadius(node: transitionNode, cornerRadius: 0.0)
sourceNode.avatarNode.isHidden = true
self.avatarListWrapperNode.contentNode.insertSubnode(transitionNode, at: 0)
}
self.insertSubnode(sourceNode.videoContainerNode, belowSubnode: self.avatarListWrapperNode)
let transitionNode = ASImageNode()
transitionNode.clipsToBounds = true
transitionNode.displaysAsynchronously = false
transitionNode.displayWithoutProcessing = true
transitionNode.image = sourceNode.avatarNode.unroundedImage
transitionNode.frame = CGRect(origin: CGPoint(), size: targetSize)
transitionNode.cornerRadius = targetRect.width / 2.0
radiusTransition.updateCornerRadius(node: transitionNode, cornerRadius: 0.0)
sourceNode.avatarNode.isHidden = true
self.avatarListWrapperNode.contentNode.insertSubnode(transitionNode, at: 0)
// if let snapshotView = sourceNode.infoNode.view.snapshotView(afterScreenUpdates: false) {
// self.videoFadeNode.image = sourceNode.fadeNode.image
// self.videoFadeNode.frame = CGRect(x: 0.0, y: sourceRect.height - sourceNode.fadeNode.frame.height, width: sourceRect.width, height: sourceNode.fadeNode.frame.height)
//
// self.insertSubnode(self.videoFadeNode, aboveSubnode: sourceNode.videoContainerNode)
// self.view.insertSubview(snapshotView, aboveSubview: sourceNode.videoContainerNode.view)
// snapshotView.frame = sourceRect
// transition.updateFrame(view: snapshotView, frame: CGRect(origin: CGPoint(x: 0.0, y: targetSize.height - snapshotView.frame.size.height), size: snapshotView.frame.size))
// snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
// snapshotView.removeFromSuperview()
// })
// transition.updateFrame(node: self.videoFadeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: targetSize.height - self.videoFadeNode.frame.size.height), size: CGSize(width: targetSize.width, height: self.videoFadeNode.frame.height)))
// self.videoFadeNode.alpha = 0.0
// self.videoFadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
// }
self.avatarListWrapperNode.layer.animateSpring(from: initialScale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
self.avatarListWrapperNode.layer.animateSpring(from: NSValue(cgPoint: initialRect.center), to: NSValue(cgPoint: self.avatarListWrapperNode.position), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, completion: { [weak self] _ in
if let strongSelf = self {
@ -450,6 +439,8 @@ final class VoiceChatPeerProfileNode: ASDisplayNode {
self.infoNode.alpha = 0.0
self.infoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
} else if let targetNode = targetNode as? VoiceChatFullscreenParticipantItemNode {
let backgroundTargetRect = targetRect
let initialSize = self.bounds
self.updateInfo(size: targetRect.size, sourceSize: targetRect.size, animate: true)
@ -457,7 +448,15 @@ final class VoiceChatPeerProfileNode: ASDisplayNode {
transition.updateCornerRadius(node: self.backgroundImageNode, cornerRadius: backgroundCornerRadius)
let targetScale = targetRect.width / avatarListContainerNode.frame.width
var targetRect = targetRect
let hasVideo: Bool
if let videoNode = targetNode.videoNode, !videoNode.alpha.isZero {
hasVideo = true
} else {
targetRect = targetNode.avatarNode.frame
hasVideo = false
}
let targetScale = targetRect.width / self.avatarListContainerNode.frame.width
self.insertSubnode(targetNode.videoContainerNode, belowSubnode: self.avatarListWrapperNode)
self.insertSubnode(self.videoFadeNode, aboveSubnode: targetNode.videoContainerNode)
@ -474,24 +473,16 @@ final class VoiceChatPeerProfileNode: ASDisplayNode {
radiusTransition.updateCornerRadius(node: self.avatarListContainerNode, cornerRadius: backgroundCornerRadius)
// if let snapshotView = targetNode.infoNode.view.snapshotView(afterScreenUpdates: false) {
// self.view.insertSubview(snapshotView, aboveSubview: targetNode.videoContainerNode.view)
// let snapshotFrame = snapshotView.frame
// snapshotView.frame = CGRect(origin: CGPoint(x: 0.0, y: initialSize.width - snapshotView.frame.size.height), size: snapshotView.frame.size)
// transition.updateFrame(view: snapshotView, frame: snapshotFrame)
// snapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
// transition.updateFrame(node: self.videoFadeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: targetRect.height - self.videoFadeNode.frame.size.height), size: CGSize(width: targetRect.width, height: self.videoFadeNode.frame.height)))
// self.videoFadeNode.alpha = 1.0
// self.videoFadeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
// }
if false, let videoNode = targetNode.videoNode {
videoNode.updateLayout(size: targetRect.size, layoutMode: .fillOrFitToSquare, transition: transition)
transition.updateFrame(node: videoNode, frame: targetRect)
transition.updateFrame(node: targetNode.videoContainerNode, frame: targetRect)
if hasVideo, let videoNode = targetNode.videoNode {
videoNode.updateLayout(size: CGSize(width: 180.0, height: 180.0), layoutMode: .fillOrFitToSquare, transition: transition)
transition.updateFrame(node: videoNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: 180.0, height: 180.0)))
transition.updateTransformScale(node: targetNode.videoContainerNode, scale: 84.0 / 180.0)
transition.updateFrameAsPositionAndBounds(node: targetNode.videoContainerNode, frame: CGRect(x: 0.0, y: 0.0, width: 180.0, height: 180.0))
transition.updatePosition(node: targetNode.videoContainerNode, position: CGPoint(x: 42.0, y: 42.0))
transition.updateFrame(node: targetNode.videoFadeNode, frame: CGRect(x: 0.0, y: 180.0 - fadeHeight, width: 180.0, height: fadeHeight))
transition.updateAlpha(node: targetNode.videoFadeNode, alpha: 1.0)
}
let backgroundTargetRect = targetRect
let initialBackgroundPosition = self.backgroundImageNode.position
self.backgroundImageNode.layer.position = backgroundTargetRect.center
let initialBackgroundBounds = self.backgroundImageNode.bounds

View File

@ -29,6 +29,7 @@ final class VoiceChatTileItem: Equatable {
let videoReady: Bool
let videoTimeouted: Bool
let isVideoLimit: Bool
let videoLimit: Int32
let isPaused: Bool
let isOwnScreencast: Bool
let strings: PresentationStrings
@ -48,13 +49,14 @@ final class VoiceChatTileItem: Equatable {
return self.videoEndpointId
}
init(account: Account, peer: Peer, videoEndpointId: String, videoReady: Bool, videoTimeouted: Bool, isVideoLimit: Bool, isPaused: Bool, isOwnScreencast: Bool, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, speaking: Bool, secondary: Bool, isTablet: Bool, icon: Icon, text: VoiceChatParticipantItem.ParticipantText, additionalText: VoiceChatParticipantItem.ParticipantText?, action: @escaping () -> Void, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?, getVideo: @escaping (GroupVideoNode.Position) -> GroupVideoNode?, getAudioLevel: (() -> Signal<Float, NoError>)?) {
init(account: Account, peer: Peer, videoEndpointId: String, videoReady: Bool, videoTimeouted: Bool, isVideoLimit: Bool, videoLimit: Int32, isPaused: Bool, isOwnScreencast: Bool, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, speaking: Bool, secondary: Bool, isTablet: Bool, icon: Icon, text: VoiceChatParticipantItem.ParticipantText, additionalText: VoiceChatParticipantItem.ParticipantText?, action: @escaping () -> Void, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?, getVideo: @escaping (GroupVideoNode.Position) -> GroupVideoNode?, getAudioLevel: (() -> Signal<Float, NoError>)?) {
self.account = account
self.peer = peer
self.videoEndpointId = videoEndpointId
self.videoReady = videoReady
self.videoTimeouted = videoTimeouted
self.isVideoLimit = isVideoLimit
self.videoLimit = videoLimit
self.isPaused = isPaused
self.isOwnScreencast = isOwnScreencast
self.strings = strings
@ -113,7 +115,6 @@ final class VoiceChatTileItem: Equatable {
}
private let fadeColor = UIColor(rgb: 0x000000, alpha: 0.5)
private let fadeHeight: CGFloat = 50.0
var tileFadeImage: UIImage? = {
return generateImage(CGSize(width: fadeHeight, height: fadeHeight), rotatedContext: { size, context in
@ -412,8 +413,8 @@ final class VoiceChatTileItemNode: ASDisplayNode {
var showPlaceholder = false
if item.isVideoLimit {
self.placeholderTextNode.attributedText = NSAttributedString(string: item.strings.VoiceChat_VideoParticipantsLimitExceeded, font: Font.semibold(13.0), textColor: .white)
self.placeholderIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/Pause"), color: .white)
self.placeholderTextNode.attributedText = NSAttributedString(string: item.strings.VoiceChat_VideoParticipantsLimitExceeded(String(item.videoLimit)).0, font: Font.semibold(13.0), textColor: .white)
self.placeholderIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/VideoUnavailable"), color: .white)
showPlaceholder = true
} else if item.isOwnScreencast {
self.placeholderTextNode.attributedText = NSAttributedString(string: item.strings.VoiceChat_YouAreSharingScreen, font: Font.semibold(13.0), textColor: .white)
@ -580,7 +581,8 @@ final class VoiceChatTileItemNode: ASDisplayNode {
let placeholderTextSize = self.placeholderTextNode.updateLayout(CGSize(width: size.width - 30.0, height: 100.0))
transition.updateFrame(node: self.placeholderTextNode, frame: CGRect(origin: CGPoint(x: floor((size.width - placeholderTextSize.width) / 2.0), y: floorToScreenPixels(size.height / 2.0) + 10.0), size: placeholderTextSize))
if let image = self.placeholderIconNode.image {
let imageSize = CGSize(width: image.size.width * 0.5, height: image.size.height * 0.5)
let imageScale: CGFloat = item.isVideoLimit ? 1.0 : 0.5
let imageSize = CGSize(width: image.size.width * imageScale, height: image.size.height * imageScale)
transition.updateFrame(node: self.placeholderIconNode, frame: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floorToScreenPixels(size.height / 2.0) - imageSize.height - 4.0), size: imageSize))
}
}

View File

@ -0,0 +1,209 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import SyncCore
private enum UploadStickerStatus {
case progress(Float)
case complete(TelegramMediaFile)
}
private enum UploadStickerError {
case generic
}
private struct UploadedStickerData {
fileprivate let resource: MediaResource
fileprivate let content: UploadedStickerDataContent
}
private enum UploadedStickerDataContent {
case result(MultipartUploadResult)
case error
}
private func uploadedSticker(postbox: Postbox, network: Network, resource: MediaResource) -> Signal<UploadedStickerData, NoError> {
return multipartUpload(network: network, postbox: postbox, source: .resource(.standalone(resource: resource)), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .file), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|> map { result -> UploadedStickerData in
return UploadedStickerData(resource: resource, content: .result(result))
}
|> `catch` { _ -> Signal<UploadedStickerData, NoError> in
return .single(UploadedStickerData(resource: resource, content: .error))
}
}
private func uploadSticker(account: Account, peer: Peer, resource: MediaResource, alt: String, dimensions: PixelDimensions, isAnimated: Bool) -> Signal<UploadStickerStatus, UploadStickerError> {
guard let inputPeer = apiInputPeer(peer) else {
return .fail(.generic)
}
return uploadedSticker(postbox: account.postbox, network: account.network, resource: resource)
|> mapError { _ -> UploadStickerError in return .generic }
|> mapToSignal { result -> Signal<UploadStickerStatus, UploadStickerError> in
switch result.content {
case .error:
return .fail(.generic)
case let .result(resultData):
switch resultData {
case let .progress(progress):
return .single(.progress(progress))
case let .inputFile(file):
var flags: Int32 = 0
flags |= (1 << 4)
var attributes: [Api.DocumentAttribute] = []
attributes.append(.documentAttributeSticker(flags: 0, alt: alt, stickerset: .inputStickerSetEmpty, maskCoords: nil))
attributes.append(.documentAttributeImageSize(w: dimensions.width, h: dimensions.height))
return account.network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: Api.InputMedia.inputMediaUploadedDocument(flags: flags, file: file, thumb: nil, mimeType: isAnimated ? "application/x-tgsticker": "image/png", attributes: attributes, stickers: nil, ttlSeconds: nil)))
|> mapError { _ -> UploadStickerError in return .generic }
|> mapToSignal { media -> Signal<UploadStickerStatus, UploadStickerError> in
switch media {
case let .messageMediaDocument(_, document, _):
if let document = document, let file = telegramMediaFileFromApiDocument(document) {
return .single(.complete(file))
}
default:
break
}
return .fail(.generic)
}
default:
return .fail(.generic)
}
}
}
}
public enum CreateStickerSetError {
case generic
}
public struct ImportSticker {
let resource: MediaResource
let emojis: [String]
let dimensions: PixelDimensions
public init(resource: MediaResource, emojis: [String], dimensions: PixelDimensions) {
self.resource = resource
self.emojis = emojis
self.dimensions = dimensions
}
}
public enum CreateStickerSetStatus {
case progress(Float)
case complete(StickerPackCollectionInfo, [ItemCollectionItem])
}
public func createStickerSet(account: Account, title: String, shortName: String, stickers: [ImportSticker], thumbnail: ImportSticker?, isAnimated: Bool) -> Signal<CreateStickerSetStatus, CreateStickerSetError> {
return account.postbox.loadedPeerWithId(account.peerId)
|> castError(CreateStickerSetError.self)
|> mapToSignal { peer -> Signal<CreateStickerSetStatus, CreateStickerSetError> in
guard let inputUser = apiInputUser(peer) else {
return .fail(.generic)
}
var uploadStickers: [Signal<UploadStickerStatus, CreateStickerSetError>] = []
var stickers = stickers
if let thumbnail = thumbnail {
stickers.append(thumbnail)
}
for sticker in stickers {
uploadStickers.append(uploadSticker(account: account, peer: peer, resource: sticker.resource, alt: sticker.emojis.first ?? "", dimensions: sticker.dimensions, isAnimated: isAnimated)
|> mapError { _ -> CreateStickerSetError in
return .generic
})
}
return combineLatest(uploadStickers)
|> mapToSignal { uploadedStickers -> Signal<CreateStickerSetStatus, CreateStickerSetError> in
var documents: [TelegramMediaFile] = []
for sticker in uploadedStickers {
if case let .complete(document) = sticker {
documents.append(document)
}
}
if documents.count == stickers.count {
var flags: Int32 = 0
if isAnimated {
flags |= (1 << 1)
}
var inputStickers: [Api.InputStickerSetItem] = []
let stickerDocuments = thumbnail != nil ? documents.dropLast() : documents
for i in 0 ..< stickerDocuments.count {
let sticker = stickers[i]
let document = documents[i]
if let resource = document.resource as? CloudDocumentMediaResource {
inputStickers.append(.inputStickerSetItem(flags: 0, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.first ?? "", maskCoords: nil))
}
}
var thumbnailDocument: Api.InputDocument?
if thumbnail != nil, let document = documents.last, let resource = document.resource as? CloudDocumentMediaResource {
flags |= (1 << 2)
thumbnailDocument = .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data()))
}
return account.network.request(Api.functions.stickers.createStickerSet(flags: flags, userId: inputUser, title: title, shortName: shortName, thumb: thumbnailDocument, stickers: inputStickers))
|> mapError { error -> CreateStickerSetError in
return .generic
}
|> mapToSignal { result -> Signal<CreateStickerSetStatus, CreateStickerSetError> in
let info: StickerPackCollectionInfo
var items: [ItemCollectionItem] = []
switch result {
case let .stickerSet(set, packs, documents):
let namespace: ItemCollectionId.Namespace
switch set {
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _):
if (flags & (1 << 3)) != 0 {
namespace = Namespaces.ItemCollection.CloudMaskPacks
} else {
namespace = Namespaces.ItemCollection.CloudStickerPacks
}
}
info = StickerPackCollectionInfo(apiSet: set, namespace: namespace)
var indexKeysByFile: [MediaId: [MemoryBuffer]] = [:]
for pack in packs {
switch pack {
case let .stickerPack(text, fileIds):
let key = ValueBoxKey(text).toMemoryBuffer()
for fileId in fileIds {
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
if indexKeysByFile[mediaId] == nil {
indexKeysByFile[mediaId] = [key]
} else {
indexKeysByFile[mediaId]!.append(key)
}
}
}
}
for apiDocument in documents {
if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id {
let fileIndexKeys: [MemoryBuffer]
if let indexKeys = indexKeysByFile[id] {
fileIndexKeys = indexKeys
} else {
fileIndexKeys = []
}
items.append(StickerPackItem(index: ItemCollectionItemIndex(index: Int32(items.count), id: id.id), file: file, indexKeys: fileIndexKeys))
}
}
}
return .single(.complete(info, items))
}
} else {
var totalProgress: Float = 0.0
for sticker in uploadedStickers {
switch sticker {
case .complete:
totalProgress += 1.0
case let .progress(progress):
totalProgress += progress
}
}
let normalizedProgress = min(1.0, max(0.0, totalProgress / Float(stickers.count)))
return .single(.progress(normalizedProgress))
}
}
}
}

View File

@ -1,6 +1,7 @@
{
"images" : [
{
"filename" : "CameraOff.pdf",
"idiom" : "universal"
}
],

View File

@ -6498,7 +6498,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = ApplicationSpecificNotice.incrementChatMessageOptionsTip(accountManager: strongSelf.context.sharedContext.accountManager, count: 4).start()
let controller = ChatSendMessageActionSheetController(context: strongSelf.context, controllerInteraction: strongSelf.controllerInteraction, interfaceState: strongSelf.presentationInterfaceState, gesture: gesture, sendButtonFrame: node.view.convert(node.bounds, to: nil), textInputNode: textInputNode, completion: { [weak self] in
let controller = ChatSendMessageActionSheetController(context: strongSelf.context, controllerInteraction: strongSelf.controllerInteraction, interfaceState: strongSelf.presentationInterfaceState, gesture: gesture, sourceSendButton: node, textInputNode: textInputNode, completion: { [weak self] in
if let strongSelf = self {
strongSelf.supportedOrientations = previousSupportedOrientations
}

View File

@ -17,7 +17,7 @@ final class ChatSendMessageActionSheetController: ViewController {
private let controllerInteraction: ChatControllerInteraction?
private let interfaceState: ChatPresentationInterfaceState
private let gesture: ContextGesture
private let sendButtonFrame: CGRect
private let sourceSendButton: ASDisplayNode
private let textInputNode: EditableTextNode
private let completion: () -> Void
@ -29,12 +29,12 @@ final class ChatSendMessageActionSheetController: ViewController {
private let hapticFeedback = HapticFeedback()
init(context: AccountContext, controllerInteraction: ChatControllerInteraction?, interfaceState: ChatPresentationInterfaceState, gesture: ContextGesture, sendButtonFrame: CGRect, textInputNode: EditableTextNode, completion: @escaping () -> Void) {
init(context: AccountContext, controllerInteraction: ChatControllerInteraction?, interfaceState: ChatPresentationInterfaceState, gesture: ContextGesture, sourceSendButton: ASDisplayNode, textInputNode: EditableTextNode, completion: @escaping () -> Void) {
self.context = context
self.controllerInteraction = controllerInteraction
self.interfaceState = interfaceState
self.gesture = gesture
self.sendButtonFrame = sendButtonFrame
self.sourceSendButton = sourceSendButton
self.textInputNode = textInputNode
self.completion = completion
@ -76,7 +76,7 @@ final class ChatSendMessageActionSheetController: ViewController {
canSchedule = !isSecret
}
self.displayNode = ChatSendMessageActionSheetControllerNode(context: self.context, reminders: reminders, gesture: gesture, sendButtonFrame: self.sendButtonFrame, textInputNode: self.textInputNode, forwardedCount: forwardedCount, send: { [weak self] in
self.displayNode = ChatSendMessageActionSheetControllerNode(context: self.context, reminders: reminders, gesture: gesture, sourceSendButton: self.sourceSendButton, textInputNode: self.textInputNode, forwardedCount: forwardedCount, send: { [weak self] in
self?.controllerInteraction?.sendCurrentMessage(false)
self?.dismiss(cancel: false)
}, sendSilently: { [weak self] in

View File

@ -154,7 +154,7 @@ private final class ActionSheetItemNode: ASDisplayNode {
final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
private let context: AccountContext
private var presentationData: PresentationData
private let sendButtonFrame: CGRect
private let sourceSendButton: ASDisplayNode
private let textFieldFrame: CGRect
private let textInputNode: EditableTextNode
private let accessoryPanelNode: AccessoryPanelNode?
@ -163,9 +163,6 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
private let send: (() -> Void)?
private let cancel: (() -> Void)?
private let textCoverNode: ASDisplayNode
private let buttonCoverNode: ASDisplayNode
private let effectView: UIVisualEffectView
private let dimNode: ASDisplayNode
@ -181,10 +178,14 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
private var validLayout: ContainerViewLayout?
init(context: AccountContext, reminders: Bool, gesture: ContextGesture, sendButtonFrame: CGRect, textInputNode: EditableTextNode, forwardedCount: Int?, send: (() -> Void)?, sendSilently: (() -> Void)?, schedule: (() -> Void)?, cancel: (() -> Void)?) {
private var sendButtonFrame: CGRect {
return self.sourceSendButton.view.convert(self.sourceSendButton.bounds, to: nil)
}
init(context: AccountContext, reminders: Bool, gesture: ContextGesture, sourceSendButton: ASDisplayNode, textInputNode: EditableTextNode, forwardedCount: Int?, send: (() -> Void)?, sendSilently: (() -> Void)?, schedule: (() -> Void)?, cancel: (() -> Void)?) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.sendButtonFrame = sendButtonFrame
self.sourceSendButton = sourceSendButton
self.textFieldFrame = textInputNode.convert(textInputNode.bounds, to: nil)
self.textInputNode = textInputNode
self.accessoryPanelNode = nil
@ -192,10 +193,7 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
self.send = send
self.cancel = cancel
self.textCoverNode = ASDisplayNode()
self.buttonCoverNode = ASDisplayNode()
self.effectView = UIVisualEffectView()
if #available(iOS 9.0, *) {
} else {
@ -249,13 +247,7 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
self.contentNodes = contentNodes
super.init()
self.textCoverNode.backgroundColor = self.presentationData.theme.chat.inputPanel.inputBackgroundColor
self.addSubnode(self.textCoverNode)
self.buttonCoverNode.backgroundColor = self.presentationData.theme.chat.inputPanel.panelBackgroundColor
self.addSubnode(self.buttonCoverNode)
self.sendButtonNode.setImage(PresentationResourcesChat.chatInputPanelSendButtonImage(self.presentationData.theme), for: [])
self.sendButtonNode.addTarget(self, action: #selector(sendButtonPressed), forControlEvents: .touchUpInside)
@ -370,8 +362,6 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
self.dimNode.backgroundColor = presentationData.theme.contextMenu.dimColor
self.contentContainerNode.backgroundColor = self.presentationData.theme.contextMenu.backgroundColor
self.textCoverNode.backgroundColor = self.presentationData.theme.chat.inputPanel.inputBackgroundColor
self.buttonCoverNode.backgroundColor = self.presentationData.theme.chat.inputPanel.panelBackgroundColor
self.sendButtonNode.setImage(PresentationResourcesChat.chatInputPanelSendButtonImage(self.presentationData.theme), for: [])
if let toAttributedText = self.textInputNode.attributedText?.mutableCopy() as? NSMutableAttributedString {
@ -406,6 +396,9 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
self.fromMessageTextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
self.toMessageTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, removeOnCompletion: false)
self.textInputNode.isHidden = true
self.sourceSendButton.isHidden = true
if let layout = self.validLayout {
let duration = 0.4
@ -466,8 +459,8 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
let intermediateCompletion: () -> Void = { [weak self] in
if completedEffect && completedButton && completedBubble && completedAlpha {
self?.textCoverNode.isHidden = true
self?.buttonCoverNode.isHidden = true
self?.textInputNode.isHidden = false
self?.sourceSendButton.isHidden = false
completion()
}
}
@ -494,7 +487,7 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
intermediateCompletion()
})
} else {
self.textCoverNode.isHidden = true
self.textInputNode.isHidden = false
self.messageClipNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
completedAlpha = true
intermediateCompletion()
@ -510,7 +503,7 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
})
if !cancel {
self.buttonCoverNode.isHidden = true
self.sourceSendButton.isHidden = false
self.sendButtonNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.2, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false)
self.sendButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false)
}
@ -565,10 +558,7 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
transition.updateFrame(node: self.textCoverNode, frame: self.textFieldFrame)
transition.updateFrame(node: self.buttonCoverNode, frame: self.sendButtonFrame.offsetBy(dx: 1.0, dy: 1.0))
transition.updateFrame(view: self.effectView, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))

View File

@ -481,7 +481,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
}
case .importStickers:
dismissInput()
if let navigationController = navigationController, let data = UIPasteboard.general.data(forPasteboardType: "org.telegram.third-party.stickerpack"), let stickerPack = ImportStickerPack(data: data) {
if let navigationController = navigationController, let data = UIPasteboard.general.data(forPasteboardType: "org.telegram.third-party.stickerpack"), let stickerPack = ImportStickerPack(data: data), !stickerPack.stickers.isEmpty {
for controller in navigationController.overlayControllers {
if controller is ImportStickerPackController {
controller.dismiss()

View File

@ -61,7 +61,7 @@ private final class PeerInfoScreenInfoItemNode: PeerInfoScreenItemNode {
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
let infoItem = InfoListItem(presentationData: ItemListPresentationData(presentationData), title: item.title, text: item.text, style: .plain, linkAction: { link in
let infoItem = InfoListItem(presentationData: ItemListPresentationData(presentationData), title: item.title, text: item.text, style: .blocks, linkAction: { link in
item.linkAction?(link)
}, closeAction: nil)
let params = ListViewItemLayoutParams(width: width, leftInset: safeInsets.left, rightInset: safeInsets.right, availableHeight: 1000.0)

View File

@ -951,14 +951,14 @@ func peerInfoHeaderButtonIsHiddenWhileExpanded(buttonKey: PeerInfoHeaderButtonKe
var hiddenWhileExpanded = false
if isOpenedFromChat {
switch buttonKey {
case .message, .search, .videoCall, .addMember:
case .message, .search, .videoCall, .addMember, .leave, .discussion:
hiddenWhileExpanded = true
default:
hiddenWhileExpanded = false
}
} else {
switch buttonKey {
case .search, .call, .videoCall, .addMember:
case .search, .call, .videoCall, .addMember, .leave, .discussion:
hiddenWhileExpanded = true
default:
hiddenWhileExpanded = false
@ -1011,7 +1011,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
if channel.flags.contains(.hasVoiceChat) {
hasVoiceChat = true
}
if channel.flags.contains(.isCreator) || channel.hasPermission(.manageCalls) {
if channel.flags.contains(.isCreator) {
displayMore = true
}
switch channel.info {
@ -1118,6 +1118,9 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
}
if isExpanded && result.count > 3 {
result = result.filter { !peerInfoHeaderButtonIsHiddenWhileExpanded(buttonKey: $0, isOpenedFromChat: isOpenedFromChat) }
if !result.contains(.more) {
result.append(.more)
}
}
return result
}

View File

@ -3687,7 +3687,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self?.openDeletePeer()
})))
} else {
if !headerButtons.contains(.leave) {
if filteredButtons.contains(.leave) {
if case .member = channel.participationStatus {
if !items.isEmpty {
items.append(.separator)