Added URL Auth support

This commit is contained in:
Ilya Laktyushin 2019-05-20 15:05:36 +02:00
parent deda2f8652
commit 4dffa90851
49 changed files with 1395 additions and 98 deletions

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "bold@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "bold@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 906 B

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "italic@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "italic@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "link@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "link@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "markdown@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "markdown@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 B

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "strike@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "strike@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

View File

@ -24,7 +24,15 @@
0913469C21883C3700846E49 /* InstantPageDetailsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0913469B21883C3700846E49 /* InstantPageDetailsItem.swift */; };
091417F221EF4E5D00C8325A /* WallpaperGalleryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091417F121EF4E5D00C8325A /* WallpaperGalleryController.swift */; };
091417F421EF4F5F00C8325A /* WallpaperGalleryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091417F321EF4F5F00C8325A /* WallpaperGalleryItem.swift */; };
091954732294591B00E11046 /* AnimatedStickerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091954722294591B00E11046 /* AnimatedStickerNode.swift */; };
09195475229474E900E11046 /* AnimatedStickerPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09195474229474E900E11046 /* AnimatedStickerPlayerManager.swift */; };
091954772294752C00E11046 /* AnimatedStickerPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091954762294752C00E11046 /* AnimatedStickerPlayer.swift */; };
091954792294754E00E11046 /* AnimatedStickerUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091954782294754E00E11046 /* AnimatedStickerUtils.swift */; };
0919547B2294788200E11046 /* AnimatedStickerVideoCompositor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919547A2294788200E11046 /* AnimatedStickerVideoCompositor.swift */; };
091BEAB3214552D9003AEA30 /* Vision.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D02DADBE2138D76F00116225 /* Vision.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
0921F5FF228B09D2001A13D7 /* GZip.m in Sources */ = {isa = PBXBuildFile; fileRef = 0921F5FC228B01B6001A13D7 /* GZip.m */; };
0921F60B228C8765001A13D7 /* ItemListPlaceholderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0921F60A228C8765001A13D7 /* ItemListPlaceholderItem.swift */; };
0921F60E228EE000001A13D7 /* ChatMessageActionUrlAuthController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0921F60D228EE000001A13D7 /* ChatMessageActionUrlAuthController.swift */; };
092F368D2154AAEA001A9F49 /* SFCompactRounded-Semibold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 092F368C2154AAE9001A9F49 /* SFCompactRounded-Semibold.otf */; };
092F36902157AB46001A9F49 /* ItemListCallListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 092F368F2157AB46001A9F49 /* ItemListCallListItem.swift */; };
09310D32213ED5FC0020033A /* anim_ungroup.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D1A213BC5DE0020033A /* anim_ungroup.json */; };
@ -1218,6 +1226,16 @@
0913469B21883C3700846E49 /* InstantPageDetailsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageDetailsItem.swift; sourceTree = "<group>"; };
091417F121EF4E5D00C8325A /* WallpaperGalleryController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperGalleryController.swift; sourceTree = "<group>"; };
091417F321EF4F5F00C8325A /* WallpaperGalleryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperGalleryItem.swift; sourceTree = "<group>"; };
09167E1B22973A14005734A7 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
091954722294591B00E11046 /* AnimatedStickerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedStickerNode.swift; sourceTree = "<group>"; };
09195474229474E900E11046 /* AnimatedStickerPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedStickerPlayerManager.swift; sourceTree = "<group>"; };
091954762294752C00E11046 /* AnimatedStickerPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedStickerPlayer.swift; sourceTree = "<group>"; };
091954782294754E00E11046 /* AnimatedStickerUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedStickerUtils.swift; sourceTree = "<group>"; };
0919547A2294788200E11046 /* AnimatedStickerVideoCompositor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedStickerVideoCompositor.swift; sourceTree = "<group>"; };
0921F5FB228B01B6001A13D7 /* GZip.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GZip.h; sourceTree = "<group>"; };
0921F5FC228B01B6001A13D7 /* GZip.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GZip.m; sourceTree = "<group>"; };
0921F60A228C8765001A13D7 /* ItemListPlaceholderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListPlaceholderItem.swift; sourceTree = "<group>"; };
0921F60D228EE000001A13D7 /* ChatMessageActionUrlAuthController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageActionUrlAuthController.swift; sourceTree = "<group>"; };
092F368C2154AAE9001A9F49 /* SFCompactRounded-Semibold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SFCompactRounded-Semibold.otf"; sourceTree = "<group>"; };
092F368F2157AB46001A9F49 /* ItemListCallListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListCallListItem.swift; sourceTree = "<group>"; };
09310D1A213BC5DE0020033A /* anim_ungroup.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_ungroup.json; sourceTree = "<group>"; };
@ -2532,6 +2550,19 @@
name = "Language Suggestion";
sourceTree = "<group>";
};
0919546D229458E900E11046 /* Animated Stickers */ = {
isa = PBXGroup;
children = (
091954722294591B00E11046 /* AnimatedStickerNode.swift */,
09167E1B22973A14005734A7 /* README.md */,
091954762294752C00E11046 /* AnimatedStickerPlayer.swift */,
09195474229474E900E11046 /* AnimatedStickerPlayerManager.swift */,
0919547A2294788200E11046 /* AnimatedStickerVideoCompositor.swift */,
091954782294754E00E11046 /* AnimatedStickerUtils.swift */,
);
name = "Animated Stickers";
sourceTree = "<group>";
};
092F368B2154AAD6001A9F49 /* Fonts */ = {
isa = PBXGroup;
children = (
@ -4504,6 +4535,7 @@
D0D03AE61DECB0D200220C46 /* Audio Recorder */,
D0F69DBC1D6B886C0046BCD6 /* Player */,
D0EC6FF71EBA1DAE00EBF1C3 /* Calls */,
0919546D229458E900E11046 /* Animated Stickers */,
D0F69CDE1D6B87D30046BCD6 /* PeerAvatar.swift */,
D0F69E9D1D6B8E240046BCD6 /* Resources */,
D0177B831DFB095000A5083A /* FileMediaResourceStatus.swift */,
@ -4739,6 +4771,7 @@
D044A0FA20BDC40C00326FAC /* CachedChannelAdmins.swift */,
D01848E721A03BDA00B6DEBD /* ChatSearchState.swift */,
D06350AD2229A7F800FA2B32 /* InChatPrefetchManager.swift */,
0921F60D228EE000001A13D7 /* ChatMessageActionUrlAuthController.swift */,
);
name = Chat;
sourceTree = "<group>";
@ -4894,6 +4927,7 @@
D0B2F76D2052B59F00D3BFB9 /* InviteContactsController.swift */,
D0B2F76F2052B5A800D3BFB9 /* InviteContactsControllerNode.swift */,
D0B2F7712052D0DD00D3BFB9 /* InviteContactsCountPanelNode.swift */,
0921F60A228C8765001A13D7 /* ItemListPlaceholderItem.swift */,
);
name = Contacts;
sourceTree = "<group>";
@ -4981,6 +5015,8 @@
09E4A800223AE1B30038140F /* PeerType.swift */,
09E4A806223D4B860038140F /* AccountUtils.swift */,
D099E21F229405BB00561B75 /* Weak.swift */,
0921F5FB228B01B6001A13D7 /* GZip.h */,
0921F5FC228B01B6001A13D7 /* GZip.m */,
);
name = Utils;
sourceTree = "<group>";
@ -5720,6 +5756,7 @@
09749BC921F1BBA1008FDDE9 /* CallFeedbackController.swift in Sources */,
099529FA21DD8A3100805E13 /* NavigationBarSearchContentNode.swift in Sources */,
D0AEAE272080D6970013176E /* PaneSearchBarNode.swift in Sources */,
0921F60B228C8765001A13D7 /* ItemListPlaceholderItem.swift in Sources */,
D0EC6D4F1EB9F58800EBF1C3 /* ChatListSearchItem.swift in Sources */,
D0EC6D501EB9F58800EBF1C3 /* ChatListNodeEntries.swift in Sources */,
D0EC6D511EB9F58800EBF1C3 /* ChatListViewTransition.swift in Sources */,
@ -5728,6 +5765,7 @@
D0EC6D531EB9F58800EBF1C3 /* ChatHistoryViewForLocation.swift in Sources */,
D06BB8821F58994B0084FC30 /* LegacyInstantVideoController.swift in Sources */,
D0EC6D541EB9F58800EBF1C3 /* ChatHistoryEntriesForView.swift in Sources */,
0921F5FF228B09D2001A13D7 /* GZip.m in Sources */,
D0943B051FDDFDA0001522CC /* OverlayInstantVideoNode.swift in Sources */,
D0EC6D551EB9F58800EBF1C3 /* PreparedChatHistoryViewTransition.swift in Sources */,
D0EB41FB1F30E75000838FE6 /* LegacyImageDownloadActor.swift in Sources */,
@ -5801,6 +5839,7 @@
D0C12EB01F9A8D1300600BB2 /* ListMessageDateHeader.swift in Sources */,
09B4EE5221A7CC3E00847FA6 /* SolidRoundedButtonNode.swift in Sources */,
D0E9BA5D1F055A3300F079A4 /* STPBINRange.m in Sources */,
091954792294754E00E11046 /* AnimatedStickerUtils.swift in Sources */,
D0EC6D741EB9F58800EBF1C3 /* AuthorizationSequenceSignUpControllerNode.swift in Sources */,
D0EC6D751EB9F58800EBF1C3 /* TelegramRootController.swift in Sources */,
D0EC6D761EB9F58800EBF1C3 /* ChatListController.swift in Sources */,
@ -5947,6 +5986,7 @@
09DE2F292269D5E30045E975 /* PrivacyIntroControllerNode.swift in Sources */,
D0F67FF01EE6B8A8000E5906 /* ChannelMembersSearchController.swift in Sources */,
D0EC6DAF1EB9F58900EBF1C3 /* ChatInterfaceInputContexts.swift in Sources */,
0921F60E228EE000001A13D7 /* ChatMessageActionUrlAuthController.swift in Sources */,
D0EC6DB01EB9F58900EBF1C3 /* ChatInterfaceInputContextPanels.swift in Sources */,
D0EC6DB11EB9F58900EBF1C3 /* ChatInterfaceInputNodes.swift in Sources */,
D0EC6DB21EB9F58900EBF1C3 /* ChatInterfaceTitlePanelNodes.swift in Sources */,
@ -5993,6 +6033,7 @@
D0EC6DC61EB9F58900EBF1C3 /* MultiplexedSoftwareVideoSourceManager.swift in Sources */,
D0EC6DC71EB9F58900EBF1C3 /* SampleBufferPool.swift in Sources */,
0962E67721B673AF00245FD9 /* Permission.swift in Sources */,
091954772294752C00E11046 /* AnimatedStickerPlayer.swift in Sources */,
D0B21B13220D6E8C003F741D /* ActionSheetPeerItem.swift in Sources */,
0900678F21ED8E0E00530762 /* HexColor.swift in Sources */,
D0EC6DC81EB9F58900EBF1C3 /* MultiplexedVideoNode.swift in Sources */,
@ -6122,6 +6163,7 @@
D0EC6E041EB9F58900EBF1C3 /* SecretMediaPreviewController.swift in Sources */,
09F2158D225CF5BC00AEDF6D /* Pasteboard.swift in Sources */,
D0C26D571FDF2388004ABF18 /* OpenChatMessage.swift in Sources */,
0919547B2294788200E11046 /* AnimatedStickerVideoCompositor.swift in Sources */,
D0FA08BE20481EA300DD23FC /* Locale.swift in Sources */,
D0E412CE206A707400BEE4A2 /* FormControllerTextItem.swift in Sources */,
D007019C2029E8F2006B9E34 /* LegacyICloudFileController.swift in Sources */,
@ -6342,6 +6384,7 @@
D056CD741FF2996B00880D28 /* ExternalMusicAlbumArtResources.swift in Sources */,
D0F0AAE41EC21AAA005EE2A5 /* CallControllerButtonsNode.swift in Sources */,
D0EC6E7A1EB9F58900EBF1C3 /* DebugController.swift in Sources */,
091954732294591B00E11046 /* AnimatedStickerNode.swift in Sources */,
D07ABBAB202A1BD1003671DE /* LegacyWallpaperEditor.swift in Sources */,
09E2D9F1226F214000EA0AA4 /* EmojiResources.swift in Sources */,
D0EC6E7B1EB9F58900EBF1C3 /* DebugAccountsController.swift in Sources */,
@ -6356,6 +6399,7 @@
D0EC6E7D1EB9F58900EBF1C3 /* ChangePhoneNumberIntroController.swift in Sources */,
09F215AB2264ABA600AEDF6D /* PasscodeBackground.swift in Sources */,
D0EC6E7E1EB9F58900EBF1C3 /* ChangePhoneNumberController.swift in Sources */,
09195475229474E900E11046 /* AnimatedStickerPlayerManager.swift in Sources */,
D0B21B17220D85E7003F741D /* TabBarAccountSwitchControllerNode.swift in Sources */,
D0EC6E7F1EB9F58900EBF1C3 /* ChangePhoneNumberControllerNode.swift in Sources */,
D0EC6E801EB9F58900EBF1C3 /* ChangePhoneNumberCodeController.swift in Sources */,

View File

@ -0,0 +1,12 @@
import Foundation
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import AVFoundation
import CoreImage
final class AnimatedStickerNode: ASDisplayNode {
}

View File

@ -0,0 +1,5 @@
import UIKit
class AnimatedStickerPlayer: NSObject {
}

View File

@ -0,0 +1,5 @@
import Foundation
final class AnimatedStickerPlayerManager {
}

View File

@ -0,0 +1,154 @@
import Foundation
import SwiftSignalKit
import Display
import AVFoundation
import Lottie
import TelegramUIPrivateModule
private func verifyLottieItems(_ items: [Any]?, shapes: Bool = true) -> Bool {
if let items = items {
for case let item as [AnyHashable: Any] in items {
if let type = item["ty"] as? String {
if type == "rp" || type == "sr" || type == "mm" || type == "gs" {
return false
}
}
if shapes, let subitems = item["it"] as? [Any] {
if !verifyLottieItems(subitems, shapes: false) {
return false
}
}
}
}
return true;
}
private func verifyLottieLayers(_ layers: [AnyHashable: Any]?) -> Bool {
return true
}
func validateStickerComposition(json: [AnyHashable: Any]) -> Bool {
guard let tgs = json["tgs"] as? Int, tgs == 1 else {
return false
}
return true
}
func convertCompressedLottieToCombinedMp4(data: Data, size: CGSize) -> Signal<String, NoError> {
return Signal({ subscriber in
let startTime = CACurrentMediaTime()
let decompressedData = TGGUnzipData(data)
if let decompressedData = decompressedData, let json = (try? JSONSerialization.jsonObject(with: decompressedData, options: [])) as? [AnyHashable: Any] {
if let _ = json["tgs"] {
let model = LOTComposition(json: json)
if let startFrame = model.startFrame?.int32Value, let endFrame = model.endFrame?.int32Value {
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
let path = NSTemporaryDirectory() + "\(randomId).mp4"
let url = URL(fileURLWithPath: path)
let videoSize = CGSize(width: size.width, height: size.height * 2.0)
let scale = size.width / 512.0
if let assetWriter = try? AVAssetWriter(outputURL: url, fileType: AVFileType.mp4) {
let videoSettings: [String: AnyObject] = [AVVideoCodecKey : AVVideoCodecH264 as AnyObject, AVVideoWidthKey : videoSize.width as AnyObject, AVVideoHeightKey : videoSize.height as AnyObject]
let assetWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings)
let sourceBufferAttributes = [(kCVPixelBufferPixelFormatTypeKey as String): Int(kCVPixelFormatType_32ARGB),
(kCVPixelBufferWidthKey as String): Float(videoSize.width),
(kCVPixelBufferHeightKey as String): Float(videoSize.height)] as [String : Any]
let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: assetWriterInput, sourcePixelBufferAttributes: sourceBufferAttributes)
assetWriter.add(assetWriterInput)
if assetWriter.startWriting() {
print("startedWriting at \(CACurrentMediaTime() - startTime)")
assetWriter.startSession(atSourceTime: kCMTimeZero)
var currentFrame: Int32 = 0
let writeQueue = DispatchQueue(label: "assetWriterQueue")
writeQueue.async {
let container = LOTAnimationLayerContainer(model: model, size: size)
let singleContext = DrawingContext(size: size, scale: 1.0, clear: true)
let context = DrawingContext(size: size, scale: 1.0, clear: false)
let fps: Int32 = model.framerate?.int32Value ?? 30
let frameDuration = CMTimeMake(1, fps)
assetWriterInput.requestMediaDataWhenReady(on: writeQueue) {
while assetWriterInput.isReadyForMoreMediaData && startFrame + currentFrame < endFrame {
let lastFrameTime = CMTimeMake(Int64(currentFrame - startFrame), fps)
let presentationTime = currentFrame == 0 ? lastFrameTime : CMTimeAdd(lastFrameTime, frameDuration)
singleContext.withContext { context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.saveGState()
context.scaleBy(x: scale, y: scale)
container?.renderFrame(startFrame + currentFrame, in: context)
context.restoreGState()
}
let image = singleContext.generateImage()
let alphaImage = generateTintedImage(image: image, color: .white, backgroundColor: .black)
context.withFlippedContext { context in
context.setFillColor(UIColor.white.cgColor)
context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.height), size: videoSize))
if let image = image?.cgImage {
context.draw(image, in: CGRect(origin: CGPoint(x: 0.0, y: size.height), size: size))
}
if let alphaImage = alphaImage?.cgImage {
context.draw(alphaImage, in: CGRect(origin: CGPoint(), size: size))
}
}
if let image = context.generateImage() {
if let pixelBufferPool = pixelBufferAdaptor.pixelBufferPool {
let pixelBufferPointer = UnsafeMutablePointer<CVPixelBuffer?>.allocate(capacity: 1)
let status = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pixelBufferPool, pixelBufferPointer)
if let pixelBuffer = pixelBufferPointer.pointee, status == 0 {
fillPixelBufferFromImage(image, pixelBuffer: pixelBuffer)
pixelBufferAdaptor.append(pixelBuffer, withPresentationTime: presentationTime)
pixelBufferPointer.deinitialize(count: 1)
} else {
break
}
pixelBufferPointer.deallocate()
} else {
break
}
}
currentFrame += 1
}
if startFrame + currentFrame == endFrame {
assetWriterInput.markAsFinished()
assetWriter.finishWriting {
subscriber.putNext(path)
subscriber.putCompletion()
print("animation render time \(CACurrentMediaTime() - startTime)")
}
}
}
}
}
}
}
}
}
return EmptyDisposable
})
}
private func fillPixelBufferFromImage(_ image: UIImage, pixelBuffer: CVPixelBuffer) {
CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer)
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: pixelData, width: Int(image.size.width), height: Int(image.size.height), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)
context?.draw(image.cgImage!, in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
}

View File

@ -0,0 +1,5 @@
import AVFoundation
final class AnimatedStickerVideoCompositor: NSObject {
}

View File

@ -213,3 +213,17 @@ final class CachedEmojiRepresentation: CachedMediaResourceRepresentation {
}
}
}
final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentation {
var uniqueId: String {
return "animated-sticker"
}
func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
if let _ = to as? CachedAnimatedStickerRepresentation {
return true
} else {
return false
}
}
}

View File

@ -131,7 +131,7 @@ final class ChatAnimationGalleryItemNode: ZoomableContentGalleryItemNode {
func setFile(context: AccountContext, fileReference: FileMediaReference) {
if self.contextAndMedia == nil || !self.contextAndMedia!.1.media.isEqual(to: fileReference.media) {
let signal = chatMessageAnimationData(postbox: context.account.postbox, fileReference: fileReference, synchronousLoad: false)
let signal = chatMessageAnimatedStrickerBackingData(postbox: context.account.postbox, fileReference: fileReference, synchronousLoad: false)
|> mapToSignal { data, completed -> Signal<Data, NoError> in
if completed, let data = data {
return .single(data)

View File

@ -160,20 +160,20 @@ final class ChatButtonKeyboardInputNode: ChatInputNode {
if let button = button as? ChatButtonKeyboardInputButtonNode, let markupButton = button.button {
switch markupButton.action {
case .text:
controllerInteraction.sendMessage(markupButton.title)
self.controllerInteraction.sendMessage(markupButton.title)
case let .url(url):
controllerInteraction.openUrl(url, true, nil)
self.controllerInteraction.openUrl(url, true, nil)
case .requestMap:
controllerInteraction.shareCurrentLocation()
self.controllerInteraction.shareCurrentLocation()
case .requestPhone:
controllerInteraction.shareAccountContact()
self.controllerInteraction.shareAccountContact()
case .openWebApp:
if let message = self.message {
controllerInteraction.requestMessageActionCallback(message.id, nil, true)
self.controllerInteraction.requestMessageActionCallback(message.id, nil, true)
}
case let .callback(data):
if let message = self.message {
controllerInteraction.requestMessageActionCallback(message.id, data, false)
self.controllerInteraction.requestMessageActionCallback(message.id, data, false)
}
case let .switchInline(samePeer, query):
if let message = message {
@ -195,13 +195,15 @@ final class ChatButtonKeyboardInputNode: ChatInputNode {
peerId = message.id.peerId
}
if let botPeer = botPeer, let addressName = botPeer.addressName {
controllerInteraction.openPeer(peerId, .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: "@\(addressName) \(query)")), messageId: nil), nil)
self.controllerInteraction.openPeer(peerId, .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: "@\(addressName) \(query)")), messageId: nil), nil)
}
}
case .payment:
break
case .urlAuth:
break
case let .urlAuth(url, buttonId):
if let message = self.message {
self.controllerInteraction.requestMessageActionUrlAuth(url, message.id, buttonId)
}
}
}
}

View File

@ -128,6 +128,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
private let sentMessageEventsDisposable = MetaDisposable()
private let failedMessageEventsDisposable = MetaDisposable()
private let messageActionCallbackDisposable = MetaDisposable()
private let messageActionUrlAuthDisposable = MetaDisposable()
private let editMessageDisposable = MetaDisposable()
private let enqueueMediaMessageDisposable = MetaDisposable()
private var resolvePeerByNameDisposable: MetaDisposable?
@ -643,6 +644,121 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
}))
}
}
}, requestMessageActionUrlAuth: { [weak self] defaultUrl, messageId, buttonId in
if let strongSelf = self {
if let _ = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if !$0.contains(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.append(.requestInProgress)
return updatedContexts.sorted()
}
return $0
}
})
strongSelf.messageActionUrlAuthDisposable.set(((combineLatest(strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId), requestMessageActionUrlAuth(account: strongSelf.context.account, messageId: messageId, buttonId: buttonId) |> afterDisposed {
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if let index = $0.index(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
}
return $0
}
})
}
}
})) |> deliverOnMainQueue).start(next: { peer, result in
if let strongSelf = self {
switch result {
case .default:
strongSelf.openUrl(defaultUrl, concealed: false)
case let .request(domain, bot, requestWriteAccess):
let controller = chatMessageActionUrlAuthController(context: strongSelf.context, defaultUrl: defaultUrl, domain: domain, bot: bot, requestWriteAccess: requestWriteAccess, displayName: peer.displayTitle, open: { [weak self] authorize, allowWriteAccess in
if let strongSelf = self {
if authorize {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if !$0.contains(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.append(.requestInProgress)
return updatedContexts.sorted()
}
return $0
}
})
strongSelf.messageActionUrlAuthDisposable.set(((acceptMessageActionUrlAuth(account: strongSelf.context.account, messageId: messageId, buttonId: buttonId, allowWriteAccess: allowWriteAccess) |> afterDisposed {
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if let index = $0.index(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
}
return $0
}
})
}
}
}) |> deliverOnMainQueue).start(next: { [weak self] result in
if let strongSelf = self {
switch result {
case let .accepted(url):
strongSelf.openUrl(url, concealed: false)
default:
strongSelf.openUrl(defaultUrl, concealed: false)
}
}
}))
} else {
strongSelf.openUrl(defaultUrl, concealed: false)
}
}
})
strongSelf.present(controller, in: .window(.root))
case let .accepted(url):
strongSelf.openUrl(url, concealed: false)
}
}
}))
}
}
}, activateSwitchInline: { [weak self] peerId, inputString in
guard let strongSelf = self else {
return
@ -1706,6 +1822,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
self.sentMessageEventsDisposable.dispose()
self.failedMessageEventsDisposable.dispose()
self.messageActionCallbackDisposable.dispose()
self.messageActionUrlAuthDisposable.dispose()
self.editMessageDisposable.dispose()
self.enqueueMediaMessageDisposable.dispose()
self.resolvePeerByNameDisposable?.dispose()

View File

@ -62,6 +62,7 @@ public final class ChatControllerInteraction {
let sendSticker: (FileMediaReference, Bool) -> Void
let sendGif: (FileMediaReference) -> Void
let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool) -> Void
let requestMessageActionUrlAuth: (String, MessageId, Int32) -> Void
let activateSwitchInline: (PeerId?, String) -> Void
let openUrl: (String, Bool, Bool?) -> Void
let shareCurrentLocation: () -> Void
@ -102,7 +103,7 @@ public final class ChatControllerInteraction {
var pollActionState: ChatInterfacePollActionState
var searchTextHighightState: String?
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState) {
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState) {
self.openMessage = openMessage
self.openPeer = openPeer
self.openPeerMention = openPeerMention
@ -114,6 +115,7 @@ public final class ChatControllerInteraction {
self.sendSticker = sendSticker
self.sendGif = sendGif
self.requestMessageActionCallback = requestMessageActionCallback
self.requestMessageActionUrlAuth = requestMessageActionUrlAuth
self.activateSwitchInline = activateSwitchInline
self.openUrl = openUrl
self.shareCurrentLocation = shareCurrentLocation
@ -153,7 +155,7 @@ public final class ChatControllerInteraction {
static var `default`: ChatControllerInteraction {
return ChatControllerInteraction(openMessage: { _, _ in
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
}, presentController: { _, _ in }, navigationController: {
return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in

View File

@ -79,7 +79,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
switch button.action {
case .text:
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingMessageIconImage : graphics.chatBubbleActionButtonOutgoingMessageIconImage
case .url:
case .url, .urlAuth:
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingLinkIconImage : graphics.chatBubbleActionButtonOutgoingLinkIconImage
case .requestPhone:
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPhoneIconImage : graphics.chatBubbleActionButtonOutgoingLinkIconImage

View File

@ -0,0 +1,372 @@
import Foundation
import SwiftSignalKit
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
private final class ChatMessageActionUrlAuthContentActionNode: HighlightableButtonNode {
private let backgroundNode: ASDisplayNode
let action: TextAlertAction
init(theme: AlertControllerTheme, action: TextAlertAction) {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.alpha = 0.0
self.action = action
super.init()
self.titleNode.maximumNumberOfLines = 2
self.highligthedChanged = { [weak self] value in
if let strongSelf = self {
if value {
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
}
strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity")
strongSelf.backgroundNode.alpha = 1.0
} else if !strongSelf.backgroundNode.alpha.isZero {
strongSelf.backgroundNode.alpha = 0.0
strongSelf.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
}
}
}
self.updateTheme(theme)
}
func updateTheme(_ theme: AlertControllerTheme) {
self.backgroundNode.backgroundColor = theme.highlightedItemColor
var font = Font.regular(17.0)
var color = theme.accentColor
switch self.action.type {
case .defaultAction, .genericAction:
break
case .destructiveAction:
color = theme.destructiveColor
}
switch self.action.type {
case .defaultAction:
font = Font.semibold(17.0)
case .destructiveAction, .genericAction:
break
}
self.setAttributedTitle(NSAttributedString(string: self.action.title, font: font, textColor: color, paragraphAlignment: .center), for: [])
}
override func didLoad() {
super.didLoad()
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
}
@objc func pressed() {
self.action.action()
}
override func layout() {
super.layout()
self.backgroundNode.frame = self.bounds
}
}
private let textFont = Font.regular(13.0)
private let boldTextFont = Font.semibold(13.0)
private func formattedText(_ text: String, color: UIColor, textAlignment: NSTextAlignment = .natural) -> NSAttributedString {
return parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: color), bold: MarkdownAttributeSet(font: boldTextFont, textColor: color), link: MarkdownAttributeSet(font: textFont, textColor: color), linkAttribute: { _ in return nil}), textAlignment: textAlignment)
}
private final class ChatMessageActionUrlAuthAlertContentNode: AlertContentNode {
private let strings: PresentationStrings
private let defaultUrl: String
private let domain: String
private let bot: Peer
private let displayName: String
private let titleNode: ASTextNode
private let textNode: ASTextNode
private let authorizeCheckNode: CheckNode
private let authorizeLabelNode: ASTextNode
private let allowWriteCheckNode: CheckNode
private let allowWriteLabelNode: ASTextNode
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [ChatMessageActionUrlAuthContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode]
private var validLayout: CGSize?
override var dismissOnOutsideTap: Bool {
return self.isUserInteractionEnabled
}
var authorize: Bool = true {
didSet {
self.authorizeCheckNode.setIsChecked(self.authorize, animated: true)
if !self.authorize && self.allowWriteAccess {
self.allowWriteAccess = false
}
}
}
var allowWriteAccess: Bool = true {
didSet {
self.allowWriteCheckNode.setIsChecked(self.allowWriteAccess, animated: true)
if !self.authorize && self.allowWriteAccess {
self.authorize = true
}
}
}
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, defaultUrl: String, domain: String, bot: Peer, requestWriteAccess: Bool, displayName: String, actions: [TextAlertAction]) {
self.strings = strings
self.defaultUrl = defaultUrl
self.domain = domain
self.bot = bot
self.displayName = displayName
self.titleNode = ASTextNode()
self.titleNode.maximumNumberOfLines = 2
self.textNode = ASTextNode()
self.textNode.maximumNumberOfLines = 0
self.authorizeCheckNode = CheckNode(strokeColor: theme.separatorColor, fillColor: theme.accentColor, foregroundColor: .white, style: .plain)
self.authorizeCheckNode.setIsChecked(true, animated: false)
self.authorizeLabelNode = ASTextNode()
self.authorizeLabelNode.maximumNumberOfLines = 2
self.allowWriteCheckNode = CheckNode(strokeColor: theme.separatorColor, fillColor: theme.accentColor, foregroundColor: .white, style: .plain)
self.allowWriteCheckNode.setIsChecked(true, animated: false)
self.allowWriteLabelNode = ASTextNode()
self.allowWriteLabelNode.maximumNumberOfLines = 2
self.actionNodesSeparator = ASDisplayNode()
self.actionNodesSeparator.isLayerBacked = true
self.actionNodes = actions.map { action -> ChatMessageActionUrlAuthContentActionNode in
return ChatMessageActionUrlAuthContentActionNode(theme: theme, action: action)
}
var actionVerticalSeparators: [ASDisplayNode] = []
if actions.count > 1 {
for _ in 0 ..< actions.count - 1 {
let separatorNode = ASDisplayNode()
separatorNode.isLayerBacked = true
actionVerticalSeparators.append(separatorNode)
}
}
self.actionVerticalSeparators = actionVerticalSeparators
super.init()
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
self.addSubnode(self.authorizeCheckNode)
self.addSubnode(self.authorizeLabelNode)
if requestWriteAccess {
self.addSubnode(self.allowWriteCheckNode)
self.addSubnode(self.allowWriteLabelNode)
}
self.addSubnode(self.actionNodesSeparator)
for actionNode in self.actionNodes {
self.addSubnode(actionNode)
}
for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode)
}
self.authorizeCheckNode.addTarget(target: self, action: #selector(self.authorizePressed))
self.allowWriteCheckNode.addTarget(target: self, action: #selector(self.allowWritePressed))
self.updateTheme(theme)
}
@objc private func authorizePressed() {
self.authorize = !self.authorize
}
@objc private func allowWritePressed() {
self.allowWriteAccess = !self.allowWriteAccess
}
override func updateTheme(_ theme: AlertControllerTheme) {
self.titleNode.attributedText = NSAttributedString(string: strings.Conversation_OpenBotLinkTitle, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
self.textNode.attributedText = formattedText(strings.Conversation_OpenBotLinkText(self.defaultUrl).0, color: theme.primaryColor, textAlignment: .center)
self.authorizeLabelNode.attributedText = formattedText(strings.Conversation_OpenBotLinkLogin(self.domain, self.displayName).0, color: theme.primaryColor)
self.allowWriteLabelNode.attributedText = formattedText(strings.Conversation_OpenBotLinkAllowMessages(self.bot.displayTitle).0, color: theme.primaryColor)
self.actionNodesSeparator.backgroundColor = theme.separatorColor
for actionNode in self.actionNodes {
actionNode.updateTheme(theme)
}
for separatorNode in self.actionVerticalSeparators {
separatorNode.backgroundColor = theme.separatorColor
}
if let size = self.validLayout {
_ = self.updateLayout(size: size, transition: .immediate)
}
}
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
var size = size
size.width = min(size.width, 270.0)
self.validLayout = size
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
let titleSize = self.titleNode.measure(size)
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
origin.y += titleSize.height + 9.0
let textSize = self.textNode.measure(size)
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
origin.y += textSize.height + 16.0
let checkSize = CGSize(width: 32.0, height: 32.0)
let condensedSize = CGSize(width: size.width - 76.0, height: size.height)
var entriesHeight: CGFloat = 0.0
let authorizeSize = self.authorizeLabelNode.measure(condensedSize)
transition.updateFrame(node: self.authorizeLabelNode, frame: CGRect(origin: CGPoint(x: 46.0, y: origin.y), size: authorizeSize))
transition.updateFrame(node: self.authorizeCheckNode, frame: CGRect(origin: CGPoint(x: 7.0, y: origin.y - 7.0), size: checkSize))
origin.y += authorizeSize.height
entriesHeight += authorizeSize.height
if self.allowWriteLabelNode.supernode != nil {
origin.y += 16.0
entriesHeight += 16.0
let allowWriteSize = self.allowWriteLabelNode.measure(condensedSize)
transition.updateFrame(node: self.allowWriteLabelNode, frame: CGRect(origin: CGPoint(x: 46.0, y: origin.y), size: allowWriteSize))
transition.updateFrame(node: self.allowWriteCheckNode, frame: CGRect(origin: CGPoint(x: 7.0, y: origin.y - 7.0), size: checkSize))
origin.y += allowWriteSize.height
entriesHeight += allowWriteSize.height
}
let actionButtonHeight: CGFloat = 44.0
var minActionsWidth: CGFloat = 0.0
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
let actionTitleInsets: CGFloat = 8.0
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
for actionNode in self.actionNodes {
let actionTitleSize = actionNode.titleNode.measure(CGSize(width: maxActionWidth, height: actionButtonHeight))
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
effectiveActionLayout = .vertical
}
switch effectiveActionLayout {
case .horizontal:
minActionsWidth += actionTitleSize.width + actionTitleInsets
case .vertical:
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
}
}
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
var contentWidth = max(titleSize.width, minActionsWidth)
contentWidth = max(contentWidth, 234.0)
var actionsHeight: CGFloat = 0.0
switch effectiveActionLayout {
case .horizontal:
actionsHeight = actionButtonHeight
case .vertical:
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
}
let resultWidth = contentWidth + insets.left + insets.right
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + entriesHeight + actionsHeight + 30.0 + insets.top + insets.bottom)
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
var actionOffset: CGFloat = 0.0
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
var separatorIndex = -1
var nodeIndex = 0
for actionNode in self.actionNodes {
if separatorIndex >= 0 {
let separatorNode = self.actionVerticalSeparators[separatorIndex]
switch effectiveActionLayout {
case .horizontal:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
case .vertical:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
}
}
separatorIndex += 1
let currentActionWidth: CGFloat
switch effectiveActionLayout {
case .horizontal:
if nodeIndex == self.actionNodes.count - 1 {
currentActionWidth = resultSize.width - actionOffset
} else {
currentActionWidth = actionWidth
}
case .vertical:
currentActionWidth = resultSize.width
}
let actionNodeFrame: CGRect
switch effectiveActionLayout {
case .horizontal:
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += currentActionWidth
case .vertical:
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += actionButtonHeight
}
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
nodeIndex += 1
}
return resultSize
}
}
func chatMessageActionUrlAuthController(context: AccountContext, defaultUrl: String, domain: String, bot: Peer, requestWriteAccess: Bool, displayName: String, open: @escaping (Bool, Bool) -> Void) -> AlertController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let theme = presentationData.theme
let strings = presentationData.strings
var contentNode: ChatMessageActionUrlAuthAlertContentNode?
var dismissImpl: ((Bool) -> Void)?
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?(true)
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Conversation_OpenBotLinkOpen, action: {
dismissImpl?(true)
if let contentNode = contentNode {
open(contentNode.authorize, contentNode.allowWriteAccess)
}
})]
contentNode = ChatMessageActionUrlAuthAlertContentNode(theme: AlertControllerTheme(presentationTheme: theme), ptheme: theme, strings: strings, defaultUrl: defaultUrl, domain: domain, bot: bot, requestWriteAccess: requestWriteAccess, displayName: displayName, actions: actions)
let controller = AlertController(theme: AlertControllerTheme(presentationTheme: theme), contentNode: contentNode!)
dismissImpl = { [weak controller] animated in
if animated {
controller?.dismissAnimated()
} else {
controller?.dismiss()
}
}
return controller
}

View File

@ -4,64 +4,165 @@ import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import Lottie
import AVFoundation
import CoreImage
private final class StickerAnimationNode : ASDisplayNode {
private var disposable = MetaDisposable()
var loopCount: Int = 0
private class AlphaFrameFilter: CIFilter {
static var kernel: CIColorKernel? = {
return CIColorKernel(source: """
kernel vec4 alphaFrame(__sample s, __sample m) {
return vec4( s.rgb, m.r );
}
""")
}()
var inputImage: CIImage?
var maskImage: CIImage?
override var outputImage: CIImage? {
let kernel = AlphaFrameFilter.kernel!
guard let inputImage = inputImage, let maskImage = maskImage else {
return nil
}
let args = [inputImage as AnyObject, maskImage as AnyObject]
return kernel.apply(extent: inputImage.extent, arguments: args)
}
}
private func createVideoComposition(for playerItem: AVPlayerItem) -> AVVideoComposition? {
let videoSize = CGSize(width: playerItem.presentationSize.width, height: playerItem.presentationSize.height / 2.0)
if #available(iOSApplicationExtension 9.0, *) {
let composition = AVMutableVideoComposition(asset: playerItem.asset, applyingCIFiltersWithHandler: { request in
let sourceRect = CGRect(origin: .zero, size: videoSize)
let alphaRect = sourceRect.offsetBy(dx: 0, dy: sourceRect.height)
let filter = AlphaFrameFilter()
filter.inputImage = request.sourceImage.cropped(to: alphaRect)
.transformed(by: CGAffineTransform(translationX: 0, y: -sourceRect.height))
filter.maskImage = request.sourceImage.cropped(to: sourceRect)
return request.finish(with: filter.outputImage!, context: nil)
})
composition.renderSize = videoSize
return composition
} else {
return nil
}
}
private final class StickerAnimationNode: ASDisplayNode {
private var account: Account?
private var fileReference: FileMediaReference?
private let disposable = MetaDisposable()
private let fetchDisposable = MetaDisposable()
var playerLayer: AVPlayerLayer {
return self.layer as! AVPlayerLayer
}
var player: AVPlayer? {
get {
if self.isNodeLoaded {
return self.playerLayer.player
} else {
return nil
}
}
set {
if let player = self.playerLayer.player {
player.removeObserver(self, forKeyPath: #keyPath(AVPlayer.rate))
}
self.playerLayer.player = newValue
if let newValue = newValue {
newValue.addObserver(self, forKeyPath: #keyPath(AVPlayer.rate), options: [], context: nil)
}
}
}
private var playerItem: AVPlayerItem? = nil {
willSet {
self.playerItem?.removeObserver(self, forKeyPath: #keyPath(AVPlayerItem.status))
}
didSet {
self.playerItem?.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.status), options: .new, context: nil)
self.setupLooping()
}
}
override init() {
super.init()
self.setViewBlock({
let view = LOTAnimationView()
return view
self.setLayerBlock({
return AVPlayerLayer()
})
self.playerLayer.isHidden = true
if #available(iOSApplicationExtension 9.0, *) {
self.playerLayer.pixelBufferAttributes = [(kCVPixelBufferPixelFormatTypeKey as String): kCVPixelFormatType_32BGRA]
}
}
deinit {
NotificationCenter.default.removeObserver(self.didPlayToEndTimeObsever as Any)
self.player = nil
self.playerItem = nil
self.disposable.dispose()
self.fetchDisposable.dispose()
}
func setSignal(_ signal: Signal<Data, NoError>) {
self.disposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] next in
if let object = try? JSONSerialization.jsonObject(with: next, options: []) as? [AnyHashable: Any], let json = object {
self?.animationView()?.setAnimation(json: json)
func setup(account: Account, fileReference: FileMediaReference) {
self.disposable.set(chatMessageAnimationData(postbox: account.postbox, fileReference: fileReference, synchronousLoad: false).start(next: { [weak self] data in
if let strongSelf = self, data.complete {
let playerItem = AVPlayerItem(url: URL(fileURLWithPath: data.path))
strongSelf.player = AVPlayer(playerItem: playerItem)
strongSelf.playerItem = playerItem
}
}))
self.fetchDisposable.set(fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(fileReference.media.resource)).start())
}
func animationView() -> LOTAnimationView? {
return self.view as? LOTAnimationView
private func setupLooping() {
guard let playerItem = self.playerItem, let player = self.player else {
return
}
self.didPlayToEndTimeObsever = NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: playerItem, queue: nil, using: { _ in
player.seek(to: kCMTimeZero) { _ in
player.play()
}
})
}
private var didPlayToEndTimeObsever: NSObjectProtocol? = nil {
willSet(newObserver) {
if let observer = self.didPlayToEndTimeObsever, self.didPlayToEndTimeObsever !== newObserver {
NotificationCenter.default.removeObserver(observer)
}
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let playerItem = object as? AVPlayerItem, playerItem === self.playerItem {
if case .readyToPlay = playerItem.status, playerItem.videoComposition == nil {
playerItem.videoComposition = createVideoComposition(for: playerItem)
playerItem.seekingWaitsForVideoCompositionRendering = true
}
self.player?.play()
} else if let player = object as? AVPlayer, player === self.player {
if self.playerLayer.isHidden && player.rate > 0.0 {
Queue.mainQueue().after(0.3) {
self.playerLayer.isHidden = false
}
}
} else {
return super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
func play() {
DispatchQueue.main.async {
if let animationView = self.animationView(), !animationView.isAnimationPlaying {
self.loopCount = 2
var completion: ((Bool) -> Void)!
let placeholder: (Bool) -> Void = { [weak animationView] _ in
self.loopCount -= 1
if self.loopCount > 0 {
animationView?.play(completion: completion)
}
}
completion = placeholder
if !animationView.isAnimationPlaying {
animationView.play(completion: completion)
}
}
}
}
func reset() {
DispatchQueue.main.async {
if let animationView = self.animationView() {
animationView.stop()
}
}
}
}
@ -77,8 +178,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
var telegramFile: TelegramMediaFile?
private let fetchDisposable = MetaDisposable()
private let dateAndStatusNode: ChatMessageDateAndStatusNode
private var replyInfoNode: ChatMessageReplyInfoNode?
private var replyBackgroundNode: ASImageNode?
@ -104,10 +203,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.fetchDisposable.dispose()
}
override func didLoad() {
super.didLoad()
@ -141,19 +236,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
for media in item.message.media {
if let telegramFile = media as? TelegramMediaFile {
if self.telegramFile != telegramFile {
let signal = chatMessageAnimationData(postbox: item.context.account.postbox, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: telegramFile), synchronousLoad: false)
|> mapToSignal { data, completed -> Signal<Data, NoError> in
if completed, let data = data {
return .single(data)
} else {
return .complete()
}
}
self.telegramFile = telegramFile
self.animationNode.setSignal(signal)
self.fetchDisposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .message(message: MessageReference(item.message), media: telegramFile)).start())
self.animationNode.play()
self.animationNode.setup(account: item.context.account, fileReference: .message(message: MessageReference(item.message), media: telegramFile))
}
break
@ -454,7 +539,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
if let item = self.item, self.imageNode.frame.contains(location) {
self.animationNode.play()
//self.animationNode.play()
//let _ = item.controllerInteraction.openMessage(item.message, .default)
return
}

View File

@ -346,7 +346,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
loop: for media in self.message.media {
if let telegramFile = media as? TelegramMediaFile {
if GlobalExperimentalSettings.animatedStickers && telegramFile.fileName == "animation.json" {
if let fileName = telegramFile.fileName, fileName.hasSuffix(".tgs"), let size = telegramFile.size, size > 0 && size < 64 * 1024 {
viewClassName = ChatMessageAnimatedStickerItemNode.self
break loop
}

View File

@ -749,8 +749,8 @@ public class ChatMessageItemView: ListViewItemNode {
}
case .payment:
item.controllerInteraction.openCheckoutOrReceipt(item.message.id)
case .urlAuth:
break
case let .urlAuth(url, buttonId):
item.controllerInteraction.requestMessageActionUrlAuth(url, item.message.id, buttonId)
}
}
}

View File

@ -178,7 +178,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self?.openPeerMention(name)
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame in
self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame)
}, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _ in
}, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _ in
self?.openUrl(url)
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {
@ -607,9 +607,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
if let query = strongSelf.filter.query, hasFilter {
text = strongSelf.presentationData.strings.Channel_AdminLog_EmptyFilterQueryText(query).0
} else {
text = isSupergroup ? strongSelf.presentationData.strings.Group_AdminLog_EmptyText : strongSelf.presentationData.strings.Broadcast_AdminLog_EmptyText
}
strongSelf.emptyNode.setup(title: hasFilter ? strongSelf.presentationData.strings.Channel_AdminLog_EmptyFilterTitle : strongSelf.presentationData.strings.Channel_AdminLog_EmptyTitle, text: text)
}

View File

@ -35,6 +35,7 @@ private struct FontAttributes: OptionSet {
static let bold = FontAttributes(rawValue: 1 << 0)
static let italic = FontAttributes(rawValue: 1 << 1)
static let monospace = FontAttributes(rawValue: 1 << 2)
static let strikethrough = FontAttributes(rawValue: 1 << 3)
}
func textAttributedStringForStateText(_ stateText: NSAttributedString, fontSize: CGFloat, textColor: UIColor, accentTextColor: UIColor) -> NSAttributedString {

View File

@ -276,17 +276,18 @@ class ContactListActionItemNode: ListViewItemNode {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.topStripeNode.isHidden = true
strongSelf.bottomStripeNode.isHidden = hideBottomStripe
if !hideBottomStripe {
print("")
}
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: titleOffset, y: floor((contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size)
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 50.0 + UIScreenPixel + UIScreenPixel))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 50.0 + UIScreenPixel + UIScreenPixel))
}
})
}

View File

@ -245,6 +245,13 @@ public class ContactsController: ViewController {
openPeer(peer, false)
}
self.contactsNode.openPeopleNearby = { [weak self] in
if let strongSelf = self {
//let controller = peopleNearbyController(context: strongSelf.context)
//(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
}
}
self.contactsNode.openInvite = { [weak self] in
if let strongSelf = self {
(strongSelf.navigationController as? NavigationController)?.pushViewController(InviteContactsController(context: strongSelf.context))

View File

@ -17,6 +17,7 @@ final class ContactsControllerNode: ASDisplayNode {
var requestDeactivateSearch: (() -> Void)?
var requestOpenPeerFromSearch: ((ContactListPeer) -> Void)?
var openPeopleNearby: (() -> Void)?
var openInvite: (() -> Void)?
private var presentationData: PresentationData
@ -27,7 +28,11 @@ final class ContactsControllerNode: ASDisplayNode {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
var addNearbyImpl: (() -> Void)?
var inviteImpl: (() -> Void)?
//ContactListAdditionalOption(title: presentationData.strings.Contacts_AddPeopleNearby, icon: .generic(UIImage(bundleImageName: "Contact List/PeopleNearbyIcon")!), action: {
// addNearbyImpl?()
//}),
let options = [ContactListAdditionalOption(title: presentationData.strings.Contacts_InviteFriends, icon: .generic(UIImage(bundleImageName: "Contact List/AddMemberIcon")!), action: {
inviteImpl?()
})]
@ -68,6 +73,12 @@ final class ContactsControllerNode: ASDisplayNode {
}
})
addNearbyImpl = { [weak self] in
if let strongSelf = self {
strongSelf.openPeopleNearby?()
}
}
inviteImpl = { [weak self] in
let _ = (DeviceAccess.authorizationStatus(context: context, subject: .contacts)
|> take(1)

View File

@ -570,7 +570,6 @@ func dataAndStorageController(context: AccountContext, focusOnItemTag: DataAndSt
}
let controller = ItemListController(context: context, state: signal)
pushControllerImpl = { [weak controller] c in
if let controller = controller {
(controller.navigationController as? NavigationController)?.pushViewController(c)
@ -579,6 +578,6 @@ func dataAndStorageController(context: AccountContext, focusOnItemTag: DataAndSt
presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a)
}
return controller
}

View File

@ -49,7 +49,7 @@ final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
}
func decode(frame: MediaTrackDecodableFrame, ptsOffset: CMTime?) -> MediaTrackFrame? {
var status = frame.packet.send(toDecoder: self.codecContext)
let status = frame.packet.send(toDecoder: self.codecContext)
if status == 0 {
if self.codecContext.receive(into: self.videoFrame) {
var pts = CMTimeMake(self.videoFrame.pts, frame.pts.timescale)

View File

@ -8,6 +8,8 @@ import Display
import UIKit
import AVFoundation
import WebP
import Lottie
import TelegramUIPrivateModule
public func fetchCachedResourceRepresentation(account: Account, resource: MediaResource, representation: CachedMediaResourceRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
if let representation = representation as? CachedStickerAJpegRepresentation {
@ -16,7 +18,7 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR
if !data.complete {
return .complete()
}
return fetchCachedStickerAJpegRepresentation(account: account, resource: resource, resourceData: data, representation: representation)
return fetchCachedStickerAJpegRepresentation(account: account, resource: resource, resourceData: data, representation: representation)
}
} else if let representation = representation as? CachedScaledImageRepresentation {
return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
@ -105,6 +107,14 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR
return fetchEmojiThumbnailRepresentation(account: account, resource: resource, representation: representation)
} else if let representation = representation as? CachedEmojiRepresentation {
return fetchEmojiRepresentation(account: account, resource: resource, representation: representation)
} else if let representation = representation as? CachedAnimatedStickerRepresentation {
return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
|> mapToSignal { data -> Signal<CachedMediaResourceRepresentationResult, NoError> in
if !data.complete {
return .complete()
}
return fetchAnimatedStickerRepresentation(account: account, resource: resource, resourceData: data, representation: representation)
}
}
return .never()
}
@ -871,3 +881,15 @@ private func fetchEmojiRepresentation(account: Account, resource: MediaResource,
}
}
private func fetchAnimatedStickerRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedAnimatedStickerRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
return Signal({ subscriber in
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
return convertCompressedLottieToCombinedMp4(data: data, size: CGSize(width: 400.0, height: 400.0)).start(next: { path in
subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path))
subscriber.putCompletion()
})
} else {
return EmptyDisposable
}
}) |> runOn(Queue.concurrentDefaultQueue())
}

17
TelegramUI/GZip.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef Telegram_GZip_h
#define Telegram_GZip_h
#import <Foundation/Foundation.h>
#ifdef __cplusplus
extern "C" {
#endif
NSData *TGGZipData(NSData *data, float level);
NSData *TGGUnzipData(NSData *data);
#ifdef __cplusplus
}
#endif
#endif

79
TelegramUI/GZip.m Normal file
View File

@ -0,0 +1,79 @@
#import "GZip.h"
#import <zlib.h>
bool TGIsGzippedData(NSData *data) {
const UInt8 *bytes = (const UInt8 *)data.bytes;
return (data.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b);
}
NSData *TGGZipData(NSData *data, float level) {
if (data.length == 0 || TGIsGzippedData(data)) {
return data;
}
z_stream stream;
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.opaque = Z_NULL;
stream.avail_in = (uint)data.length;
stream.next_in = (Bytef *)(void *)data.bytes;
stream.total_out = 0;
stream.avail_out = 0;
static const NSUInteger ChunkSize = 16384;
NSMutableData *output = nil;
int compression = (level < 0.0f) ? Z_DEFAULT_COMPRESSION : (int)(roundf(level * 9));
if (deflateInit2(&stream, compression, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
output = [NSMutableData dataWithLength:ChunkSize];
while (stream.avail_out == 0) {
if (stream.total_out >= output.length) {
output.length += ChunkSize;
}
stream.next_out = (uint8_t *)output.mutableBytes + stream.total_out;
stream.avail_out = (uInt)(output.length - stream.total_out);
deflate(&stream, Z_FINISH);
}
deflateEnd(&stream);
output.length = stream.total_out;
}
return output;
}
NSData *TGGUnzipData(NSData *data)
{
if (data.length == 0 || !TGIsGzippedData(data)) {
return data;
}
z_stream stream;
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.avail_in = (uint)data.length;
stream.next_in = (Bytef *)data.bytes;
stream.total_out = 0;
stream.avail_out = 0;
NSMutableData *output = nil;
if (inflateInit2(&stream, 47) == Z_OK) {
int status = Z_OK;
output = [NSMutableData dataWithCapacity:data.length * 2];
while (status == Z_OK) {
if (stream.total_out >= output.length) {
output.length += data.length / 2;
}
stream.next_out = (uint8_t *)output.mutableBytes + stream.total_out;
stream.avail_out = (uInt)(output.length - stream.total_out);
status = inflate (&stream, Z_SYNC_FLUSH);
}
if (inflateEnd(&stream) == Z_OK) {
if (status == Z_STREAM_END) {
output.length = stream.total_out;
}
}
}
return output;
}

View File

@ -0,0 +1,205 @@
import Foundation
import Display
import AsyncDisplayKit
import SwiftSignalKit
class ItemListPlaceholderItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let text: String
let sectionId: ItemListSectionId
let style: ItemListStyle
let tag: ItemListItemTag?
init(theme: PresentationTheme, text: String, sectionId: ItemListSectionId, style: ItemListStyle, tag: ItemListItemTag? = nil) {
self.theme = theme
self.text = text
self.sectionId = sectionId
self.style = style
self.tag = tag
}
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = ItemListPlaceholderItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply() })
})
}
}
}
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? ItemListPlaceholderItemNode {
let makeLayout = nodeValue.asyncLayout()
async {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async {
completion(layout, { _ in
apply()
})
}
}
}
}
}
let selectable = false
}
private let textFont = Font.regular(13.0)
class ItemListPlaceholderItemNode: ListViewItemNode, ItemListItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
let textNode: TextNode
private var item: ItemListPlaceholderItem?
override var canBeSelected: Bool {
return false
}
var tag: ItemListItemTag? {
return self.item?.tag
}
init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.backgroundColor = .white
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.textNode = TextNode()
self.textNode.isUserInteractionEnabled = false
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.textNode)
}
func asyncLayout() -> (_ item: ItemListPlaceholderItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeTextLayout = TextNode.asyncLayout(self.textNode)
let currentItem = self.item
return { item, params, neighbors in
var updatedTheme: PresentationTheme?
if currentItem?.theme !== item.theme {
updatedTheme = item.theme
}
let contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor
let leftInset = 16.0 + params.leftInset
let rightInset = 16.0 + params.rightInset
let textColor = item.theme.list.itemSecondaryTextColor
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.text, font: textFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let height: CGFloat = 34.0 + textLayout.size.height
switch item.style {
case .plain:
itemBackgroundColor = item.theme.list.plainBackgroundColor
itemSeparatorColor = item.theme.list.itemPlainSeparatorColor
contentSize = CGSize(width: params.width, height: height)
insets = itemListNeighborsPlainInsets(neighbors)
case .blocks:
itemBackgroundColor = item.theme.list.itemBlocksBackgroundColor
itemSeparatorColor = item.theme.list.itemBlocksSeparatorColor
contentSize = CGSize(width: params.width, height: height)
insets = itemListNeighborsGroupedInsets(neighbors)
}
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
if let strongSelf = self {
strongSelf.item = item
if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
}
let _ = textApply()
switch item.style {
case .plain:
if strongSelf.backgroundNode.supernode != nil {
strongSelf.backgroundNode.removeFromSupernode()
}
if strongSelf.topStripeNode.supernode != nil {
strongSelf.topStripeNode.removeFromSupernode()
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0)
}
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
case .blocks:
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
}
if strongSelf.topStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
strongSelf.topStripeNode.isHidden = false
}
let bottomStripeInset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = leftInset
default:
bottomStripeInset = 0.0
}
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
}
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 17.0), size: textLayout.size)
}
})
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
}

View File

@ -142,7 +142,7 @@ final class MultiplexedVideoNode: UIScrollView, UIScrollViewDelegate {
deinit {
self.displayLink.invalidate()
self.displayLink.isPaused = true
for(_, disposable) in statusDisposable {
for(_, disposable) in self.statusDisposable {
disposable.dispose()
}
for (_, value) in self.visibleLayers {

View File

@ -55,8 +55,30 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec
} else {
return false
}
}, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
}, presentController: { _, _ in }, navigationController: {
}, openPeer: { _, _, _ in
}, openPeerMention: { _ in
}, openMessageContextMenu: { _, _, _, _ in
}, navigateToMessage: { _, _ in
}, clickThroughMessage: {
}, toggleMessagesSelection: { _, _ in
}, sendMessage: { _ in
}, sendSticker: { _, _ in
}, sendGif: { _ in
}, requestMessageActionCallback: { _, _, _ in
}, requestMessageActionUrlAuth: { _, _, _ in
}, activateSwitchInline: { _, _ in
}, openUrl: { _, _, _ in
}, shareCurrentLocation: {
}, shareAccountContact: {
}, sendBotCommand: { _, _ in
}, openInstantPage: { _, _ in
}, openWallpaper: { _ in
}, openHashtag: { _, _ in
}, updateInputState: { _ in
}, updateInputMode: { _ in
}, openMessageShareMenu: { _ in
}, presentController: { _, _ in
}, navigationController: {
return nil
}, presentGlobalOverlayController: { _, _ in
}, callPeer: { _ in
@ -76,8 +98,7 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec
}, seekToTimecode: { _, _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
pollActionState: ChatInterfacePollActionState())
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState())
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)

View File

@ -197,14 +197,7 @@ final public class PasscodeEntryController: ViewController {
self.controllerNode.activateInput()
if self.arguments.animated {
let iconFrame = self.arguments.lockIconInitialFrame()
if !iconFrame.isEmpty {
Queue.mainQueue().after(0.5) {
serviceSoundManager.playLockSound()
}
}
self.controllerNode.animateIn(iconFrame: iconFrame, completion: { [weak self] in
self.controllerNode.animateIn(iconFrame: self.arguments.lockIconInitialFrame(), completion: { [weak self] in
self?.presentationCompleted?()
})
} else {

View File

@ -182,6 +182,7 @@ public class PeerMediaCollectionController: TelegramController {
},sendSticker: { _, _ in
}, sendGif: { _ in
}, requestMessageActionCallback: { _, _, _ in
}, requestMessageActionUrlAuth: { _, _, _ in
}, activateSwitchInline: { _, _ in
}, openUrl: { [weak self] url, _, external in
self?.openUrl(url, external: external ?? false)

View File

@ -647,5 +647,6 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
controller.present(c, in: .window(.root), with: p)
}
}
return controller
}

View File

@ -97,7 +97,6 @@ private func chatMessageStickerDatas(postbox: Postbox, file: TelegramMediaFile,
private func chatMessageStickerPackThumbnailData(postbox: Postbox, representation: TelegramMediaImageRepresentation, synchronousLoad: Bool) -> Signal<Data?, NoError> {
let resource = representation.resource
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedStickerAJpegRepresentation(size: CGSize(width: 160.0, height: 160.0)), complete: false, fetch: false, attemptSynchronously: synchronousLoad)
return maybeFetched
@ -131,7 +130,21 @@ private func chatMessageStickerPackThumbnailData(postbox: Postbox, representatio
}
}
func chatMessageAnimationData(postbox: Postbox, fileReference: FileMediaReference, synchronousLoad: Bool) -> Signal<(Data?, Bool), NoError> {
func chatMessageAnimationData(postbox: Postbox, fileReference: FileMediaReference, synchronousLoad: Bool) -> Signal<MediaResourceData, NoError> {
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(fileReference.media.resource, representation: CachedAnimatedStickerRepresentation(), pathExtension: "mp4", complete: false, fetch: false, attemptSynchronously: synchronousLoad)
return maybeFetched
|> take(1)
|> mapToSignal { maybeData in
if maybeData.complete {
return .single(maybeData)
} else {
return postbox.mediaBox.cachedResourceRepresentation(fileReference.media.resource, representation: CachedAnimatedStickerRepresentation(), pathExtension: "mp4", complete: false)
}
}
}
func chatMessageAnimatedStrickerBackingData(postbox: Postbox, fileReference: FileMediaReference, synchronousLoad: Bool) -> Signal<(Data?, Bool), NoError> {
let resource = fileReference.media.resource
let maybeFetched = postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
@ -144,8 +157,8 @@ func chatMessageAnimationData(postbox: Postbox, fileReference: FileMediaReferenc
return .single((loadedData, true))
} else {
let fullSizeData = postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
|> map { next -> (Data?, Bool) in
return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)
|> map { next -> (Data?, Bool) in
return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)
}
return fullSizeData
}

View File

@ -22,6 +22,7 @@ module TelegramUIPrivateModule {
header "../EDSunriseSet.h"
header "../TGBridgeAudioDecoder.h"
header "../TGBridgeAudioEncoder.h"
header "../GZip.h"
private header "../../third-party/libjpeg-turbo/turbojpeg.h"
private header "../../third-party/libjpeg-turbo/jpeglib.h"
}