Various Improvements

This commit is contained in:
Ilya Laktyushin 2022-01-22 20:54:32 +03:00
parent 241f78ddd9
commit 5154356e38
24 changed files with 339 additions and 94 deletions

View File

@ -115,7 +115,9 @@ public final class CallListController: TelegramBaseController {
self.tabBarItem.title = self.presentationData.strings.Calls_TabTitle self.tabBarItem.title = self.presentationData.strings.Calls_TabTitle
self.tabBarItem.image = icon self.tabBarItem.image = icon
self.tabBarItem.selectedImage = icon self.tabBarItem.selectedImage = icon
self.tabBarItem.animationName = "TabCalls" if !self.presentationData.reduceMotion {
self.tabBarItem.animationName = "TabCalls"
}
} }
self.segmentedTitleView.indexUpdated = { [weak self] index in self.segmentedTitleView.indexUpdated = { [weak self] index in
@ -166,6 +168,11 @@ public final class CallListController: TelegramBaseController {
self.segmentedTitleView.index = index self.segmentedTitleView.index = index
self.tabBarItem.title = self.presentationData.strings.Calls_TabTitle self.tabBarItem.title = self.presentationData.strings.Calls_TabTitle
if !self.presentationData.reduceMotion {
self.tabBarItem.animationName = "TabCalls"
} else {
self.tabBarItem.animationName = nil
}
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
switch self.mode { switch self.mode {
case .tab: case .tab:

View File

@ -2,6 +2,7 @@ import Foundation
import AsyncDisplayKit import AsyncDisplayKit
import Display import Display
import AVFoundation import AVFoundation
import SwiftSignalKit
private final class CameraPreviewNodeLayerNullAction: NSObject, CAAction { private final class CameraPreviewNodeLayerNullAction: NSObject, CAAction {
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) { @objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
@ -16,16 +17,25 @@ private final class CameraPreviewNodeLayer: AVSampleBufferDisplayLayer {
public final class CameraPreviewNode: ASDisplayNode { public final class CameraPreviewNode: ASDisplayNode {
private var displayLayer: AVSampleBufferDisplayLayer private var displayLayer: AVSampleBufferDisplayLayer
private let fadeNode: ASDisplayNode
private var fadedIn = false
public override init() { public override init() {
self.displayLayer = AVSampleBufferDisplayLayer() self.displayLayer = AVSampleBufferDisplayLayer()
self.displayLayer.videoGravity = .resizeAspectFill self.displayLayer.videoGravity = .resizeAspectFill
self.fadeNode = ASDisplayNode()
self.fadeNode.backgroundColor = .black
self.fadeNode.isUserInteractionEnabled = false
super.init() super.init()
self.clipsToBounds = true self.clipsToBounds = true
self.layer.addSublayer(self.displayLayer) self.layer.addSublayer(self.displayLayer)
self.addSubnode(self.fadeNode)
} }
func prepare() { func prepare() {
@ -36,6 +46,14 @@ public final class CameraPreviewNode: ASDisplayNode {
func enqueue(_ sampleBuffer: CMSampleBuffer) { func enqueue(_ sampleBuffer: CMSampleBuffer) {
self.displayLayer.enqueue(sampleBuffer) self.displayLayer.enqueue(sampleBuffer)
if !self.fadedIn {
self.fadedIn = true
Queue.mainQueue().after(0.2) {
self.fadeNode.alpha = 0.0
self.fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
}
} }
override public func layout() { override public func layout() {
@ -46,5 +64,6 @@ public final class CameraPreviewNode: ASDisplayNode {
self.displayLayer.setAffineTransform(transform) self.displayLayer.setAffineTransform(transform)
self.displayLayer.frame = self.bounds self.displayLayer.frame = self.bounds
self.fadeNode.frame = self.bounds
} }
} }

View File

@ -206,8 +206,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.tabBarItem.image = icon self.tabBarItem.image = icon
self.tabBarItem.selectedImage = icon self.tabBarItem.selectedImage = icon
self.tabBarItem.animationName = "TabChats" if !self.presentationData.reduceMotion {
self.tabBarItem.animationOffset = CGPoint(x: 0.0, y: UIScreenPixel) self.tabBarItem.animationName = "TabChats"
self.tabBarItem.animationOffset = CGPoint(x: 0.0, y: UIScreenPixel)
}
let leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) let leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
leftBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Edit leftBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Edit
@ -520,6 +522,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.DialogList_Title, style: .plain, target: nil, action: nil) let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.DialogList_Title, style: .plain, target: nil, action: nil)
backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back
self.navigationItem.backBarButtonItem = backBarButtonItem self.navigationItem.backBarButtonItem = backBarButtonItem
if !self.presentationData.reduceMotion {
self.tabBarItem.animationName = "TabChats"
} else {
self.tabBarItem.animationName = nil
}
} else { } else {
let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back

View File

@ -192,7 +192,9 @@ public class ContactsController: ViewController {
self.tabBarItem.image = icon self.tabBarItem.image = icon
self.tabBarItem.selectedImage = icon self.tabBarItem.selectedImage = icon
self.tabBarItem.animationName = "TabContacts" if !self.presentationData.reduceMotion {
self.tabBarItem.animationName = "TabContacts"
}
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
@ -281,6 +283,11 @@ public class ContactsController: ViewController {
self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search) self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search)
self.title = self.presentationData.strings.Contacts_Title self.title = self.presentationData.strings.Contacts_Title
self.tabBarItem.title = self.presentationData.strings.Contacts_Title self.tabBarItem.title = self.presentationData.strings.Contacts_Title
if !self.presentationData.reduceMotion {
self.tabBarItem.animationName = "TabContacts"
} else {
self.tabBarItem.animationName = nil
}
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
if self.navigationItem.rightBarButtonItem != nil { if self.navigationItem.rightBarButtonItem != nil {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationAddIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.addPressed)) self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationAddIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.addPressed))

View File

@ -143,7 +143,7 @@ final class GameControllerNode: ViewControllerTracingNode {
if eventName == "share_game" || eventName == "share_score" { if eventName == "share_game" || eventName == "share_score" {
if let (botPeer, gameName) = self.shareData(), let addressName = botPeer.addressName, !addressName.isEmpty, !gameName.isEmpty { if let (botPeer, gameName) = self.shareData(), let addressName = botPeer.addressName, !addressName.isEmpty, !gameName.isEmpty {
if eventName == "share_score" { if eventName == "share_score" {
self.present(ShareController(context: self.context, subject: .fromExternal({ [weak self] peerIds, text, account in self.present(ShareController(context: self.context, subject: .fromExternal({ [weak self] peerIds, text, account, _ in
if let strongSelf = self { if let strongSelf = self {
let signals = peerIds.map { TelegramEngine(account: account).messages.forwardGameWithScore(messageId: strongSelf.message.id, to: $0, as: nil) } let signals = peerIds.map { TelegramEngine(account: account).messages.forwardGameWithScore(messageId: strongSelf.message.id, to: $0, as: nil) }
return .single(.preparing) return .single(.preparing)

View File

@ -1,6 +1,7 @@
import Foundation import Foundation
import UIKit import UIKit
import Postbox import Postbox
import TelegramCore
enum StickerVerificationStatus { enum StickerVerificationStatus {
case loading case loading
@ -9,10 +10,39 @@ enum StickerVerificationStatus {
} }
public class ImportStickerPack { public class ImportStickerPack {
public enum StickerPackType {
case image
case animation
case video
var importType: CreateStickerSetType {
switch self {
case .image:
return .image
case .animation:
return .animation
case .video:
return .video
}
}
}
public class Sticker: Equatable { public class Sticker: Equatable {
public enum Content { public enum Content {
case image(Data) case image(Data)
case animation(Data) case animation(Data)
case video(Data, String)
}
var mimeType: String {
switch self.content {
case .image:
return "image/png"
case .animation:
return "application/x-tgsticker"
case let .video(_, mimeType):
return mimeType
}
} }
public static func == (lhs: ImportStickerPack.Sticker, rhs: ImportStickerPack.Sticker) -> Bool { public static func == (lhs: ImportStickerPack.Sticker, rhs: ImportStickerPack.Sticker) -> Bool {
@ -32,9 +62,7 @@ public class ImportStickerPack {
var data: Data { var data: Data {
switch self.content { switch self.content {
case let .image(data): case let .image(data), let .animation(data), let .video(data, _):
return data
case let .animation(data):
return data return data
} }
} }
@ -49,7 +77,7 @@ public class ImportStickerPack {
} }
public let software: String public let software: String
public let isAnimated: Bool public let type: StickerPackType
public let thumbnail: Sticker? public let thumbnail: Sticker?
public let stickers: [Sticker] public let stickers: [Sticker]
@ -59,20 +87,33 @@ public class ImportStickerPack {
} }
self.software = json["software"] as? String ?? "" self.software = json["software"] as? String ?? ""
let isAnimated = json["isAnimated"] as? Bool ?? false let isAnimated = json["isAnimated"] as? Bool ?? false
self.isAnimated = isAnimated let isVideo = json["isVideo"] as? Bool ?? false
let type: StickerPackType
if isAnimated {
type = .animation
} else if isVideo {
type = .video
} else {
type = .image
}
self.type = type
func parseSticker(_ sticker: [String: Any]) -> Sticker? { func parseSticker(_ sticker: [String: Any]) -> Sticker? {
if let dataString = sticker["data"] as? String, let mimeType = sticker["mimeType"] as? String, let data = Data(base64Encoded: dataString) { if let dataString = sticker["data"] as? String, let mimeType = sticker["mimeType"] as? String, let data = Data(base64Encoded: dataString) {
var content: Sticker.Content? var content: Sticker.Content?
switch mimeType.lowercased() { switch mimeType.lowercased() {
case "image/png": case "image/png":
if !isAnimated { if case .image = type {
content = .image(data) content = .image(data)
} }
case "application/x-tgsticker": case "application/x-tgsticker":
if isAnimated { if case .animation = type {
content = .animation(data) content = .animation(data)
} }
case "video/webm", "image/webp", "image/gif":
if case .video = type {
content = .video(data, mimeType)
}
default: default:
break break
} }

View File

@ -79,7 +79,8 @@ public final class ImportStickerPackController: ViewController, StandalonePresen
Queue.mainQueue().after(0.1) { Queue.mainQueue().after(0.1) {
self.controllerNode.updateStickerPack(self.stickerPack, verifiedStickers: Set(), declinedStickers: Set(), uploadedStickerResources: [:]) self.controllerNode.updateStickerPack(self.stickerPack, verifiedStickers: Set(), declinedStickers: Set(), uploadedStickerResources: [:])
if self.stickerPack.isAnimated { if case .image = self.stickerPack.type {
} else {
let _ = (self.context.account.postbox.loadedPeerWithId(self.context.account.peerId) let _ = (self.context.account.postbox.loadedPeerWithId(self.context.account.peerId)
|> deliverOnMainQueue).start(next: { [weak self] peer in |> deliverOnMainQueue).start(next: { [weak self] peer in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -89,13 +90,13 @@ public final class ImportStickerPackController: ViewController, StandalonePresen
var signals: [Signal<(UUID, StickerVerificationStatus, MediaResource?), NoError>] = [] var signals: [Signal<(UUID, StickerVerificationStatus, MediaResource?), NoError>] = []
for sticker in strongSelf.stickerPack.stickers { for sticker in strongSelf.stickerPack.stickers {
if let resource = strongSelf.controllerNode.stickerResources[sticker.uuid] { if let resource = strongSelf.controllerNode.stickerResources[sticker.uuid] {
signals.append(strongSelf.context.engine.stickers.uploadSticker(peer: peer, resource: resource, alt: sticker.emojis.first ?? "", dimensions: PixelDimensions(width: 512, height: 512), isAnimated: true) signals.append(strongSelf.context.engine.stickers.uploadSticker(peer: peer, resource: resource, alt: sticker.emojis.first ?? "", dimensions: PixelDimensions(width: 512, height: 512), mimeType: sticker.mimeType)
|> map { result -> (UUID, StickerVerificationStatus, MediaResource?) in |> map { result -> (UUID, StickerVerificationStatus, MediaResource?) in
switch result { switch result {
case .progress: case .progress:
return (sticker.uuid, .loading, nil) return (sticker.uuid, .loading, nil)
case let .complete(resource, mimeType): case let .complete(resource, mimeType):
if mimeType == "application/x-tgsticker" { if ["application/x-tgsticker", "video/webm"].contains(mimeType) {
return (sticker.uuid, .verified, resource) return (sticker.uuid, .verified, resource)
} else { } else {
return (sticker.uuid, .declined, nil) return (sticker.uuid, .declined, nil)

View File

@ -607,9 +607,9 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
if let localResource = item.stickerItem.resource { if let localResource = item.stickerItem.resource {
self.context.account.postbox.mediaBox.copyResourceData(from: localResource.id, to: resource.id) self.context.account.postbox.mediaBox.copyResourceData(from: localResource.id, to: resource.id)
} }
stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions)) stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType))
} else if let resource = item.stickerItem.resource { } else if let resource = item.stickerItem.resource {
stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions)) stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType))
} }
} }
var thumbnailSticker: ImportSticker? var thumbnailSticker: ImportSticker?
@ -620,7 +620,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
} }
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnail.data) self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnail.data)
thumbnailSticker = ImportSticker(resource: resource, emojis: [], dimensions: dimensions) thumbnailSticker = ImportSticker(resource: resource, emojis: [], dimensions: dimensions, mimeType: thumbnail.mimeType)
} }
let firstStickerItem = thumbnailSticker ?? stickers.first let firstStickerItem = thumbnailSticker ?? stickers.first
@ -631,13 +631,17 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.2, curve: .easeInOut)) self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.2, curve: .easeInOut))
} }
self.disposable.set((self.context.engine.stickers.createStickerSet(title: title, shortName: shortName, stickers: stickers, thumbnail: thumbnailSticker, isAnimated: stickerPack.isAnimated, software: stickerPack.software) self.disposable.set((self.context.engine.stickers.createStickerSet(title: title, shortName: shortName, stickers: stickers, thumbnail: thumbnailSticker, type: stickerPack.type.importType, software: stickerPack.software)
|> deliverOnMainQueue).start(next: { [weak self] status in |> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self { if let strongSelf = self {
if case let .complete(info, items) = status { if case let .complete(info, items) = status {
if let (_, _, count) = strongSelf.progress { if let (_, _, count) = strongSelf.progress {
strongSelf.progress = (1.0, count, count) strongSelf.progress = (1.0, count, count)
strongSelf.radialStatus.transitionToState(.progress(color: strongSelf.presentationData.theme.list.itemAccentColor, lineWidth: 6.0, value: 1.0, cancelEnabled: false, animateRotation: false), animated: !stickerPack.isAnimated, synchronous: true, completion: {}) var animated = false
if case .image = stickerPack.type {
animated = true
}
strongSelf.radialStatus.transitionToState(.progress(color: strongSelf.presentationData.theme.list.itemAccentColor, lineWidth: 6.0, value: 1.0, cancelEnabled: false, animateRotation: false), animated: animated, synchronous: true, completion: {})
if let (layout, navigationBarHeight) = strongSelf.containerLayout { if let (layout, navigationBarHeight) = strongSelf.containerLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
} }
@ -675,7 +679,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
Queue.mainQueue().after(1.0) { Queue.mainQueue().after(1.0) {
var firstItem: StickerPackItem? var firstItem: StickerPackItem?
if let firstStickerItem = firstStickerItem, let resource = firstStickerItem.resource as? TelegramMediaResource { if let firstStickerItem = firstStickerItem, let resource = firstStickerItem.resource as? TelegramMediaResource {
firstItem = StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: stickerPack.isAnimated ? "application/x-tgsticker": "image/png", size: nil, attributes: [.FileName(fileName: stickerPack.isAnimated ? "sticker.tgs" : "sticker.png"), .ImageSize(size: firstStickerItem.dimensions)]), indexKeys: []) firstItem = StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: firstStickerItem.mimeType, size: nil, attributes: [.FileName(fileName: ""), .ImageSize(size: firstStickerItem.dimensions)]), indexKeys: [])
} }
strongSelf.presentInGlobalOverlay?(UndoOverlayController(presentationData: strongSelf.presentationData, content: .stickersModified(title: strongSelf.presentationData.strings.StickerPackActionInfo_AddedTitle, text: strongSelf.presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: firstItem ?? items.first, context: strongSelf.context), elevatedLayout: false, action: { action in strongSelf.presentInGlobalOverlay?(UndoOverlayController(presentationData: strongSelf.presentationData, content: .stickersModified(title: strongSelf.presentationData.strings.StickerPackActionInfo_AddedTitle, text: strongSelf.presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: firstItem ?? items.first, context: strongSelf.context), elevatedLayout: false, action: { action in
if case .info = action { if case .info = action {
@ -781,11 +785,16 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
item.resource = resource item.resource = resource
self.stickerResources[item.uuid] = resource self.stickerResources[item.uuid] = resource
} }
updatedItems.append(StickerPackPreviewGridEntry(index: updatedItems.count, stickerItem: item, isVerified: !item.isAnimated || verifiedStickers.contains(item.uuid))) var isInitiallyVerified = false
if case .image = item.content {
isInitiallyVerified = true
}
updatedItems.append(StickerPackPreviewGridEntry(index: updatedItems.count, stickerItem: item, isVerified: isInitiallyVerified || verifiedStickers.contains(item.uuid)))
} }
self.pendingItems = updatedItems self.pendingItems = updatedItems
if stickerPack.isAnimated { if case .image = stickerPack.type {
} else {
self.stickerPackReady = stickerPack.stickers.count == (verifiedStickers.count + declinedStickers.count) && updatedItems.count > 0 self.stickerPackReady = stickerPack.stickers.count == (verifiedStickers.count + declinedStickers.count) && updatedItems.count > 0
} }

View File

@ -11,6 +11,7 @@ import AnimatedStickerNode
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
import TelegramPresentationData import TelegramPresentationData
import ShimmerEffect import ShimmerEffect
import SoftwareVideo
final class StickerPackPreviewInteraction { final class StickerPackPreviewInteraction {
var previewedItem: ImportStickerPack.Sticker? var previewedItem: ImportStickerPack.Sticker?
@ -60,6 +61,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
private var isVerified: Bool? private var isVerified: Bool?
private let imageNode: ASImageNode private let imageNode: ASImageNode
private var animationNode: AnimatedStickerNode? private var animationNode: AnimatedStickerNode?
private var videoNode: VideoStickerNode?
private var placeholderNode: ShimmerEffectNode? private var placeholderNode: ShimmerEffectNode?
private var theme: PresentationTheme? private var theme: PresentationTheme?
@ -145,6 +147,39 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
let placeholderNode = ShimmerEffectNode() let placeholderNode = ShimmerEffectNode()
self.placeholderNode = placeholderNode self.placeholderNode = placeholderNode
self.addSubnode(placeholderNode)
if let (absoluteRect, containerSize) = self.absoluteLocation {
placeholderNode.updateAbsoluteRect(absoluteRect, within: containerSize)
}
}
case .video:
self.imageNode.isHidden = true
if isVerified {
let videoNode = VideoStickerNode()
self.videoNode = videoNode
if let resource = stickerItem.resource as? TelegramMediaResource {
let dummyFile = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 1), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/webm", size: resource.size ?? 1, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [])])
videoNode.update(account: account, fileReference: .standalone(media: dummyFile))
}
if let placeholderNode = self.placeholderNode {
self.placeholderNode = nil
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak placeholderNode] _ in
placeholderNode?.removeFromSupernode()
})
self.insertSubnode(videoNode, belowSubnode: placeholderNode)
} else {
self.addSubnode(videoNode)
}
videoNode.update(isPlaying: self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true)
} else {
let placeholderNode = ShimmerEffectNode()
self.placeholderNode = placeholderNode
self.addSubnode(placeholderNode) self.addSubnode(placeholderNode)
if let (absoluteRect, containerSize) = self.absoluteLocation { if let (absoluteRect, containerSize) = self.absoluteLocation {
placeholderNode.updateAbsoluteRect(absoluteRect, within: containerSize) placeholderNode.updateAbsoluteRect(absoluteRect, within: containerSize)
@ -175,6 +210,11 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
animationNode.updateLayout(size: imageSize) animationNode.updateLayout(size: imageSize)
} }
if let videoNode = self.videoNode {
videoNode.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
videoNode.updateLayout(size: imageSize)
}
if let placeholderNode = self.placeholderNode, let theme = self.theme { if let placeholderNode = self.placeholderNode, let theme = self.theme {
placeholderNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: [.roundedRect(rect: CGRect(origin: CGPoint(), size: imageSize), cornerRadius: 11.0)], horizontal: true, size: imageSize) placeholderNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: [.roundedRect(rect: CGRect(origin: CGPoint(), size: imageSize), cornerRadius: 11.0)], horizontal: true, size: imageSize)
placeholderNode.frame = self.imageNode.frame placeholderNode.frame = self.imageNode.frame

View File

@ -9,6 +9,7 @@ import StickerResources
import AnimatedStickerNode import AnimatedStickerNode
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
import ContextUI import ContextUI
import SoftwareVideo
public final class StickerPreviewPeekContent: PeekControllerContent { public final class StickerPreviewPeekContent: PeekControllerContent {
let account: Account let account: Account
@ -57,6 +58,7 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
private var textNode: ASTextNode private var textNode: ASTextNode
private var imageNode: ASImageNode private var imageNode: ASImageNode
private var animationNode: AnimatedStickerNode? private var animationNode: AnimatedStickerNode?
private var videoNode: VideoStickerNode?
private var containerLayout: (ContainerViewLayout, CGFloat)? private var containerLayout: (ContainerViewLayout, CGFloat)?
@ -79,6 +81,16 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil)) self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil))
} }
self.animationNode?.visibility = true self.animationNode?.visibility = true
case .video:
let videoNode = VideoStickerNode()
self.videoNode = videoNode
if let resource = item.resource as? TelegramMediaResource {
let dummyFile = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 1), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/webm", size: resource.size ?? 1, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [])])
videoNode.update(account: account, fileReference: .standalone(media: dummyFile))
}
videoNode.update(isPlaying: true)
} }
if case let .image(data) = item.content, let image = UIImage(data: data) { if case let .image(data) = item.content, let image = UIImage(data: data) {
self.imageNode.image = image self.imageNode.image = image
@ -89,7 +101,9 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
self.isUserInteractionEnabled = false self.isUserInteractionEnabled = false
if let animationNode = self.animationNode { if let videoNode = self.videoNode {
self.addSubnode(videoNode)
} else if let animationNode = self.animationNode {
self.addSubnode(animationNode) self.addSubnode(animationNode)
} else { } else {
self.addSubnode(self.imageNode) self.addSubnode(self.imageNode)
@ -108,7 +122,10 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
self.imageNode.frame = imageFrame self.imageNode.frame = imageFrame
if let animationNode = self.animationNode { if let videoNode = self.videoNode {
videoNode.frame = imageFrame
videoNode.updateLayout(size: imageFrame.size)
} else if let animationNode = self.animationNode {
animationNode.frame = imageFrame animationNode.frame = imageFrame
animationNode.updateLayout(size: imageFrame.size) animationNode.updateLayout(size: imageFrame.size)
} }

View File

@ -59,22 +59,29 @@ public class InvisibleInkDustNode: ASDisplayNode {
public var isRevealed = false public var isRevealed = false
private var exploding = false
public init(textNode: TextNode?) { public init(textNode: TextNode?) {
self.textNode = textNode self.textNode = textNode
self.emitterNode = ASDisplayNode() self.emitterNode = ASDisplayNode()
self.emitterNode.isUserInteractionEnabled = false
self.emitterNode.clipsToBounds = true self.emitterNode.clipsToBounds = true
self.textMaskNode = ASDisplayNode() self.textMaskNode = ASDisplayNode()
self.textMaskNode.isUserInteractionEnabled = false
self.textSpotNode = ASImageNode() self.textSpotNode = ASImageNode()
self.textSpotNode.contentMode = .scaleToFill self.textSpotNode.contentMode = .scaleToFill
self.textSpotNode.isUserInteractionEnabled = false
self.emitterMaskNode = ASDisplayNode() self.emitterMaskNode = ASDisplayNode()
self.emitterSpotNode = ASImageNode() self.emitterSpotNode = ASImageNode()
self.emitterSpotNode.contentMode = .scaleToFill self.emitterSpotNode.contentMode = .scaleToFill
self.emitterSpotNode.isUserInteractionEnabled = false
self.emitterMaskFillNode = ASDisplayNode() self.emitterMaskFillNode = ASDisplayNode()
self.emitterMaskFillNode.backgroundColor = .white self.emitterMaskFillNode.backgroundColor = .white
self.emitterMaskFillNode.isUserInteractionEnabled = false
super.init() super.init()
@ -135,7 +142,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap(_:)))) self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap(_:))))
} }
public func update(revealed: Bool) { public func update(revealed: Bool, animated: Bool = true) {
guard self.isRevealed != revealed, let textNode = self.textNode else { guard self.isRevealed != revealed, let textNode = self.textNode else {
return return
} }
@ -143,13 +150,18 @@ public class InvisibleInkDustNode: ASDisplayNode {
self.isRevealed = revealed self.isRevealed = revealed
if revealed { if revealed {
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear) let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .linear) : .immediate
transition.updateAlpha(node: self, alpha: 0.0) transition.updateAlpha(node: self, alpha: 0.0)
transition.updateAlpha(node: textNode, alpha: 1.0) transition.updateAlpha(node: textNode, alpha: 1.0)
} else { } else {
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .linear) let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .linear) : .immediate
transition.updateAlpha(node: self, alpha: 1.0) transition.updateAlpha(node: self, alpha: 1.0)
transition.updateAlpha(node: textNode, alpha: 0.0) transition.updateAlpha(node: textNode, alpha: 0.0)
if self.exploding {
self.exploding = false
self.emitterLayer?.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
}
} }
} }
@ -159,6 +171,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
} }
self.isRevealed = true self.isRevealed = true
self.exploding = true
let position = gestureRecognizer.location(in: self.view) let position = gestureRecognizer.location(in: self.view)
self.emitterLayer?.setValue(true, forKeyPath: "emitterBehaviors.fingerAttractor.enabled") self.emitterLayer?.setValue(true, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
@ -214,6 +227,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
} }
Queue.mainQueue().after(0.8 * UIView.animationDurationFactor()) { Queue.mainQueue().after(0.8 * UIView.animationDurationFactor()) {
self.exploding = false
self.emitterLayer?.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled") self.emitterLayer?.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
self.textSpotNode.layer.removeAllAnimations() self.textSpotNode.layer.removeAllAnimations()

View File

@ -61,7 +61,7 @@ public enum ShareControllerSubject {
case image([ImageRepresentationWithReference]) case image([ImageRepresentationWithReference])
case media(AnyMediaReference) case media(AnyMediaReference)
case mapMedia(TelegramMediaMap) case mapMedia(TelegramMediaMap)
case fromExternal(([PeerId], String, Account) -> Signal<ShareControllerExternalStatus, NoError>) case fromExternal(([PeerId], String, Account, Bool) -> Signal<ShareControllerExternalStatus, NoError>)
} }
private enum ExternalShareItem { private enum ExternalShareItem {
@ -503,7 +503,7 @@ public final class ShareController: ViewController {
}, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, fromForeignApp: self.fromForeignApp, forceTheme: self.forceTheme, fromPublicChannel: fromPublicChannel, segmentedValues: self.segmentedValues) }, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, fromForeignApp: self.fromForeignApp, forceTheme: self.forceTheme, fromPublicChannel: fromPublicChannel, segmentedValues: self.segmentedValues)
self.controllerNode.completed = self.completed self.controllerNode.completed = self.completed
self.controllerNode.present = { [weak self] c in self.controllerNode.present = { [weak self] c in
self?.presentInGlobalOverlay(c, with: nil) self?.present(c, in: .window(.root))
} }
self.controllerNode.dismiss = { [weak self] shared in self.controllerNode.dismiss = { [weak self] shared in
self?.dismissed?(shared) self?.dismissed?(shared)
@ -623,7 +623,7 @@ public final class ShareController: ViewController {
shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messagesToEnqueue)) shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messagesToEnqueue))
} }
case let .fromExternal(f): case let .fromExternal(f):
return f(peerIds, text, strongSelf.currentAccount) return f(peerIds, text, strongSelf.currentAccount, silently)
|> map { state -> ShareState in |> map { state -> ShareState in
switch state { switch state {
case .preparing: case .preparing:

View File

@ -191,29 +191,35 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
self.actionButtonNode.contextAction = { [weak self] node, gesture in self.actionButtonNode.contextAction = { [weak self] node, gesture in
if let strongSelf = self, let context = strongSelf.context, let node = node as? ContextReferenceContentNode { if let strongSelf = self, let context = strongSelf.context, let node = node as? ContextReferenceContentNode {
let presentationData = strongSelf.presentationData let presentationData = strongSelf.presentationData
let fromForeignApp = strongSelf.fromForeignApp
let items: Signal<ContextController.Items, NoError> = let items: Signal<ContextController.Items, NoError> =
strongSelf.showNames.get() strongSelf.showNames.get()
|> map { showNamesValue in |> map { showNamesValue in
return ContextController.Items(content: .list([ var items: [ContextMenuItem] = []
.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ForwardOptions_ShowSendersName, icon: { theme in if !fromForeignApp {
if showNamesValue { items.append(contentsOf: [
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) .action(ContextMenuActionItem(text: presentationData.strings.Conversation_ForwardOptions_ShowSendersName, icon: { theme in
} else { if showNamesValue {
return nil return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
} } else {
}, action: { _, _ in return nil
self?.showNames.set(true) }
})), }, action: { _, _ in
.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ForwardOptions_HideSendersName, icon: { theme in self?.showNames.set(true)
if !showNamesValue { })),
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) .action(ContextMenuActionItem(text: presentationData.strings.Conversation_ForwardOptions_HideSendersName, icon: { theme in
} else { if !showNamesValue {
return nil return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
} } else {
}, action: { _, _ in return nil
self?.showNames.set(false) }
})), }, action: { _, _ in
.separator, self?.showNames.set(false)
})),
.separator,
])
}
items.append(contentsOf: [
.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendSilently, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.contextMenu.primaryColor) }, action: { _, f in .action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendSilently, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default) f(.default)
if let strongSelf = self { if let strongSelf = self {
@ -225,8 +231,9 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
if let strongSelf = self { if let strongSelf = self {
strongSelf.send(showNames: showNamesValue, silently: false) strongSelf.send(showNames: showNamesValue, silently: false)
} }
})), }))
])) ])
return ContextController.Items(content: .list(items))
} }
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(ShareContextReferenceContentSource(sourceNode: node, customPosition: CGPoint(x: 0.0, y: -116.0))), items: items, gesture: gesture) let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(ShareContextReferenceContentSource(sourceNode: node, customPosition: CGPoint(x: 0.0, y: -116.0))), items: items, gesture: gesture)
contextController.immediateItemsTransitionAnimation = true contextController.immediateItemsTransitionAnimation = true

View File

@ -365,7 +365,7 @@ public func preparedShareItems(account: Account, to peerId: PeerId, dataItems: [
}) })
} }
public func sentShareItems(account: Account, to peerIds: [PeerId], items: [PreparedShareItemContent]) -> Signal<Float, Void> { public func sentShareItems(account: Account, to peerIds: [PeerId], items: [PreparedShareItemContent], silently: Bool) -> Signal<Float, Void> {
var messages: [EnqueueMessage] = [] var messages: [EnqueueMessage] = []
var groupingKey: Int64? var groupingKey: Int64?
var mediaTypes: (photo: Int, video: Int, music: Int, other: Int) = (0, 0, 0, 0) var mediaTypes: (photo: Int, video: Int, music: Int, other: Int) = (0, 0, 0, 0)
@ -401,15 +401,20 @@ public func sentShareItems(account: Account, to peerIds: [PeerId], items: [Prepa
groupingKey = Int64.random(in: Int64.min ... Int64.max) groupingKey = Int64.random(in: Int64.min ... Int64.max)
} }
var attributes: [MessageAttribute] = []
if silently {
attributes.append(NotificationInfoMessageAttribute(flags: .muted))
}
var mediaMessages: [EnqueueMessage] = [] var mediaMessages: [EnqueueMessage] = []
for item in items { for item in items {
switch item { switch item {
case let .text(text): case let .text(text):
messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil)) messages.append(.message(text: text, attributes: attributes, mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil))
case let .media(media): case let .media(media):
switch media { switch media {
case let .media(reference): case let .media(reference):
let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: reference, replyToMessageId: nil, localGroupingKey: groupingKey, correlationId: nil) let message: EnqueueMessage = .message(text: "", attributes: attributes, mediaReference: reference, replyToMessageId: nil, localGroupingKey: groupingKey, correlationId: nil)
messages.append(message) messages.append(message)
mediaMessages.append(message) mediaMessages.append(message)

View File

@ -723,7 +723,9 @@ class TabBarNode: ASDisplayNode {
let previousSelectedIndex = self.selectedIndex let previousSelectedIndex = self.selectedIndex
self.itemSelected(closestNode.0, longTap, [container.imageNode.imageNode, container.imageNode.textImageNode, container.badgeContainerNode]) self.itemSelected(closestNode.0, longTap, [container.imageNode.imageNode, container.imageNode.textImageNode, container.badgeContainerNode])
if previousSelectedIndex != closestNode.0 { if previousSelectedIndex != closestNode.0 {
container.imageNode.animationNode.play() if let selectedIndex = self.selectedIndex, let _ = self.tabBarItems[selectedIndex].item.animationName {
container.imageNode.animationNode.play()
}
} }
} }
} }

View File

@ -284,7 +284,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
} }
} }
switch message { switch message {
case let .message(_, _, _, replyToMessageId, _, _): case let .message(_, attributes, _, replyToMessageId, _, _):
if let replyToMessageId = replyToMessageId, replyToMessageId.peerId != peerId, let replyMessage = transaction.getMessage(replyToMessageId) { if let replyToMessageId = replyToMessageId, replyToMessageId.peerId != peerId, let replyMessage = transaction.getMessage(replyToMessageId) {
var canBeForwarded = true var canBeForwarded = true
if replyMessage.id.namespace != Namespaces.Message.Cloud { if replyMessage.id.namespace != Namespaces.Message.Cloud {
@ -297,7 +297,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
} }
} }
if canBeForwarded { if canBeForwarded {
updatedMessages.append((true, .forward(source: replyToMessageId, grouping: .none, attributes: [], correlationId: nil))) updatedMessages.append((true, .forward(source: replyToMessageId, grouping: .none, attributes: attributes, correlationId: nil)))
} }
} }
case let .forward(sourceId, _, _, _): case let .forward(sourceId, _, _, _):

View File

@ -33,7 +33,7 @@ private func uploadedSticker(postbox: Postbox, network: Network, resource: Media
} }
} }
func _internal_uploadSticker(account: Account, peer: Peer, resource: MediaResource, alt: String, dimensions: PixelDimensions, isAnimated: Bool) -> Signal<UploadStickerStatus, UploadStickerError> { func _internal_uploadSticker(account: Account, peer: Peer, resource: MediaResource, alt: String, dimensions: PixelDimensions, mimeType: String) -> Signal<UploadStickerStatus, UploadStickerError> {
guard let inputPeer = apiInputPeer(peer) else { guard let inputPeer = apiInputPeer(peer) else {
return .fail(.generic) return .fail(.generic)
} }
@ -53,7 +53,7 @@ func _internal_uploadSticker(account: Account, peer: Peer, resource: MediaResour
var attributes: [Api.DocumentAttribute] = [] var attributes: [Api.DocumentAttribute] = []
attributes.append(.documentAttributeSticker(flags: 0, alt: alt, stickerset: .inputStickerSetEmpty, maskCoords: nil)) attributes.append(.documentAttributeSticker(flags: 0, alt: alt, stickerset: .inputStickerSetEmpty, maskCoords: nil))
attributes.append(.documentAttributeImageSize(w: dimensions.width, h: dimensions.height)) 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))) return account.network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: Api.InputMedia.inputMediaUploadedDocument(flags: flags, file: file, thumb: nil, mimeType: mimeType, attributes: attributes, stickers: nil, ttlSeconds: nil)))
|> mapError { _ -> UploadStickerError in return .generic } |> mapError { _ -> UploadStickerError in return .generic }
|> mapToSignal { media -> Signal<UploadStickerStatus, UploadStickerError> in |> mapToSignal { media -> Signal<UploadStickerStatus, UploadStickerError> in
switch media { switch media {
@ -81,11 +81,13 @@ public struct ImportSticker {
public let resource: MediaResource public let resource: MediaResource
let emojis: [String] let emojis: [String]
public let dimensions: PixelDimensions public let dimensions: PixelDimensions
public let mimeType: String
public init(resource: MediaResource, emojis: [String], dimensions: PixelDimensions) { public init(resource: MediaResource, emojis: [String], dimensions: PixelDimensions, mimeType: String) {
self.resource = resource self.resource = resource
self.emojis = emojis self.emojis = emojis
self.dimensions = dimensions self.dimensions = dimensions
self.mimeType = mimeType
} }
} }
@ -94,7 +96,13 @@ public enum CreateStickerSetStatus {
case complete(StickerPackCollectionInfo, [StickerPackItem]) case complete(StickerPackCollectionInfo, [StickerPackItem])
} }
func _internal_createStickerSet(account: Account, title: String, shortName: String, stickers: [ImportSticker], thumbnail: ImportSticker?, isAnimated: Bool, software: String?) -> Signal<CreateStickerSetStatus, CreateStickerSetError> { public enum CreateStickerSetType {
case image
case animation
case video
}
func _internal_createStickerSet(account: Account, title: String, shortName: String, stickers: [ImportSticker], thumbnail: ImportSticker?, type: CreateStickerSetType, software: String?) -> Signal<CreateStickerSetStatus, CreateStickerSetError> {
return account.postbox.loadedPeerWithId(account.peerId) return account.postbox.loadedPeerWithId(account.peerId)
|> castError(CreateStickerSetError.self) |> castError(CreateStickerSetError.self)
|> mapToSignal { peer -> Signal<CreateStickerSetStatus, CreateStickerSetError> in |> mapToSignal { peer -> Signal<CreateStickerSetStatus, CreateStickerSetError> in
@ -108,9 +116,9 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri
} }
for sticker in stickers { for sticker in stickers {
if let resource = sticker.resource as? CloudDocumentMediaResource { if let resource = sticker.resource as? CloudDocumentMediaResource {
uploadStickers.append(.single(.complete(resource, isAnimated ? "application/x-tgsticker": "image/png"))) uploadStickers.append(.single(.complete(resource, sticker.mimeType)))
} else { } else {
uploadStickers.append(_internal_uploadSticker(account: account, peer: peer, resource: sticker.resource, alt: sticker.emojis.first ?? "", dimensions: sticker.dimensions, isAnimated: isAnimated) uploadStickers.append(_internal_uploadSticker(account: account, peer: peer, resource: sticker.resource, alt: sticker.emojis.first ?? "", dimensions: sticker.dimensions, mimeType: sticker.mimeType)
|> mapError { _ -> CreateStickerSetError in |> mapError { _ -> CreateStickerSetError in
return .generic return .generic
}) })
@ -126,7 +134,7 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri
} }
if resources.count == stickers.count { if resources.count == stickers.count {
var flags: Int32 = 0 var flags: Int32 = 0
if isAnimated { if case .animation = type {
flags |= (1 << 1) flags |= (1 << 1)
} }
var inputStickers: [Api.InputStickerSetItem] = [] var inputStickers: [Api.InputStickerSetItem] = []

View File

@ -69,12 +69,12 @@ public extension TelegramEngine {
return _internal_stickerPacksAttachedToMedia(account: self.account, media: media) return _internal_stickerPacksAttachedToMedia(account: self.account, media: media)
} }
public func uploadSticker(peer: Peer, resource: MediaResource, alt: String, dimensions: PixelDimensions, isAnimated: Bool) -> Signal<UploadStickerStatus, UploadStickerError> { public func uploadSticker(peer: Peer, resource: MediaResource, alt: String, dimensions: PixelDimensions, mimeType: String) -> Signal<UploadStickerStatus, UploadStickerError> {
return _internal_uploadSticker(account: self.account, peer: peer, resource: resource, alt: alt, dimensions: dimensions, isAnimated: isAnimated) return _internal_uploadSticker(account: self.account, peer: peer, resource: resource, alt: alt, dimensions: dimensions, mimeType: mimeType)
} }
public func createStickerSet(title: String, shortName: String, stickers: [ImportSticker], thumbnail: ImportSticker?, isAnimated: Bool, software: String?) -> Signal<CreateStickerSetStatus, CreateStickerSetError> { public func createStickerSet(title: String, shortName: String, stickers: [ImportSticker], thumbnail: ImportSticker?, type: CreateStickerSetType, software: String?) -> Signal<CreateStickerSetStatus, CreateStickerSetError> {
return _internal_createStickerSet(account: self.account, title: title, shortName: shortName, stickers: stickers, thumbnail: thumbnail, isAnimated: isAnimated, software: software) return _internal_createStickerSet(account: self.account, title: title, shortName: shortName, stickers: stickers, thumbnail: thumbnail, type: type, software: software)
} }
public func getStickerSetShortNameSuggestion(title: String) -> Signal<String?, NoError> { public func getStickerSetShortNameSuggestion(title: String) -> Signal<String?, NoError> {

View File

@ -52,6 +52,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
private let lineNode: AnimatedNavigationStripeNode private let lineNode: AnimatedNavigationStripeNode
private let titleNode: AnimatedCountLabelNode private let titleNode: AnimatedCountLabelNode
private let textNode: TextNode private let textNode: TextNode
private var spoilerTextNode: TextNode?
private var dustNode: InvisibleInkDustNode? private var dustNode: InvisibleInkDustNode?
private let imageNode: TransformImageNode private let imageNode: TransformImageNode
@ -71,6 +72,15 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
private let queue = Queue() private let queue = Queue()
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let containerResult = self.contentTextContainer.hitTest(point.offsetBy(dx: -self.contentTextContainer.frame.minX, dy: -self.contentTextContainer.frame.minY), with: event)
if containerResult?.asyncdisplaykit_node === self.dustNode, self.dustNode?.isRevealed == false {
return containerResult
}
let result = super.hitTest(point, with: event)
return result
}
init(context: AccountContext) { init(context: AccountContext) {
self.context = context self.context = context
@ -270,6 +280,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
self.currentMessage = interfaceState.pinnedMessage self.currentMessage = interfaceState.pinnedMessage
if let currentMessage = self.currentMessage, let currentLayout = self.currentLayout { if let currentMessage = self.currentMessage, let currentLayout = self.currentLayout {
self.dustNode?.update(revealed: false, animated: false)
self.enqueueTransition(width: currentLayout.0, panelHeight: panelHeight, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, animation: messageUpdatedAnimation, pinnedMessage: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, dateTimeFormat: interfaceState.dateTimeFormat, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil, isReplyThread: isReplyThread) self.enqueueTransition(width: currentLayout.0, panelHeight: panelHeight, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, animation: messageUpdatedAnimation, pinnedMessage: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, dateTimeFormat: interfaceState.dateTimeFormat, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil, isReplyThread: isReplyThread)
} }
} }
@ -352,6 +363,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
let makeTitleLayout = self.titleNode.asyncLayout() let makeTitleLayout = self.titleNode.asyncLayout()
let makeTextLayout = TextNode.asyncLayout(self.textNode) let makeTextLayout = TextNode.asyncLayout(self.textNode)
let makeSpoilerTextLayout = TextNode.asyncLayout(self.spoilerTextNode)
let imageNodeLayout = self.imageNode.asyncLayout() let imageNodeLayout = self.imageNode.asyncLayout()
let previousMediaReference = self.previousMediaReference let previousMediaReference = self.previousMediaReference
@ -476,7 +488,15 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
messageText = NSAttributedString(string: foldLineBreaks(textString), font: textFont, textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor) messageText = NSAttributedString(string: foldLineBreaks(textString), font: textFont, textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor)
} }
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0))) let textConstrainedSize = CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude)
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0)))
let spoilerTextLayoutAndApply: (TextNodeLayout, () -> TextNode)?
if !textLayout.spoilers.isEmpty {
spoilerTextLayoutAndApply = makeSpoilerTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0), displaySpoilers: true))
} else {
spoilerTextLayoutAndApply = nil
}
Queue.mainQueue().async { Queue.mainQueue().async {
if let strongSelf = self { if let strongSelf = self {
@ -485,28 +505,47 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
strongSelf.previousMediaReference = updatedMediaReference strongSelf.previousMediaReference = updatedMediaReference
animationTransition.updateFrameAdditive(node: strongSelf.contentTextContainer, frame: CGRect(origin: CGPoint(x: contentLeftInset + textLineInset, y: 0.0), size: CGSize())) animationTransition.updateFrameAdditive(node: strongSelf.contentTextContainer, frame: CGRect(origin: CGPoint(x: contentLeftInset + textLineInset, y: 0.0), size: CGSize(width: width, height: panelHeight)))
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 5.0), size: titleLayout.size) strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 5.0), size: titleLayout.size)
let textFrame = CGRect(origin: CGPoint(x: 0.0, y: 23.0), size: textLayout.size) let textFrame = CGRect(origin: CGPoint(x: 0.0, y: 23.0), size: textLayout.size)
strongSelf.textNode.frame = textFrame strongSelf.textNode.frame = textFrame
if !textLayout.spoilers.isEmpty {
if let (_, spoilerTextApply) = spoilerTextLayoutAndApply {
let spoilerTextNode = spoilerTextApply()
if strongSelf.spoilerTextNode == nil {
spoilerTextNode.alpha = 0.0
spoilerTextNode.isUserInteractionEnabled = false
spoilerTextNode.contentMode = .topLeft
spoilerTextNode.contentsScale = UIScreenScale
spoilerTextNode.displaysAsynchronously = false
strongSelf.contentTextContainer.insertSubnode(spoilerTextNode, aboveSubnode: strongSelf.textNode)
strongSelf.spoilerTextNode = spoilerTextNode
}
strongSelf.spoilerTextNode?.frame = textFrame
let dustNode: InvisibleInkDustNode let dustNode: InvisibleInkDustNode
if let current = strongSelf.dustNode { if let current = strongSelf.dustNode {
dustNode = current dustNode = current
} else { } else {
dustNode = InvisibleInkDustNode(textNode: nil) dustNode = InvisibleInkDustNode(textNode: spoilerTextNode)
dustNode.isUserInteractionEnabled = false
strongSelf.dustNode = dustNode strongSelf.dustNode = dustNode
strongSelf.contentTextContainer.insertSubnode(dustNode, aboveSubnode: strongSelf.textNode) strongSelf.contentTextContainer.insertSubnode(dustNode, aboveSubnode: spoilerTextNode)
} }
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
dustNode.update(size: dustNode.frame.size, color: theme.chat.inputPanel.secondaryTextColor, textColor: theme.chat.inputPanel.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) dustNode.update(size: dustNode.frame.size, color: theme.chat.inputPanel.secondaryTextColor, textColor: theme.chat.inputPanel.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
} else if let dustNode = strongSelf.dustNode { } else if let spoilerTextNode = strongSelf.spoilerTextNode {
dustNode.removeFromSupernode() strongSelf.spoilerTextNode = nil
strongSelf.dustNode = nil spoilerTextNode.removeFromSupernode()
if let dustNode = strongSelf.dustNode {
strongSelf.dustNode = nil
dustNode.removeFromSupernode()
}
} }
let lineFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: 0.0), size: CGSize(width: 2.0, height: panelHeight)) let lineFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: 0.0), size: CGSize(width: 2.0, height: panelHeight))

View File

@ -2167,7 +2167,13 @@ private class MessageContentNode: ASDisplayNode, ContentNode {
} }
} }
func renderVideo(context: AccountContext, backgroundImage: UIImage, media: TelegramMediaFile, videoFrame: CGRect, completion: @escaping (URL?) -> Void) { private enum RenderVideoResult {
case progress(Float)
case completion(URL)
case error
}
private func renderVideo(context: AccountContext, backgroundImage: UIImage, media: TelegramMediaFile, videoFrame: CGRect, completion: @escaping (URL?) -> Void) {
let _ = (fetchMediaData(context: context, postbox: context.account.postbox, mediaReference: AnyMediaReference.standalone(media: media)) let _ = (fetchMediaData(context: context, postbox: context.account.postbox, mediaReference: AnyMediaReference.standalone(media: media))
|> deliverOnMainQueue).start(next: { value, isImage in |> deliverOnMainQueue).start(next: { value, isImage in
guard case let .data(data) = value, data.complete else { guard case let .data(data) = value, data.complete else {

View File

@ -227,7 +227,7 @@ func presentedLegacyShortcutCamera(context: AccountContext, saveCapturedMedia: B
nativeGenerator(_1, _2, _3, nil) nativeGenerator(_1, _2, _3, nil)
}) })
if let parentController = parentController { if let parentController = parentController {
parentController.present(ShareController(context: context, subject: .fromExternal({ peerIds, text, account in parentController.present(ShareController(context: context, subject: .fromExternal({ peerIds, text, account, silently in
return legacyAssetPickerEnqueueMessages(account: account, signals: signals!) return legacyAssetPickerEnqueueMessages(account: account, signals: signals!)
|> `catch` { _ -> Signal<[LegacyAssetPickerEnqueueMessage], NoError> in |> `catch` { _ -> Signal<[LegacyAssetPickerEnqueueMessage], NoError> in
return .single([]) return .single([])

View File

@ -5,6 +5,7 @@ import AccountContext
import TextFormat import TextFormat
import UIKit import UIKit
import AppBundle import AppBundle
import TelegramStringFormatting
enum PeerInfoScreenLabeledValueTextColor { enum PeerInfoScreenLabeledValueTextColor {
case primary case primary
@ -284,16 +285,23 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
self.labelNode.attributedText = NSAttributedString(string: item.label, font: Font.regular(14.0), textColor: presentationData.theme.list.itemPrimaryTextColor) self.labelNode.attributedText = NSAttributedString(string: item.label, font: Font.regular(14.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
var text = item.text
let maxNumberOfLines: Int
switch item.textBehavior { switch item.textBehavior {
case .singleLine: case .singleLine:
maxNumberOfLines = 1
self.textNode.maximumNumberOfLines = maxNumberOfLines
self.textNode.cutout = nil self.textNode.cutout = nil
self.textNode.maximumNumberOfLines = 1
self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: textColorValue) self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: textColorValue)
case let .multiLine(maxLines, enabledEntities): case let .multiLine(maxLines, enabledEntities):
self.textNode.maximumNumberOfLines = self.isExpanded ? maxLines : 3 if !self.isExpanded {
// self.textNode.cutout = self.isExpanded ? nil : TextNodeCutout(bottomRight: CGSize(width: expandSize.width + 4.0, height: expandSize.height)) text = trimToLineCount(text, lineCount: 3)
}
maxNumberOfLines = self.isExpanded ? maxLines : 3
self.textNode.maximumNumberOfLines = maxNumberOfLines
if enabledEntities.isEmpty { if enabledEntities.isEmpty {
self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: textColorValue) self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(17.0), textColor: textColorValue)
} else { } else {
let fontSize: CGFloat = 17.0 let fontSize: CGFloat = 17.0
@ -304,8 +312,8 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
let boldItalicFont = Font.semiboldItalic(fontSize) let boldItalicFont = Font.semiboldItalic(fontSize)
let titleFixedFont = Font.monospace(fontSize) let titleFixedFont = Font.monospace(fontSize)
let entities = generateTextEntities(item.text, enabledTypes: enabledEntities) let entities = generateTextEntities(text, enabledTypes: enabledEntities)
self.textNode.attributedText = stringWithAppliedEntities(item.text, entities: entities, baseColor: textColorValue, linkColor: presentationData.theme.list.itemAccentColor, baseFont: baseFont, linkFont: linkFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: titleFixedFont, blockQuoteFont: baseFont) self.textNode.attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: textColorValue, linkColor: presentationData.theme.list.itemAccentColor, baseFont: baseFont, linkFont: linkFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: titleFixedFont, blockQuoteFont: baseFont)
} }
} }
@ -328,7 +336,14 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
let textLayout = self.textNode.updateLayoutInfo(CGSize(width: width - sideInset * 2.0 - additionalSideInset, height: .greatestFiniteMagnitude)) let textLayout = self.textNode.updateLayoutInfo(CGSize(width: width - sideInset * 2.0 - additionalSideInset, height: .greatestFiniteMagnitude))
let textSize = textLayout.size let textSize = textLayout.size
if case .multiLine = item.textBehavior, textLayout.truncated, !self.isExpanded { var displayMore = false
if !self.isExpanded {
if textLayout.truncated || text.count < item.text.count {
displayMore = true
}
}
if case .multiLine = item.textBehavior, displayMore {
self.expandBackgroundNode.isHidden = false self.expandBackgroundNode.isHidden = false
self.expandNode.isHidden = false self.expandNode.isHidden = false
self.expandButonNode.isHidden = false self.expandButonNode.isHidden = false

View File

@ -7213,7 +7213,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
if accountTabBarAvatarBadge > 0 { if accountTabBarAvatarBadge > 0 {
otherAccountsBadge = compactNumericCountString(Int(accountTabBarAvatarBadge), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) otherAccountsBadge = compactNumericCountString(Int(accountTabBarAvatarBadge), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
} }
return (presentationData.strings.Settings_Title, accountTabBarAvatar?.0 ?? icon, accountTabBarAvatar?.1 ?? icon, notificationsWarning || phoneNumberWarning || passwordWarning ? "!" : otherAccountsBadge, accountTabBarAvatar != nil) return (presentationData.strings.Settings_Title, accountTabBarAvatar?.0 ?? icon, accountTabBarAvatar?.1 ?? icon, notificationsWarning || phoneNumberWarning || passwordWarning ? "!" : otherAccountsBadge, accountTabBarAvatar != nil || presentationData.reduceMotion)
} }
self.tabBarItemDisposable = (tabBarItem |> deliverOnMainQueue).start(next: { [weak self] title, image, selectedImage, badgeValue, isAvatar in self.tabBarItemDisposable = (tabBarItem |> deliverOnMainQueue).start(next: { [weak self] title, image, selectedImage, badgeValue, isAvatar in

View File

@ -348,8 +348,8 @@ public class ShareRootControllerImpl {
} |> runOn(Queue.mainQueue()) } |> runOn(Queue.mainQueue())
} }
let sentItems: ([PeerId], [PreparedShareItemContent], Account) -> Signal<ShareControllerExternalStatus, NoError> = { peerIds, contents, account in let sentItems: ([PeerId], [PreparedShareItemContent], Account, Bool) -> Signal<ShareControllerExternalStatus, NoError> = { peerIds, contents, account, silently in
let sentItems = sentShareItems(account: account, to: peerIds, items: contents) let sentItems = sentShareItems(account: account, to: peerIds, items: contents, silently: silently)
|> `catch` { _ -> Signal< |> `catch` { _ -> Signal<
Float, NoError> in Float, NoError> in
return .complete() return .complete()
@ -361,7 +361,7 @@ public class ShareRootControllerImpl {
|> then(.single(.done)) |> then(.single(.done))
} }
let shareController = ShareController(context: context, subject: .fromExternal({ peerIds, additionalText, account in let shareController = ShareController(context: context, subject: .fromExternal({ peerIds, additionalText, account, silently in
if let strongSelf = self, let inputItems = strongSelf.getExtensionContext()?.inputItems, !inputItems.isEmpty, !peerIds.isEmpty { if let strongSelf = self, let inputItems = strongSelf.getExtensionContext()?.inputItems, !inputItems.isEmpty, !peerIds.isEmpty {
let rawSignals = TGItemProviderSignals.itemSignals(forInputItems: inputItems)! let rawSignals = TGItemProviderSignals.itemSignals(forInputItems: inputItems)!
return preparedShareItems(account: account, to: peerIds[0], dataItems: rawSignals, additionalText: additionalText) return preparedShareItems(account: account, to: peerIds[0], dataItems: rawSignals, additionalText: additionalText)
@ -381,10 +381,10 @@ public class ShareRootControllerImpl {
case let .userInteractionRequired(value): case let .userInteractionRequired(value):
return requestUserInteraction(value) return requestUserInteraction(value)
|> mapToSignal { contents -> Signal<ShareControllerExternalStatus, NoError> in |> mapToSignal { contents -> Signal<ShareControllerExternalStatus, NoError> in
return sentItems(peerIds, contents, account) return sentItems(peerIds, contents, account, silently)
} }
case let .done(contents): case let .done(contents):
return sentItems(peerIds, contents, account) return sentItems(peerIds, contents, account, silently)
} }
} }
} else { } else {