mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various Improvements
This commit is contained in:
parent
241f78ddd9
commit
5154356e38
@ -115,8 +115,10 @@ public final class CallListController: TelegramBaseController {
|
||||
self.tabBarItem.title = self.presentationData.strings.Calls_TabTitle
|
||||
self.tabBarItem.image = icon
|
||||
self.tabBarItem.selectedImage = icon
|
||||
if !self.presentationData.reduceMotion {
|
||||
self.tabBarItem.animationName = "TabCalls"
|
||||
}
|
||||
}
|
||||
|
||||
self.segmentedTitleView.indexUpdated = { [weak self] index in
|
||||
if let strongSelf = self {
|
||||
@ -166,6 +168,11 @@ public final class CallListController: TelegramBaseController {
|
||||
self.segmentedTitleView.index = index
|
||||
|
||||
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)
|
||||
switch self.mode {
|
||||
case .tab:
|
||||
|
@ -2,6 +2,7 @@ import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import AVFoundation
|
||||
import SwiftSignalKit
|
||||
|
||||
private final class CameraPreviewNodeLayerNullAction: NSObject, CAAction {
|
||||
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
|
||||
@ -17,15 +18,24 @@ private final class CameraPreviewNodeLayer: AVSampleBufferDisplayLayer {
|
||||
public final class CameraPreviewNode: ASDisplayNode {
|
||||
private var displayLayer: AVSampleBufferDisplayLayer
|
||||
|
||||
private let fadeNode: ASDisplayNode
|
||||
private var fadedIn = false
|
||||
|
||||
public override init() {
|
||||
self.displayLayer = AVSampleBufferDisplayLayer()
|
||||
self.displayLayer.videoGravity = .resizeAspectFill
|
||||
|
||||
self.fadeNode = ASDisplayNode()
|
||||
self.fadeNode.backgroundColor = .black
|
||||
self.fadeNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
|
||||
self.layer.addSublayer(self.displayLayer)
|
||||
|
||||
self.addSubnode(self.fadeNode)
|
||||
}
|
||||
|
||||
func prepare() {
|
||||
@ -36,6 +46,14 @@ public final class CameraPreviewNode: ASDisplayNode {
|
||||
|
||||
func enqueue(_ sampleBuffer: CMSampleBuffer) {
|
||||
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() {
|
||||
@ -46,5 +64,6 @@ public final class CameraPreviewNode: ASDisplayNode {
|
||||
self.displayLayer.setAffineTransform(transform)
|
||||
|
||||
self.displayLayer.frame = self.bounds
|
||||
self.fadeNode.frame = self.bounds
|
||||
}
|
||||
}
|
||||
|
@ -206,8 +206,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
self.tabBarItem.image = icon
|
||||
self.tabBarItem.selectedImage = icon
|
||||
if !self.presentationData.reduceMotion {
|
||||
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))
|
||||
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)
|
||||
backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back
|
||||
self.navigationItem.backBarButtonItem = backBarButtonItem
|
||||
|
||||
if !self.presentationData.reduceMotion {
|
||||
self.tabBarItem.animationName = "TabChats"
|
||||
} else {
|
||||
self.tabBarItem.animationName = nil
|
||||
}
|
||||
} else {
|
||||
let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back
|
||||
|
@ -192,7 +192,9 @@ public class ContactsController: ViewController {
|
||||
|
||||
self.tabBarItem.image = icon
|
||||
self.tabBarItem.selectedImage = icon
|
||||
if !self.presentationData.reduceMotion {
|
||||
self.tabBarItem.animationName = "TabContacts"
|
||||
}
|
||||
|
||||
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.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)
|
||||
if self.navigationItem.rightBarButtonItem != nil {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationAddIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.addPressed))
|
||||
|
@ -143,7 +143,7 @@ final class GameControllerNode: ViewControllerTracingNode {
|
||||
if eventName == "share_game" || eventName == "share_score" {
|
||||
if let (botPeer, gameName) = self.shareData(), let addressName = botPeer.addressName, !addressName.isEmpty, !gameName.isEmpty {
|
||||
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 {
|
||||
let signals = peerIds.map { TelegramEngine(account: account).messages.forwardGameWithScore(messageId: strongSelf.message.id, to: $0, as: nil) }
|
||||
return .single(.preparing)
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
enum StickerVerificationStatus {
|
||||
case loading
|
||||
@ -9,10 +10,39 @@ enum StickerVerificationStatus {
|
||||
}
|
||||
|
||||
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 enum Content {
|
||||
case image(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 {
|
||||
@ -32,9 +62,7 @@ public class ImportStickerPack {
|
||||
|
||||
var data: Data {
|
||||
switch self.content {
|
||||
case let .image(data):
|
||||
return data
|
||||
case let .animation(data):
|
||||
case let .image(data), let .animation(data), let .video(data, _):
|
||||
return data
|
||||
}
|
||||
}
|
||||
@ -49,7 +77,7 @@ public class ImportStickerPack {
|
||||
}
|
||||
|
||||
public let software: String
|
||||
public let isAnimated: Bool
|
||||
public let type: StickerPackType
|
||||
public let thumbnail: Sticker?
|
||||
public let stickers: [Sticker]
|
||||
|
||||
@ -59,20 +87,33 @@ public class ImportStickerPack {
|
||||
}
|
||||
self.software = json["software"] as? String ?? ""
|
||||
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? {
|
||||
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 {
|
||||
if case .image = type {
|
||||
content = .image(data)
|
||||
}
|
||||
case "application/x-tgsticker":
|
||||
if isAnimated {
|
||||
if case .animation = type {
|
||||
content = .animation(data)
|
||||
}
|
||||
case "video/webm", "image/webp", "image/gif":
|
||||
if case .video = type {
|
||||
content = .video(data, mimeType)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -79,7 +79,8 @@ public final class ImportStickerPackController: ViewController, StandalonePresen
|
||||
Queue.mainQueue().after(0.1) {
|
||||
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)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let strongSelf = self else {
|
||||
@ -89,13 +90,13 @@ public final class ImportStickerPackController: ViewController, StandalonePresen
|
||||
var signals: [Signal<(UUID, StickerVerificationStatus, MediaResource?), NoError>] = []
|
||||
for sticker in strongSelf.stickerPack.stickers {
|
||||
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
|
||||
switch result {
|
||||
case .progress:
|
||||
return (sticker.uuid, .loading, nil)
|
||||
case let .complete(resource, mimeType):
|
||||
if mimeType == "application/x-tgsticker" {
|
||||
if ["application/x-tgsticker", "video/webm"].contains(mimeType) {
|
||||
return (sticker.uuid, .verified, resource)
|
||||
} else {
|
||||
return (sticker.uuid, .declined, nil)
|
||||
|
@ -607,9 +607,9 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
if let localResource = item.stickerItem.resource {
|
||||
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 {
|
||||
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?
|
||||
@ -620,7 +620,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
}
|
||||
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
|
||||
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
|
||||
@ -631,13 +631,17 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
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
|
||||
if let strongSelf = self {
|
||||
if case let .complete(info, items) = status {
|
||||
if let (_, _, count) = strongSelf.progress {
|
||||
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 {
|
||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
}
|
||||
@ -675,7 +679,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
Queue.mainQueue().after(1.0) {
|
||||
var firstItem: StickerPackItem?
|
||||
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
|
||||
if case .info = action {
|
||||
@ -781,11 +785,16 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
item.resource = 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
|
||||
|
||||
if stickerPack.isAnimated {
|
||||
if case .image = stickerPack.type {
|
||||
} else {
|
||||
self.stickerPackReady = stickerPack.stickers.count == (verifiedStickers.count + declinedStickers.count) && updatedItems.count > 0
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import TelegramPresentationData
|
||||
import ShimmerEffect
|
||||
import SoftwareVideo
|
||||
|
||||
final class StickerPackPreviewInteraction {
|
||||
var previewedItem: ImportStickerPack.Sticker?
|
||||
@ -60,6 +61,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
||||
private var isVerified: Bool?
|
||||
private let imageNode: ASImageNode
|
||||
private var animationNode: AnimatedStickerNode?
|
||||
private var videoNode: VideoStickerNode?
|
||||
private var placeholderNode: ShimmerEffectNode?
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
@ -145,6 +147,39 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
||||
let placeholderNode = ShimmerEffectNode()
|
||||
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)
|
||||
if let (absoluteRect, containerSize) = self.absoluteLocation {
|
||||
placeholderNode.updateAbsoluteRect(absoluteRect, within: containerSize)
|
||||
@ -175,6 +210,11 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
||||
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 {
|
||||
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
|
||||
|
@ -9,6 +9,7 @@ import StickerResources
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import ContextUI
|
||||
import SoftwareVideo
|
||||
|
||||
public final class StickerPreviewPeekContent: PeekControllerContent {
|
||||
let account: Account
|
||||
@ -57,6 +58,7 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
|
||||
private var textNode: ASTextNode
|
||||
private var imageNode: ASImageNode
|
||||
private var animationNode: AnimatedStickerNode?
|
||||
private var videoNode: VideoStickerNode?
|
||||
|
||||
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?.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) {
|
||||
self.imageNode.image = image
|
||||
@ -89,7 +101,9 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
|
||||
|
||||
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)
|
||||
} else {
|
||||
self.addSubnode(self.imageNode)
|
||||
@ -108,7 +122,10 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
|
||||
|
||||
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.updateLayout(size: imageFrame.size)
|
||||
}
|
||||
|
@ -59,22 +59,29 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
||||
|
||||
public var isRevealed = false
|
||||
|
||||
private var exploding = false
|
||||
|
||||
public init(textNode: TextNode?) {
|
||||
self.textNode = textNode
|
||||
|
||||
self.emitterNode = ASDisplayNode()
|
||||
self.emitterNode.isUserInteractionEnabled = false
|
||||
self.emitterNode.clipsToBounds = true
|
||||
|
||||
self.textMaskNode = ASDisplayNode()
|
||||
self.textMaskNode.isUserInteractionEnabled = false
|
||||
self.textSpotNode = ASImageNode()
|
||||
self.textSpotNode.contentMode = .scaleToFill
|
||||
self.textSpotNode.isUserInteractionEnabled = false
|
||||
|
||||
self.emitterMaskNode = ASDisplayNode()
|
||||
self.emitterSpotNode = ASImageNode()
|
||||
self.emitterSpotNode.contentMode = .scaleToFill
|
||||
self.emitterSpotNode.isUserInteractionEnabled = false
|
||||
|
||||
self.emitterMaskFillNode = ASDisplayNode()
|
||||
self.emitterMaskFillNode.backgroundColor = .white
|
||||
self.emitterMaskFillNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
@ -135,7 +142,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -143,13 +150,18 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
||||
self.isRevealed = 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: textNode, alpha: 1.0)
|
||||
} 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: 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.exploding = true
|
||||
|
||||
let position = gestureRecognizer.location(in: self.view)
|
||||
self.emitterLayer?.setValue(true, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||
@ -214,6 +227,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(0.8 * UIView.animationDurationFactor()) {
|
||||
self.exploding = false
|
||||
self.emitterLayer?.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||
self.textSpotNode.layer.removeAllAnimations()
|
||||
|
||||
|
@ -61,7 +61,7 @@ public enum ShareControllerSubject {
|
||||
case image([ImageRepresentationWithReference])
|
||||
case media(AnyMediaReference)
|
||||
case mapMedia(TelegramMediaMap)
|
||||
case fromExternal(([PeerId], String, Account) -> Signal<ShareControllerExternalStatus, NoError>)
|
||||
case fromExternal(([PeerId], String, Account, Bool) -> Signal<ShareControllerExternalStatus, NoError>)
|
||||
}
|
||||
|
||||
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)
|
||||
self.controllerNode.completed = self.completed
|
||||
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?.dismissed?(shared)
|
||||
@ -623,7 +623,7 @@ public final class ShareController: ViewController {
|
||||
shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messagesToEnqueue))
|
||||
}
|
||||
case let .fromExternal(f):
|
||||
return f(peerIds, text, strongSelf.currentAccount)
|
||||
return f(peerIds, text, strongSelf.currentAccount, silently)
|
||||
|> map { state -> ShareState in
|
||||
switch state {
|
||||
case .preparing:
|
||||
|
@ -191,10 +191,13 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
self.actionButtonNode.contextAction = { [weak self] node, gesture in
|
||||
if let strongSelf = self, let context = strongSelf.context, let node = node as? ContextReferenceContentNode {
|
||||
let presentationData = strongSelf.presentationData
|
||||
let fromForeignApp = strongSelf.fromForeignApp
|
||||
let items: Signal<ContextController.Items, NoError> =
|
||||
strongSelf.showNames.get()
|
||||
|> map { showNamesValue in
|
||||
return ContextController.Items(content: .list([
|
||||
var items: [ContextMenuItem] = []
|
||||
if !fromForeignApp {
|
||||
items.append(contentsOf: [
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ForwardOptions_ShowSendersName, icon: { theme in
|
||||
if showNamesValue {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
|
||||
@ -214,6 +217,9 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
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
|
||||
f(.default)
|
||||
if let strongSelf = self {
|
||||
@ -225,8 +231,9 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
if let strongSelf = self {
|
||||
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)
|
||||
contextController.immediateItemsTransitionAnimation = true
|
||||
|
@ -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 groupingKey: Int64?
|
||||
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)
|
||||
}
|
||||
|
||||
var attributes: [MessageAttribute] = []
|
||||
if silently {
|
||||
attributes.append(NotificationInfoMessageAttribute(flags: .muted))
|
||||
}
|
||||
|
||||
var mediaMessages: [EnqueueMessage] = []
|
||||
for item in items {
|
||||
switch item {
|
||||
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):
|
||||
switch media {
|
||||
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)
|
||||
mediaMessages.append(message)
|
||||
|
||||
|
@ -723,9 +723,11 @@ class TabBarNode: ASDisplayNode {
|
||||
let previousSelectedIndex = self.selectedIndex
|
||||
self.itemSelected(closestNode.0, longTap, [container.imageNode.imageNode, container.imageNode.textImageNode, container.badgeContainerNode])
|
||||
if previousSelectedIndex != closestNode.0 {
|
||||
if let selectedIndex = self.selectedIndex, let _ = self.tabBarItems[selectedIndex].item.animationName {
|
||||
container.imageNode.animationNode.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -284,7 +284,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
}
|
||||
}
|
||||
switch message {
|
||||
case let .message(_, _, _, replyToMessageId, _, _):
|
||||
case let .message(_, attributes, _, replyToMessageId, _, _):
|
||||
if let replyToMessageId = replyToMessageId, replyToMessageId.peerId != peerId, let replyMessage = transaction.getMessage(replyToMessageId) {
|
||||
var canBeForwarded = true
|
||||
if replyMessage.id.namespace != Namespaces.Message.Cloud {
|
||||
@ -297,7 +297,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
}
|
||||
}
|
||||
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, _, _, _):
|
||||
|
@ -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 {
|
||||
return .fail(.generic)
|
||||
}
|
||||
@ -53,7 +53,7 @@ func _internal_uploadSticker(account: Account, peer: Peer, resource: MediaResour
|
||||
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)))
|
||||
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 }
|
||||
|> mapToSignal { media -> Signal<UploadStickerStatus, UploadStickerError> in
|
||||
switch media {
|
||||
@ -81,11 +81,13 @@ public struct ImportSticker {
|
||||
public let resource: MediaResource
|
||||
let emojis: [String]
|
||||
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.emojis = emojis
|
||||
self.dimensions = dimensions
|
||||
self.mimeType = mimeType
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +96,13 @@ public enum CreateStickerSetStatus {
|
||||
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)
|
||||
|> castError(CreateStickerSetError.self)
|
||||
|> mapToSignal { peer -> Signal<CreateStickerSetStatus, CreateStickerSetError> in
|
||||
@ -108,9 +116,9 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri
|
||||
}
|
||||
for sticker in stickers {
|
||||
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 {
|
||||
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
|
||||
return .generic
|
||||
})
|
||||
@ -126,7 +134,7 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri
|
||||
}
|
||||
if resources.count == stickers.count {
|
||||
var flags: Int32 = 0
|
||||
if isAnimated {
|
||||
if case .animation = type {
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
var inputStickers: [Api.InputStickerSetItem] = []
|
||||
|
@ -69,12 +69,12 @@ public extension TelegramEngine {
|
||||
return _internal_stickerPacksAttachedToMedia(account: self.account, media: media)
|
||||
}
|
||||
|
||||
public func uploadSticker(peer: Peer, resource: MediaResource, alt: String, dimensions: PixelDimensions, isAnimated: Bool) -> Signal<UploadStickerStatus, UploadStickerError> {
|
||||
return _internal_uploadSticker(account: self.account, peer: peer, resource: resource, alt: alt, dimensions: dimensions, isAnimated: isAnimated)
|
||||
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, mimeType: mimeType)
|
||||
}
|
||||
|
||||
public func createStickerSet(title: String, shortName: String, stickers: [ImportSticker], thumbnail: ImportSticker?, isAnimated: Bool, software: String?) -> Signal<CreateStickerSetStatus, CreateStickerSetError> {
|
||||
return _internal_createStickerSet(account: self.account, title: title, shortName: shortName, stickers: stickers, thumbnail: thumbnail, isAnimated: isAnimated, software: software)
|
||||
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, type: type, software: software)
|
||||
}
|
||||
|
||||
public func getStickerSetShortNameSuggestion(title: String) -> Signal<String?, NoError> {
|
||||
|
@ -52,6 +52,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
private let lineNode: AnimatedNavigationStripeNode
|
||||
private let titleNode: AnimatedCountLabelNode
|
||||
private let textNode: TextNode
|
||||
private var spoilerTextNode: TextNode?
|
||||
private var dustNode: InvisibleInkDustNode?
|
||||
|
||||
private let imageNode: TransformImageNode
|
||||
@ -71,6 +72,15 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
|
||||
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) {
|
||||
self.context = context
|
||||
|
||||
@ -270,6 +280,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.currentMessage = interfaceState.pinnedMessage
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -352,6 +363,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
|
||||
let makeTitleLayout = self.titleNode.asyncLayout()
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
let makeSpoilerTextLayout = TextNode.asyncLayout(self.spoilerTextNode)
|
||||
let imageNodeLayout = self.imageNode.asyncLayout()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
if let strongSelf = self {
|
||||
@ -485,28 +505,47 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
|
||||
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)
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: 0.0, y: 23.0), size: textLayout.size)
|
||||
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
|
||||
if let current = strongSelf.dustNode {
|
||||
dustNode = current
|
||||
} else {
|
||||
dustNode = InvisibleInkDustNode(textNode: nil)
|
||||
dustNode.isUserInteractionEnabled = false
|
||||
dustNode = InvisibleInkDustNode(textNode: spoilerTextNode)
|
||||
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.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 {
|
||||
dustNode.removeFromSupernode()
|
||||
} else if let spoilerTextNode = strongSelf.spoilerTextNode {
|
||||
strongSelf.spoilerTextNode = 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))
|
||||
|
@ -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))
|
||||
|> deliverOnMainQueue).start(next: { value, isImage in
|
||||
guard case let .data(data) = value, data.complete else {
|
||||
|
@ -227,7 +227,7 @@ func presentedLegacyShortcutCamera(context: AccountContext, saveCapturedMedia: B
|
||||
nativeGenerator(_1, _2, _3, nil)
|
||||
})
|
||||
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!)
|
||||
|> `catch` { _ -> Signal<[LegacyAssetPickerEnqueueMessage], NoError> in
|
||||
return .single([])
|
||||
|
@ -5,6 +5,7 @@ import AccountContext
|
||||
import TextFormat
|
||||
import UIKit
|
||||
import AppBundle
|
||||
import TelegramStringFormatting
|
||||
|
||||
enum PeerInfoScreenLabeledValueTextColor {
|
||||
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)
|
||||
|
||||
var text = item.text
|
||||
let maxNumberOfLines: Int
|
||||
switch item.textBehavior {
|
||||
case .singleLine:
|
||||
maxNumberOfLines = 1
|
||||
self.textNode.maximumNumberOfLines = maxNumberOfLines
|
||||
self.textNode.cutout = nil
|
||||
self.textNode.maximumNumberOfLines = 1
|
||||
self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: textColorValue)
|
||||
case let .multiLine(maxLines, enabledEntities):
|
||||
self.textNode.maximumNumberOfLines = self.isExpanded ? maxLines : 3
|
||||
// self.textNode.cutout = self.isExpanded ? nil : TextNodeCutout(bottomRight: CGSize(width: expandSize.width + 4.0, height: expandSize.height))
|
||||
if !self.isExpanded {
|
||||
text = trimToLineCount(text, lineCount: 3)
|
||||
}
|
||||
|
||||
maxNumberOfLines = self.isExpanded ? maxLines : 3
|
||||
self.textNode.maximumNumberOfLines = maxNumberOfLines
|
||||
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 {
|
||||
let fontSize: CGFloat = 17.0
|
||||
|
||||
@ -304,8 +312,8 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
|
||||
let boldItalicFont = Font.semiboldItalic(fontSize)
|
||||
let titleFixedFont = Font.monospace(fontSize)
|
||||
|
||||
let entities = generateTextEntities(item.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)
|
||||
let entities = generateTextEntities(text, enabledTypes: enabledEntities)
|
||||
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 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.expandNode.isHidden = false
|
||||
self.expandButonNode.isHidden = false
|
||||
|
@ -7213,7 +7213,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
|
||||
if accountTabBarAvatarBadge > 0 {
|
||||
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
|
||||
|
@ -348,8 +348,8 @@ public class ShareRootControllerImpl {
|
||||
} |> runOn(Queue.mainQueue())
|
||||
}
|
||||
|
||||
let sentItems: ([PeerId], [PreparedShareItemContent], Account) -> Signal<ShareControllerExternalStatus, NoError> = { peerIds, contents, account in
|
||||
let sentItems = sentShareItems(account: account, to: peerIds, items: contents)
|
||||
let sentItems: ([PeerId], [PreparedShareItemContent], Account, Bool) -> Signal<ShareControllerExternalStatus, NoError> = { peerIds, contents, account, silently in
|
||||
let sentItems = sentShareItems(account: account, to: peerIds, items: contents, silently: silently)
|
||||
|> `catch` { _ -> Signal<
|
||||
Float, NoError> in
|
||||
return .complete()
|
||||
@ -361,7 +361,7 @@ public class ShareRootControllerImpl {
|
||||
|> 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 {
|
||||
let rawSignals = TGItemProviderSignals.itemSignals(forInputItems: inputItems)!
|
||||
return preparedShareItems(account: account, to: peerIds[0], dataItems: rawSignals, additionalText: additionalText)
|
||||
@ -381,10 +381,10 @@ public class ShareRootControllerImpl {
|
||||
case let .userInteractionRequired(value):
|
||||
return requestUserInteraction(value)
|
||||
|> mapToSignal { contents -> Signal<ShareControllerExternalStatus, NoError> in
|
||||
return sentItems(peerIds, contents, account)
|
||||
return sentItems(peerIds, contents, account, silently)
|
||||
}
|
||||
case let .done(contents):
|
||||
return sentItems(peerIds, contents, account)
|
||||
return sentItems(peerIds, contents, account, silently)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user