diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index 7d9e4b84e0..a7c8628a6b 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -115,7 +115,9 @@ public final class CallListController: TelegramBaseController { self.tabBarItem.title = self.presentationData.strings.Calls_TabTitle self.tabBarItem.image = icon self.tabBarItem.selectedImage = icon - self.tabBarItem.animationName = "TabCalls" + if !self.presentationData.reduceMotion { + self.tabBarItem.animationName = "TabCalls" + } } self.segmentedTitleView.indexUpdated = { [weak self] index in @@ -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: diff --git a/submodules/Camera/Sources/CameraPreviewNode.swift b/submodules/Camera/Sources/CameraPreviewNode.swift index 9e88730a71..019e73a47e 100644 --- a/submodules/Camera/Sources/CameraPreviewNode.swift +++ b/submodules/Camera/Sources/CameraPreviewNode.swift @@ -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]?) { @@ -16,16 +17,25 @@ 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 } } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 05618a191c..8727dcbd25 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -206,8 +206,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.tabBarItem.image = icon self.tabBarItem.selectedImage = icon - self.tabBarItem.animationName = "TabChats" - self.tabBarItem.animationOffset = CGPoint(x: 0.0, y: UIScreenPixel) + 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 diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 6aa57f983a..0b14205861 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -192,7 +192,9 @@ public class ContactsController: ViewController { self.tabBarItem.image = 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) @@ -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)) diff --git a/submodules/GameUI/Sources/GameControllerNode.swift b/submodules/GameUI/Sources/GameControllerNode.swift index 61d7d0137f..6b22a0c21b 100644 --- a/submodules/GameUI/Sources/GameControllerNode.swift +++ b/submodules/GameUI/Sources/GameControllerNode.swift @@ -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) diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPack.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPack.swift index dadfc898be..2a37ff824d 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPack.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPack.swift @@ -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 } diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift index 4fb5ca1240..1f49b051f9 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift @@ -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) diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift index e84e2685b1..15b6145ccb 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift @@ -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 } diff --git a/submodules/ImportStickerPackUI/Sources/StickerPackPreviewGridItem.swift b/submodules/ImportStickerPackUI/Sources/StickerPackPreviewGridItem.swift index ae862e592c..7182a53a63 100644 --- a/submodules/ImportStickerPackUI/Sources/StickerPackPreviewGridItem.swift +++ b/submodules/ImportStickerPackUI/Sources/StickerPackPreviewGridItem.swift @@ -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 diff --git a/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift b/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift index cac2126712..fc67597ed2 100644 --- a/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift @@ -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) } diff --git a/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift b/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift index feeb97d394..0ff3170822 100644 --- a/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift +++ b/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift @@ -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() diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index b4f614a1d7..dcdd97a66a 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -61,7 +61,7 @@ public enum ShareControllerSubject { case image([ImageRepresentationWithReference]) case media(AnyMediaReference) case mapMedia(TelegramMediaMap) - case fromExternal(([PeerId], String, Account) -> Signal) + case fromExternal(([PeerId], String, Account, Bool) -> Signal) } 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: diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index f3bf10b027..5be75c6259 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -191,29 +191,35 @@ 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 = strongSelf.showNames.get() |> map { showNamesValue in - return ContextController.Items(content: .list([ - .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) - } else { - return nil - } - }, action: { _, _ in - self?.showNames.set(true) - })), - .action(ContextMenuActionItem(text: presentationData.strings.Conversation_ForwardOptions_HideSendersName, icon: { theme in - if !showNamesValue { - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) - } else { - return nil - } - }, action: { _, _ in - self?.showNames.set(false) - })), - .separator, + 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) + } else { + return nil + } + }, action: { _, _ in + self?.showNames.set(true) + })), + .action(ContextMenuActionItem(text: presentationData.strings.Conversation_ForwardOptions_HideSendersName, icon: { theme in + if !showNamesValue { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } else { + return nil + } + }, action: { _, _ in + 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 diff --git a/submodules/ShareItems/Sources/ShareItems.swift b/submodules/ShareItems/Sources/ShareItems.swift index 2af74b474b..50639b606d 100644 --- a/submodules/ShareItems/Sources/ShareItems.swift +++ b/submodules/ShareItems/Sources/ShareItems.swift @@ -365,7 +365,7 @@ public func preparedShareItems(account: Account, to peerId: PeerId, dataItems: [ }) } -public func sentShareItems(account: Account, to peerIds: [PeerId], items: [PreparedShareItemContent]) -> Signal { +public func sentShareItems(account: Account, to peerIds: [PeerId], items: [PreparedShareItemContent], silently: Bool) -> Signal { 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) diff --git a/submodules/TabBarUI/Sources/TabBarNode.swift b/submodules/TabBarUI/Sources/TabBarNode.swift index 7a03f80e1e..9a9cc97ce5 100644 --- a/submodules/TabBarUI/Sources/TabBarNode.swift +++ b/submodules/TabBarUI/Sources/TabBarNode.swift @@ -723,7 +723,9 @@ class TabBarNode: ASDisplayNode { let previousSelectedIndex = self.selectedIndex self.itemSelected(closestNode.0, longTap, [container.imageNode.imageNode, container.imageNode.textImageNode, container.badgeContainerNode]) if previousSelectedIndex != closestNode.0 { - container.imageNode.animationNode.play() + if let selectedIndex = self.selectedIndex, let _ = self.tabBarItems[selectedIndex].item.animationName { + container.imageNode.animationNode.play() + } } } } diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index 30d5e33023..ac9b620397 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -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, _, _, _): diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift index 099e873870..294727ca33 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift @@ -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 { +func _internal_uploadSticker(account: Account, peer: Peer, resource: MediaResource, alt: String, dimensions: PixelDimensions, mimeType: String) -> Signal { 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 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 { +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 { return account.postbox.loadedPeerWithId(account.peerId) |> castError(CreateStickerSetError.self) |> mapToSignal { peer -> Signal 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] = [] diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift index b43bf94077..da417b8d0e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift @@ -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 { - 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 { + 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 { - 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 { + return _internal_createStickerSet(account: self.account, title: title, shortName: shortName, stickers: stickers, thumbnail: thumbnail, type: type, software: software) } public func getStickerSetShortNameSuggestion(title: String) -> Signal { diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 6d4fa04092..7677776378 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -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() - strongSelf.dustNode = nil + } 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)) diff --git a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift index b4cb282917..14e1e627f8 100644 --- a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/LegacyCamera.swift b/submodules/TelegramUI/Sources/LegacyCamera.swift index b9e42eca07..ede0ddfc9f 100644 --- a/submodules/TelegramUI/Sources/LegacyCamera.swift +++ b/submodules/TelegramUI/Sources/LegacyCamera.swift @@ -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([]) diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift index c75a7aea1d..a9e16035d8 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 826b047c78..ceba580a57 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Sources/ShareExtensionContext.swift index 9d4fefba03..649ca55f98 100644 --- a/submodules/TelegramUI/Sources/ShareExtensionContext.swift +++ b/submodules/TelegramUI/Sources/ShareExtensionContext.swift @@ -348,8 +348,8 @@ public class ShareRootControllerImpl { } |> runOn(Queue.mainQueue()) } - let sentItems: ([PeerId], [PreparedShareItemContent], Account) -> Signal = { peerIds, contents, account in - let sentItems = sentShareItems(account: account, to: peerIds, items: contents) + let sentItems: ([PeerId], [PreparedShareItemContent], Account, Bool) -> Signal = { 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 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 {