Merge branch 'master' into beta

This commit is contained in:
Ali 2020-08-05 00:40:55 +04:00
commit ba1edc3cf6
640 changed files with 175089 additions and 5088 deletions

View file

@ -4788,6 +4788,7 @@ Any member of this group will be able to see messages in the channel.";
"ContactList.Context.SendMessage" = "Send Message";
"ContactList.Context.StartSecretChat" = "Start Secret Chat";
"ContactList.Context.Call" = "Call";
"ContactList.Context.VideoCall" = "Video Call";
"Theme.Context.Apply" = "Apply";
@ -5332,7 +5333,7 @@ Any member of this group will be able to see messages in the channel.";
"PeerInfo.ButtonMessage" = "Message";
"PeerInfo.ButtonDiscuss" = "Discuss";
"PeerInfo.ButtonCall" = "Call";
"PeerInfo.ButtonVideoCall" = "Video Call";
"PeerInfo.ButtonVideoCall" = "Video";
"PeerInfo.ButtonMute" = "Mute";
"PeerInfo.ButtonUnmute" = "Unmute";
"PeerInfo.ButtonMore" = "More";
@ -5710,3 +5711,8 @@ Any member of this group will be able to see messages in the channel.";
"Cache.MaximumCacheSize" = "Maximum Cache Size";
"Cache.NoLimit" = "No Limit";
"Cache.MaximumCacheSizeHelp" = "If your cache size exceeds this limit, the oldest media will be deleted.\n\nAll media will stay in the Telegram cloud and can be re-downloaded if you need it again.";
"Stats.MessageTitle" = "Message Statistics";
"Stats.MessageOverview" = "Overview";
"Stats.MessageInteractionsTitle" = "Interactions";
"Stats.MessagePublicForwardsTitle" = "Public Shares";

View file

@ -647,6 +647,7 @@ public protocol AccountContext: class {
var currentLimitsConfiguration: Atomic<LimitsConfiguration> { get }
var currentContentSettings: Atomic<ContentSettings> { get }
var currentAppConfiguration: Atomic<AppConfiguration> { get }
func storeSecureIdPassword(password: String)
func getStoredSecureIdPassword() -> String?

View file

@ -47,13 +47,19 @@ public func messageMediaFileCancelInteractiveFetch(context: AccountContext, mess
context.fetchManager.cancelInteractiveFetches(category: fetchCategoryForFile(file), location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: file.resource)
}
public func messageMediaImageInteractiveFetched(context: AccountContext, message: Message, image: TelegramMediaImage, resource: MediaResource, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
return messageMediaImageInteractiveFetched(fetchManager: context.fetchManager, messageId: message.id, messageReference: MessageReference(message), image: image, resource: resource, userInitiated: true, priority: .userInitiated, storeToDownloadsPeerType: storeToDownloadsPeerType)
public func messageMediaImageInteractiveFetched(context: AccountContext, message: Message, image: TelegramMediaImage, resource: MediaResource, range: Range<Int>? = nil, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
return messageMediaImageInteractiveFetched(fetchManager: context.fetchManager, messageId: message.id, messageReference: MessageReference(message), image: image, resource: resource, range: range, userInitiated: true, priority: .userInitiated, storeToDownloadsPeerType: storeToDownloadsPeerType)
}
public func messageMediaImageInteractiveFetched(fetchManager: FetchManager, messageId: MessageId, messageReference: MessageReference, image: TelegramMediaImage, resource: MediaResource, userInitiated: Bool, priority: FetchManagerPriority, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
public func messageMediaImageInteractiveFetched(fetchManager: FetchManager, messageId: MessageId, messageReference: MessageReference, image: TelegramMediaImage, resource: MediaResource, range: Range<Int>? = nil, userInitiated: Bool, priority: FetchManagerPriority, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
let mediaReference = AnyMediaReference.message(message: messageReference, media: image)
return fetchManager.interactivelyFetched(category: .image, location: .chat(messageId.peerId), locationKey: .messageId(messageId), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(resource), ranges: FetchCompleteRange, statsCategory: .image, elevatedPriority: false, userInitiated: userInitiated, priority: priority, storeToDownloadsPeerType: storeToDownloadsPeerType)
let ranges: IndexSet
if let range = range {
ranges = IndexSet(integersIn: range)
} else {
ranges = FetchCompleteRange
}
return fetchManager.interactivelyFetched(category: .image, location: .chat(messageId.peerId), locationKey: .messageId(messageId), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(resource), ranges: ranges, statsCategory: .image, elevatedPriority: false, userInitiated: userInitiated, priority: priority, storeToDownloadsPeerType: storeToDownloadsPeerType)
}
public func messageMediaImageCancelInteractiveFetch(context: AccountContext, messageId: MessageId, image: TelegramMediaImage, resource: MediaResource) {

View file

@ -48,7 +48,7 @@ public struct PresentationCallState: Equatable {
case notAvailable
case possible
case outgoingRequested
case incomingRequested
case incomingRequested(sendsVideo: Bool)
case active
}
@ -81,17 +81,20 @@ public final class PresentationCallVideoView {
public let getOrientation: () -> Orientation
public let setOnOrientationUpdated: (((Orientation) -> Void)?) -> Void
public let setOnIsMirroredUpdated: (((Bool) -> Void)?) -> Void
public init(
view: UIView,
setOnFirstFrameReceived: @escaping ((() -> Void)?) -> Void,
getOrientation: @escaping () -> Orientation,
setOnOrientationUpdated: @escaping (((Orientation) -> Void)?) -> Void
setOnOrientationUpdated: @escaping (((Orientation) -> Void)?) -> Void,
setOnIsMirroredUpdated: @escaping (((Bool) -> Void)?) -> Void
) {
self.view = view
self.setOnFirstFrameReceived = setOnFirstFrameReceived
self.getOrientation = getOrientation
self.setOnOrientationUpdated = setOnOrientationUpdated
self.setOnIsMirroredUpdated = setOnIsMirroredUpdated
}
}
@ -133,5 +136,5 @@ public protocol PresentationCall: class {
public protocol PresentationCallManager: class {
var currentCallSignal: Signal<PresentationCall?, NoError> { get }
func requestCall(account: Account, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult
func requestCall(context: AccountContext, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult
}

View file

@ -0,0 +1,39 @@
import SyncCore
public struct VideoCallsConfiguration: Equatable {
public enum VideoCallsSupport {
case disabled
case full
case onlyVideo
}
public var videoCallsSupport: VideoCallsSupport
public init(appConfiguration: AppConfiguration) {
var videoCallsSupport: VideoCallsSupport = .full
if let data = appConfiguration.data, let value = data["video_calls_support"] as? String {
switch value {
case "disabled":
videoCallsSupport = .disabled
case "full":
videoCallsSupport = .full
case "only_video":
videoCallsSupport = .onlyVideo
default:
videoCallsSupport = .full
}
}
self.videoCallsSupport = videoCallsSupport
}
}
public extension VideoCallsConfiguration {
var areVideoCallsEnabled: Bool {
switch self.videoCallsSupport {
case .disabled:
return false
case .full, .onlyVideo:
return true
}
}
}

View file

@ -5,8 +5,10 @@ import Display
import AsyncDisplayKit
import RLottieBinding
import GZip
import YuvConversion
private let sharedQueue = Queue()
private let sharedStoreQueue = Queue.concurrentDefaultQueue()
private class AnimatedStickerNodeDisplayEvents: ASDisplayNode {
private var value: Bool = false
@ -46,7 +48,7 @@ private class AnimatedStickerNodeDisplayEvents: ASDisplayNode {
public enum AnimatedStickerMode {
case cached
case direct
case direct(cachePathPrefix: String?)
}
public enum AnimatedStickerPlaybackPosition {
@ -267,11 +269,321 @@ public final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource
}
}
private func wrappedWrite(_ fd: Int32, _ data: UnsafeRawPointer, _ count: Int) -> Int {
return write(fd, data, count)
}
private func wrappedRead(_ fd: Int32, _ data: UnsafeMutableRawPointer, _ count: Int) -> Int {
return read(fd, data, count)
}
//TODO: separate ManagedFile into its own module
private final class ManagedFileImpl {
enum Mode {
case read
case readwrite
case append
}
private let queue: Queue?
private let fd: Int32
private let mode: Mode
init?(queue: Queue?, path: String, mode: Mode) {
if let queue = queue {
assert(queue.isCurrent())
}
self.queue = queue
self.mode = mode
let fileMode: Int32
let accessMode: UInt16
switch mode {
case .read:
fileMode = O_RDONLY
accessMode = S_IRUSR
case .readwrite:
fileMode = O_RDWR | O_CREAT
accessMode = S_IRUSR | S_IWUSR
case .append:
fileMode = O_WRONLY | O_CREAT | O_APPEND
accessMode = S_IRUSR | S_IWUSR
}
let fd = open(path, fileMode, accessMode)
if fd >= 0 {
self.fd = fd
} else {
return nil
}
}
deinit {
if let queue = self.queue {
assert(queue.isCurrent())
}
close(self.fd)
}
public func write(_ data: UnsafeRawPointer, count: Int) -> Int {
if let queue = self.queue {
assert(queue.isCurrent())
}
return wrappedWrite(self.fd, data, count)
}
public func read(_ data: UnsafeMutableRawPointer, _ count: Int) -> Int {
if let queue = self.queue {
assert(queue.isCurrent())
}
return wrappedRead(self.fd, data, count)
}
public func readData(count: Int) -> Data {
if let queue = self.queue {
assert(queue.isCurrent())
}
var result = Data(count: count)
result.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Void in
let readCount = self.read(bytes, count)
assert(readCount == count)
}
return result
}
public func seek(position: Int64) {
if let queue = self.queue {
assert(queue.isCurrent())
}
lseek(self.fd, position, SEEK_SET)
}
public func truncate(count: Int64) {
if let queue = self.queue {
assert(queue.isCurrent())
}
ftruncate(self.fd, count)
}
public func getSize() -> Int? {
if let queue = self.queue {
assert(queue.isCurrent())
}
var value = stat()
if fstat(self.fd, &value) == 0 {
return Int(value.st_size)
} else {
return nil
}
}
public func sync() {
if let queue = self.queue {
assert(queue.isCurrent())
}
fsync(self.fd)
}
}
private func compressFrame(width: Int, height: Int, rgbData: Data) -> Data? {
let bytesPerRow = rgbData.count / height
let yuvaPixelsPerAlphaRow = (Int(width) + 1) & (~1)
assert(yuvaPixelsPerAlphaRow % 2 == 0)
let yuvaLength = Int(width) * Int(height) * 2 + yuvaPixelsPerAlphaRow * Int(height) / 2
var yuvaFrameData = malloc(yuvaLength)!
defer {
free(yuvaFrameData)
}
memset(yuvaFrameData, 0, yuvaLength)
var compressedFrameData = Data(count: yuvaLength)
let compressedFrameDataLength = compressedFrameData.count
let scratchData = malloc(compression_encode_scratch_buffer_size(COMPRESSION_LZFSE))!
defer {
free(scratchData)
}
var rgbData = rgbData
rgbData.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) -> Void in
if let baseAddress = buffer.baseAddress {
encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), baseAddress.assumingMemoryBound(to: UInt8.self), Int32(width), Int32(height), Int32(bytesPerRow))
}
}
var maybeResultSize: Int?
compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
let length = compression_encode_buffer(bytes, compressedFrameDataLength, yuvaFrameData.assumingMemoryBound(to: UInt8.self), yuvaLength, scratchData, COMPRESSION_LZFSE)
maybeResultSize = length
}
guard let resultSize = maybeResultSize else {
return nil
}
compressedFrameData.count = resultSize
return compressedFrameData
}
private final class AnimatedStickerDirectFrameSourceCache {
private let queue: Queue
private let storeQueue: Queue
private let file: ManagedFileImpl
private let frameCount: Int
private let width: Int
private let height: Int
private var isStoringFrames = Set<Int>()
private var scratchBuffer: Data
private var decodeBuffer: Data
init?(queue: Queue, pathPrefix: String, width: Int, height: Int, frameCount: Int) {
self.queue = queue
self.storeQueue = sharedStoreQueue
self.frameCount = frameCount
self.width = width
self.height = height
let path = "\(pathPrefix)_\(width):\(height).stickerframecache"
var file = ManagedFileImpl(queue: queue, path: path, mode: .readwrite)
if let file = file {
self.file = file
} else {
let _ = try? FileManager.default.removeItem(atPath: path)
file = ManagedFileImpl(queue: queue, path: path, mode: .readwrite)
if let file = file {
self.file = file
} else {
return nil
}
}
self.scratchBuffer = Data(count: compression_decode_scratch_buffer_size(COMPRESSION_LZFSE))
let yuvaPixelsPerAlphaRow = (Int(width) + 1) & (~1)
let yuvaLength = Int(width) * Int(height) * 2 + yuvaPixelsPerAlphaRow * Int(height) / 2
self.decodeBuffer = Data(count: yuvaLength)
self.initializeFrameTable()
}
private func initializeFrameTable() {
if let size = self.file.getSize(), size >= self.frameCount * 4 * 2 {
} else {
self.file.truncate(count: 0)
for _ in 0 ..< self.frameCount {
var zero: Int32 = 0
let _ = self.file.write(&zero, count: 4)
let _ = self.file.write(&zero, count: 4)
}
}
}
private func readFrameRange(index: Int) -> Range<Int>? {
if index < 0 || index >= self.frameCount {
return nil
}
self.file.seek(position: Int64(index * 4 * 2))
var offset: Int32 = 0
var length: Int32 = 0
if self.file.read(&offset, 4) != 4 {
return nil
}
if self.file.read(&length, 4) != 4 {
return nil
}
if length == 0 {
return nil
}
if length < 0 || offset < 0 {
return nil
}
return (Int(offset) ..< Int(offset + length))
}
func storeUncompressedRgbFrame(index: Int, rgbData: Data) {
if index < 0 || index >= self.frameCount {
return
}
if self.isStoringFrames.contains(index) {
return
}
self.isStoringFrames.insert(index)
let width = self.width
let height = self.height
let queue = self.queue
self.storeQueue.async { [weak self] in
let compressedData = compressFrame(width: width, height: height, rgbData: rgbData)
queue.async {
guard let strongSelf = self else {
return
}
guard let currentSize = strongSelf.file.getSize() else {
return
}
guard let compressedData = compressedData else {
return
}
strongSelf.file.seek(position: Int64(index * 4 * 2))
var offset = Int32(currentSize)
var length = Int32(compressedData.count)
let _ = strongSelf.file.write(&offset, count: 4)
let _ = strongSelf.file.write(&length, count: 4)
strongSelf.file.seek(position: Int64(currentSize))
compressedData.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in
if let baseAddress = buffer.baseAddress {
let _ = strongSelf.file.write(baseAddress, count: Int(length))
}
}
}
}
}
func readUncompressedYuvFrame(index: Int) -> Data? {
if index < 0 || index >= self.frameCount {
return nil
}
guard let range = self.readFrameRange(index: index) else {
return nil
}
self.file.seek(position: Int64(range.lowerBound))
let length = range.upperBound - range.lowerBound
let compressedData = self.file.readData(count: length)
if compressedData.count != length {
return nil
}
var frameData: Data?
let decodeBufferLength = self.decodeBuffer.count
compressedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
self.scratchBuffer.withUnsafeMutableBytes { (scratchBytes: UnsafeMutablePointer<UInt8>) -> Void in
self.decodeBuffer.withUnsafeMutableBytes { (decodeBytes: UnsafeMutablePointer<UInt8>) -> Void in
let resultLength = compression_decode_buffer(decodeBytes, decodeBufferLength, bytes, length, UnsafeMutableRawPointer(scratchBytes), COMPRESSION_LZFSE)
frameData = Data(bytes: decodeBytes, count: resultLength)
}
}
}
return frameData
}
}
private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource {
private let queue: Queue
private let data: Data
private let width: Int
private let height: Int
private let cache: AnimatedStickerDirectFrameSourceCache?
private let bytesPerRow: Int
let frameCount: Int
let frameRate: Int
@ -282,7 +594,7 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource
return self.currentFrame % self.frameCount
}
init?(queue: Queue, data: Data, width: Int, height: Int) {
init?(queue: Queue, data: Data, width: Int, height: Int, cachePathPrefix: String?) {
self.queue = queue
self.data = data
self.width = width
@ -294,8 +606,13 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource
return nil
}
self.animation = animation
self.frameCount = Int(animation.frameCount)
let frameCount = Int(animation.frameCount)
self.frameCount = frameCount
self.frameRate = Int(animation.frameRate)
self.cache = cachePathPrefix.flatMap { cachePathPrefix in
AnimatedStickerDirectFrameSourceCache(queue: queue, pathPrefix: cachePathPrefix, width: width, height: height, frameCount: frameCount)
}
}
deinit {
@ -306,12 +623,19 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource
let frameIndex = self.currentFrame % self.frameCount
self.currentFrame += 1
if draw {
var frameData = Data(count: self.bytesPerRow * self.height)
frameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
memset(bytes, 0, self.bytesPerRow * self.height)
self.animation.renderFrame(with: Int32(frameIndex), into: bytes, width: Int32(self.width), height: Int32(self.height), bytesPerRow: Int32(self.bytesPerRow))
if let cache = self.cache, let yuvData = cache.readUncompressedYuvFrame(index: frameIndex) {
return AnimatedStickerFrame(data: yuvData, type: .yuva, width: self.width, height: self.height, bytesPerRow: 0, index: frameIndex, isLastFrame: frameIndex == self.frameCount - 1)
} else {
var frameData = Data(count: self.bytesPerRow * self.height)
frameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
memset(bytes, 0, self.bytesPerRow * self.height)
self.animation.renderFrame(with: Int32(frameIndex), into: bytes, width: Int32(self.width), height: Int32(self.height), bytesPerRow: Int32(self.bytesPerRow))
}
if let cache = self.cache {
cache.storeUncompressedRgbFrame(index: frameIndex, rgbData: frameData)
}
return AnimatedStickerFrame(data: frameData, type: .argb, width: self.width, height: self.height, bytesPerRow: self.bytesPerRow, index: frameIndex, isLastFrame: frameIndex == self.frameCount - 1)
}
return AnimatedStickerFrame(data: frameData, type: .argb, width: self.width, height: self.height, bytesPerRow: self.bytesPerRow, index: frameIndex, isLastFrame: frameIndex == self.frameCount - 1)
} else {
return nil
}
@ -409,7 +733,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
private let timer = Atomic<SwiftSignalKit.Timer?>(value: nil)
private let frameSource = Atomic<QueueLocalObject<AnimatedStickerFrameSourceWrapper>?>(value: nil)
private var directData: (Data, String, Int, Int)?
private var directData: (Data, String, Int, Int, String?)?
private var cachedData: (Data, Bool)?
private var renderer: (AnimationRenderer & ASDisplayNode)?
@ -479,13 +803,13 @@ public final class AnimatedStickerNode: ASDisplayNode {
}
self.playbackMode = playbackMode
switch mode {
case .direct:
case let .direct(cachePathPrefix):
let f: (String) -> Void = { [weak self] path in
guard let strongSelf = self else {
return
}
if let directData = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
strongSelf.directData = (directData, path, width, height)
strongSelf.directData = (directData, path, width, height, cachePathPrefix)
}
if case let .still(position) = playbackMode {
strongSelf.seekTo(position)
@ -568,7 +892,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
if maybeFrameSource == nil {
let notifyUpdated: (() -> Void)? = nil
if let directData = directData {
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3)
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3, cachePathPrefix: directData.4)
} else if let (cachedData, cachedDataComplete) = cachedData {
if #available(iOS 9.0, *) {
maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData, complete: cachedDataComplete, notifyUpdated: {
@ -640,7 +964,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
var maybeFrameSource: AnimatedStickerFrameSource?
let notifyUpdated: (() -> Void)? = nil
if let directData = directData {
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3)
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3, cachePathPrefix: directData.4)
} else if let (cachedData, cachedDataComplete) = cachedData {
if #available(iOS 9.0, *) {
maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData, complete: cachedDataComplete, notifyUpdated: {
@ -730,7 +1054,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
} else {
var maybeFrameSource: AnimatedStickerFrameSource?
if let directData = directData {
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3)
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3, cachePathPrefix: directData.4)
if case .end = position {
maybeFrameSource?.skipToEnd()
}

View file

@ -8,8 +8,13 @@ import YuvConversion
final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer {
func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, completion: @escaping () -> Void) {
queue.async { [weak self] in
let calculatedBytesPerRow = (4 * Int(width) + 15) & (~15)
assert(bytesPerRow == calculatedBytesPerRow)
switch type {
case .argb:
let calculatedBytesPerRow = (4 * Int(width) + 15) & (~15)
assert(bytesPerRow == calculatedBytesPerRow)
case .yuva:
break
}
let image = generateImagePixel(CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, pixelGenerator: { _, pixelData, bytesPerRow in
switch type {

View file

@ -277,7 +277,7 @@ public final class CallListController: ViewController {
return
}
let callResult = strongSelf.context.sharedContext.callManager?.requestCall(account: strongSelf.context.account, peerId: peerId, isVideo: isVideo, endCurrentIfAny: false)
let callResult = strongSelf.context.sharedContext.callManager?.requestCall(context: strongSelf.context, peerId: peerId, isVideo: isVideo, endCurrentIfAny: false)
if let callResult = callResult {
if case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == peerId {
@ -291,7 +291,7 @@ public final class CallListController: ViewController {
if let strongSelf = self, let peer = peer, let current = current {
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
if let strongSelf = self {
let _ = strongSelf.context.sharedContext.callManager?.requestCall(account: strongSelf.context.account, peerId: peerId, isVideo: isVideo, endCurrentIfAny: true)
let _ = strongSelf.context.sharedContext.callManager?.requestCall(context: strongSelf.context, peerId: peerId, isVideo: isVideo, endCurrentIfAny: true)
began?()
}
})]), in: .window(.root))

View file

@ -68,7 +68,7 @@ final class ChatListEmptyNode: ASDisplayNode {
animationName = "ChatListEmpty"
}
if let path = getAppBundle().path(forResource: animationName, ofType: "tgs") {
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, playbackMode: .once, mode: .direct)
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
self.animationSize = CGSize(width: 124.0, height: 124.0)
self.animationNode.visibility = true
}

View file

@ -126,7 +126,7 @@ class ChatListFilterSettingsHeaderItemNode: ListViewItemNode {
animationName = "ChatListNewFolder"
}
if let path = getAppBundle().path(forResource: animationName, ofType: "tgs") {
strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 192, height: 192, playbackMode: .once, mode: .direct)
strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 192, height: 192, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
strongSelf.animationNode.visibility = true
}
}

View file

@ -113,15 +113,17 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
}
var canVideoCall = false
if canCall {
if context.sharedContext.immediateExperimentalUISettings.videoCalls {
canVideoCall = true
if let cachedUserData = transaction.getPeerCachedData(peerId: peerId) as? CachedUserData {
if cachedUserData.videoCallsAvailable {
canVideoCall = true
}
}
}
if canCall {
items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_Call, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) }, action: { _, f in
if let contactsController = contactsController {
let callResult = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peerId, isVideo: false, endCurrentIfAny: false)
let callResult = context.sharedContext.callManager?.requestCall(context: context, peerId: peerId, isVideo: false, endCurrentIfAny: false)
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == peerId {
context.sharedContext.navigateToCurrentCall()
@ -133,7 +135,7 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
|> deliverOnMainQueue).start(next: { [weak contactsController] peer, current in
if let contactsController = contactsController, let peer = peer, let current = current {
contactsController.present(textAlertController(context: context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
let _ = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peerId, isVideo: false, endCurrentIfAny: true)
let _ = context.sharedContext.callManager?.requestCall(context: context, peerId: peerId, isVideo: false, endCurrentIfAny: true)
})]), in: .window(.root))
}
})
@ -144,10 +146,9 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
})))
}
if canVideoCall {
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Video Call", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) }, action: { _, f in
items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_VideoCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) }, action: { _, f in
if let contactsController = contactsController {
let callResult = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peerId, isVideo: true, endCurrentIfAny: false)
let callResult = context.sharedContext.callManager?.requestCall(context: context, peerId: peerId, isVideo: true, endCurrentIfAny: false)
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == peerId {
context.sharedContext.navigateToCurrentCall()
@ -159,7 +160,7 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
|> deliverOnMainQueue).start(next: { [weak contactsController] peer, current in
if let contactsController = contactsController, let peer = peer, let current = current {
contactsController.present(textAlertController(context: context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
let _ = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peerId, isVideo: true, endCurrentIfAny: true)
let _ = context.sharedContext.callManager?.requestCall(context: context, peerId: peerId, isVideo: true, endCurrentIfAny: true)
})]), in: .window(.root))
}
})

View file

@ -5,6 +5,9 @@ static_library(
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/MozjpegBinding:MozjpegBinding",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",

View file

@ -4,8 +4,11 @@ swift_library(
name = "ImageCompression",
module_name = "ImageCompression",
srcs = glob([
"Sources/**/*.swift",
"Sources/**/*.swift",
]),
deps = [
"//submodules/MozjpegBinding:MozjpegBinding",
],
visibility = [
"//visibility:public",
],

View file

@ -1,8 +1,19 @@
import Foundation
import AVFoundation
import UIKit
import MozjpegBinding
public func extractImageExtraScans(_ data: Data) -> [Int] {
return extractJPEGDataScans(data).map { item in
return item.intValue
}
}
public func compressImageToJPEG(_ image: UIImage, quality: Float) -> Data? {
if let result = compressJPEGData(image) {
return result
}
let data = NSMutableData()
guard let destination = CGImageDestinationCreateWithData(data as CFMutableData, "public.jpeg" as CFString, 1, nil) else {
return nil
@ -46,3 +57,7 @@ public func compressImage(_ image: UIImage, quality: Float) -> Data? {
return data as Data
}
public func compressImageMiniThumbnail(_ image: UIImage) -> Data? {
return compressMiniThumbnail(image)
}

View file

@ -118,7 +118,7 @@ public struct InstantPageGalleryEntry: Equatable {
var representations: [TelegramMediaImageRepresentation] = []
representations.append(contentsOf: file.previewRepresentations)
if let dimensions = file.dimensions {
representations.append(TelegramMediaImageRepresentation(dimensions: dimensions, resource: file.resource))
representations.append(TelegramMediaImageRepresentation(dimensions: dimensions, resource: file.resource, progressiveSizes: []))
}
let image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, immediateThumbnailData: file.immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
return InstantImageGalleryItem(context: context, presentationData: presentationData, itemId: self.index, imageReference: .webPage(webPage: WebpageReference(webPage), media: image), caption: caption, credit: credit, location: self.location, openUrl: openUrl, openUrlOptions: openUrlOptions)

View file

@ -375,7 +375,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
thumbnailItem = .animated(item.file.resource)
resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: item.file.resource)
} else if let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource {
thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource))
thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: []))
resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: resource)
}
}

View file

@ -86,7 +86,7 @@ static void setViewFrame(UIView *view, CGRect frame)
{
localizationPlaceholderText = TGLocalized(@"MediaPicker.AddCaption");
NSString *placeholderText = TGLocalized(@"MediaPicker.AddCaption");
UIFont *placeholderFont = TGSystemFontOfSize(16);
UIFont *placeholderFont = TGSystemFontOfSize(17);
CGSize placeholderSize = [placeholderText sizeWithFont:placeholderFont];
placeholderSize.width += 2.0f;
placeholderSize.height += 2.0f;
@ -121,7 +121,7 @@ static void setViewFrame(UIView *view, CGRect frame)
_placeholderLabel = [[UILabel alloc] init];
_placeholderLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
_placeholderLabel.backgroundColor = [UIColor clearColor];
_placeholderLabel.font = TGSystemFontOfSize(16);
_placeholderLabel.font = TGSystemFontOfSize(17);
_placeholderLabel.textColor = UIColorRGB(0x7f7f7f);
_placeholderLabel.text = TGLocalized(@"MediaPicker.AddCaption");
_placeholderLabel.userInteractionEnabled = true;
@ -130,7 +130,7 @@ static void setViewFrame(UIView *view, CGRect frame)
_inputFieldOnelineLabel = [[UILabel alloc] init];
_inputFieldOnelineLabel.backgroundColor = [UIColor clearColor];
_inputFieldOnelineLabel.font = TGSystemFontOfSize(16);
_inputFieldOnelineLabel.font = TGSystemFontOfSize(17);
_inputFieldOnelineLabel.hidden = true;
_inputFieldOnelineLabel.numberOfLines = 1;
_inputFieldOnelineLabel.textColor = [UIColor whiteColor];
@ -169,7 +169,7 @@ static void setViewFrame(UIView *view, CGRect frame)
_inputField.textColor = [UIColor whiteColor];
_inputField.disableFormatting = !_allowEntities;
_inputField.placeholderView = _placeholderLabel;
_inputField.font = TGSystemFontOfSize(16);
_inputField.font = TGSystemFontOfSize(17);
_inputField.accentColor = UIColorRGB(0x78b1f9);
_inputField.clipsToBounds = true;
_inputField.backgroundColor = nil;
@ -188,7 +188,7 @@ static void setViewFrame(UIView *view, CGRect frame)
_inputField.internalTextView.scrollIndicatorInsets = UIEdgeInsetsMake(-inputFieldInternalEdgeInsets.top, 0, 5 - TGRetinaPixel, 0);
[_inputField setAttributedText:[TGMediaPickerCaptionInputPanel attributedStringForText:_caption entities:_entities fontSize:16.0f] keepFormatting:true animated:false];
[_inputField setAttributedText:[TGMediaPickerCaptionInputPanel attributedStringForText:_caption entities:_entities fontSize:17.0f] keepFormatting:true animated:false];
[_inputFieldClippingContainer addSubview:_inputField];
}
@ -439,7 +439,7 @@ static void setViewFrame(UIView *view, CGRect frame)
_fieldBackground.alpha = _placeholderLabel.hidden ? 1.0f : 0.0f;
}
[self.inputField setAttributedText:[TGMediaPickerCaptionInputPanel attributedStringForText:_caption entities:_entities fontSize:16.0f] keepFormatting:true animated:false];
[self.inputField setAttributedText:[TGMediaPickerCaptionInputPanel attributedStringForText:_caption entities:_entities fontSize:17.0f] keepFormatting:true animated:false];
}
+ (NSAttributedString *)attributedStringForText:(NSString *)text entities:(NSArray *)entities fontSize:(CGFloat)fontSize {
@ -777,14 +777,14 @@ static void setViewFrame(UIView *view, CGRect frame)
if (text == nil)
return nil;
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithAttributedString:[TGMediaPickerCaptionInputPanel attributedStringForText:text entities:entities fontSize:16.0f]];
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithAttributedString:[TGMediaPickerCaptionInputPanel attributedStringForText:text entities:entities fontSize:17.0f]];
for (NSUInteger i = 0; i < string.length; i++)
{
unichar c = [text characterAtIndex:i];
if (c == '\t' || c == '\n')
{
[string insertAttributedString:[[NSAttributedString alloc] initWithString:tokenString attributes:@{NSFontAttributeName:TGSystemFontOfSize(16.0f)}] atIndex:i];
[string insertAttributedString:[[NSAttributedString alloc] initWithString:tokenString attributes:@{NSFontAttributeName:TGSystemFontOfSize(17.0f)}] atIndex:i];
break;
}
}

View file

@ -167,13 +167,14 @@
if (recipientName.length > 0)
{
_arrowView = [[UIImageView alloc] initWithImage: TGComponentsImageNamed(@"PhotoPickerArrow")];
_arrowView.alpha = 0.45f;
_arrowView.alpha = 0.6f;
[_wrapperView addSubview:_arrowView];
_recipientLabel = [[UILabel alloc] init];
_recipientLabel.alpha = 0.6;
_recipientLabel.backgroundColor = [UIColor clearColor];
_recipientLabel.font = TGBoldSystemFontOfSize(13.0f);
_recipientLabel.textColor = UIColorRGBA(0xffffff, 0.45f);
_recipientLabel.textColor = UIColorRGB(0xffffff);
_recipientLabel.text = recipientName;
_recipientLabel.userInteractionEnabled = false;
[_recipientLabel sizeToFit];
@ -510,13 +511,7 @@
UIEdgeInsets screenEdges = [self screenEdges];
__weak TGMediaPickerGalleryInterfaceView *weakSelf = self;
if ([itemView.headerView isKindOfClass:[TGMediaPickerScrubberHeaderView class]])
{
TGMediaPickerScrubberHeaderView *headerView = (TGMediaPickerScrubberHeaderView *)itemView.headerView;
[headerView.scrubberView setRecipientName:_recipientLabel.text];
}
[self _layoutRecipientLabelForOrientation:[self interfaceOrientation] screenEdges:screenEdges hasHeaderView:(itemView.headerView != nil)];
if (_selectionContext != nil)
@ -1039,8 +1034,8 @@
{
_checkButton.alpha = alpha;
_muteButton.alpha = alpha;
_arrowView.alpha = alpha * 0.45f;
_recipientLabel.alpha = alpha;
_arrowView.alpha = alpha * 0.6f;
_recipientLabel.alpha = alpha * 0.6;
} completion:^(BOOL finished)
{
if (finished)
@ -1070,8 +1065,8 @@
_muteButton.alpha = alpha;
_muteButton.userInteractionEnabled = !hidden;
_arrowView.alpha = alpha * 0.45f;
_recipientLabel.alpha = alpha;
_arrowView.alpha = alpha * 0.6f;
_recipientLabel.alpha = alpha * 0.6;
}
if (hidden)
@ -1095,7 +1090,7 @@
{
_checkButton.alpha = alpha;
_muteButton.alpha = alpha;
_arrowView.alpha = alpha * 0.45f;
_arrowView.alpha = alpha * 0.6;
_recipientLabel.alpha = alpha;
_portraitToolbarView.alpha = alpha;
_landscapeToolbarView.alpha = alpha;
@ -1132,7 +1127,7 @@
_muteButton.alpha = alpha;
_muteButton.userInteractionEnabled = !hidden;
_arrowView.alpha = alpha * 0.45f;
_arrowView.alpha = alpha * 0.6;
_recipientLabel.alpha = alpha;
_portraitToolbarView.alpha = alpha;
@ -1391,6 +1386,8 @@
screenEdges.left += _safeAreaInset.left;
screenEdges.right -= _safeAreaInset.right;
CGFloat panelInset = 0.0f;
switch (orientation)
{
case UIInterfaceOrientationLandscapeLeft:
@ -1402,13 +1399,10 @@
break;
default:
frame = CGRectMake(screenEdges.left + 5, screenEdges.top + 6, _muteButton.frame.size.width, _muteButton.frame.size.height);
frame = CGRectMake(screenEdges.left + 5, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 45 - _safeAreaInset.bottom - panelInset - (hasHeaderView ? 64.0 : 0.0), _muteButton.frame.size.width, _muteButton.frame.size.height);
break;
}
if (hasHeaderView)
frame.origin.y += 64;
return frame;
}
@ -1462,9 +1456,6 @@
break;
}
if (hasHeaderView)
frame.origin.y += 64;
return frame;
}
@ -1526,9 +1517,6 @@
_arrowView.frame = frame;
_recipientLabel.frame = CGRectMake(CGRectGetMaxX(_arrowView.frame) + 6.0f, _arrowView.frame.origin.y - 2.0f, recipientWidth, _recipientLabel.frame.size.height);
_arrowView.hidden = hasHeaderView;
_recipientLabel.hidden = hasHeaderView;
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)__unused duration
@ -1670,15 +1658,14 @@
{
[UIView performWithoutAnimation:^
{
_photoCounterButton.frame = CGRectMake(screenEdges.right - 56 - _safeAreaInset.right, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 40 - _safeAreaInset.bottom, 64, 38);
_photoCounterButton.frame = CGRectMake(screenEdges.right - 56 - _safeAreaInset.right, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 40 - _safeAreaInset.bottom - (hasHeaderView ? 64.0 : 0.0), 64, 38);
_selectedPhotosView.frame = CGRectMake(screenEdges.left + 4, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - photosViewSize - 54 - _safeAreaInset.bottom, self.frame.size.width - 4 * 2 - _safeAreaInset.right, photosViewSize);
_selectedPhotosView.frame = CGRectMake(screenEdges.left + 4, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - photosViewSize - 54 - _safeAreaInset.bottom - (hasHeaderView ? 64.0 : 0.0), self.frame.size.width - 4 * 2 - _safeAreaInset.right, photosViewSize);
}];
_landscapeToolbarView.frame = CGRectMake(_landscapeToolbarView.frame.origin.x, screenEdges.top, TGPhotoEditorToolbarSize, self.frame.size.height);
CGFloat topInset = _safeAreaInset.top > FLT_EPSILON ? _safeAreaInset.top - 14.0 : 0.0f;
_headerWrapperView.frame = CGRectMake(screenEdges.left, screenEdges.top + topInset, self.frame.size.width, 64);
_headerWrapperView.frame = CGRectMake(screenEdges.left, _portraitToolbarView.frame.origin.y - 64.0 - [_captionMixin.inputPanel baseHeight], self.frame.size.width, 64.0);
}
break;
}

View file

@ -71,6 +71,8 @@
bool _scrubbingPanelPresented;
bool _scrubbingPanelLocked;
bool _shouldResetScrubber;
NSArray *_cachedThumbnails;
UIImage *_immediateThumbnail;
UILabel *_fileInfoLabel;
@ -215,13 +217,12 @@
_headerView = headerView;
_headerView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
_scrubberPanelView = [[UIView alloc] initWithFrame:CGRectMake(0, -64, _headerView.frame.size.width, 64)];
_scrubberPanelView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, _headerView.frame.size.width, 64)];
_scrubberPanelView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
_scrubberPanelView.hidden = true;
headerView.panelView = _scrubberPanelView;
[_headerView addSubview:_scrubberPanelView];
UIView *scrubberBackgroundView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, -100.0f, _headerView.frame.size.width, 164.0f)];
UIView *scrubberBackgroundView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, _headerView.frame.size.width, 64.0f)];
scrubberBackgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
scrubberBackgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor];
[_scrubberPanelView addSubview:scrubberBackgroundView];
@ -301,7 +302,6 @@
_appeared = false;
[self setScrubbingPanelApperanceLocked:false];
[self setScrubbingPanelHidden:true animated:false];
[_positionTimer invalidate];
_positionTimer = nil;
@ -386,9 +386,14 @@
- (void)setItem:(TGMediaPickerGalleryVideoItem *)item synchronously:(bool)synchronously
{
bool itemChanged = ![item isEqual:self.item];
bool itemIdChanged = item.uniqueId != self.item.uniqueId;
[super setItem:item synchronously:synchronously];
if (itemIdChanged) {
_immediateThumbnail = item.immediateThumbnailImage;
}
if (itemChanged) {
[self _playerCleanup];
@ -618,17 +623,15 @@
void (^changeBlock)(void) = ^
{
_scrubberPanelView.frame = CGRectMake(0.0f, -64.0f - _safeAreaInset.top, _scrubberPanelView.frame.size.width, _scrubberPanelView.frame.size.height);
_scrubberPanelView.alpha = 0.0f;
};
void (^completionBlock)(BOOL) = ^(BOOL finished)
{
if (finished)
_scrubberPanelView.hidden = true;
};
if (animated)
{
[UIView animateWithDuration:0.3f delay:0.0f options:(7 << 16) animations:changeBlock completion:completionBlock];
[UIView animateWithDuration:0.2f delay:0.0f options:UIViewAnimationOptionCurveLinear animations:changeBlock completion:completionBlock];
}
else
{
@ -642,18 +645,17 @@
return;
_scrubbingPanelPresented = true;
_scrubberPanelView.hidden = false;
[_scrubberPanelView layoutSubviews];
[_scrubberView layoutSubviews];
void (^changeBlock)(void) = ^
{
_scrubberPanelView.frame = CGRectMake(0.0f, 0.0f, _scrubberPanelView.frame.size.width, _scrubberPanelView.frame.size.height);
_scrubberPanelView.alpha = 1.0f;
};
if (animated)
[UIView animateWithDuration:0.3f delay:0.0f options:(7 << 16) animations:changeBlock completion:nil];
[UIView animateWithDuration:0.2f delay:0.0f options:UIViewAnimationOptionCurveLinear animations:changeBlock completion:nil];
else
changeBlock();
}
@ -708,7 +710,6 @@
{
[_scrubberView resetThumbnails];
[self setScrubbingPanelHidden:true animated:false];
[_scrubberPanelView setNeedsLayout];
[_scrubberPanelView layoutIfNeeded];
@ -722,11 +723,14 @@
if (_containerView == nil)
return;
_containerView.frame = self.bounds;
if (self.bounds.size.width > self.bounds.size.height)
_containerView.frame = self.bounds;
else
_containerView.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height - 44.0);
[self _layoutPlayerView];
_videoContentView.frame = (CGRect){CGPointZero, frame.size};
_videoContentView.frame = (CGRect){CGPointZero, _containerView.frame.size};
if (_tooltipContainerView != nil && frame.size.width > frame.size.height)
{
@ -753,8 +757,6 @@
mirrored = adjustments.cropMirrored;
}
// _scrubberView.maximumLength = adjustments.sendAsGif ? TGVideoEditMaximumGifDuration : 0.0;
[self _layoutPlayerViewWithCropRect:cropRect videoFrameSize:videoFrameSize orientation:orientation mirrored:mirrored];
}
@ -1440,40 +1442,8 @@
trimEndValue = adjustments.trimEndValue;
}
}
// NSTimeInterval trimDuration = trimEndValue - trimStartValue;
bool sendAsGif = !adjustments.sendAsGif;
// if (sendAsGif && _scrubberView.allowsTrimming)
// {
// if (trimDuration > TGVideoEditMaximumGifDuration)
// {
// trimEndValue = trimStartValue + TGVideoEditMaximumGifDuration;
//
// if (_scrubberView.value > trimEndValue)
// {
// [self stop];
// [_scrubberView setValue:_scrubberView.trimStartValue resetPosition:true];
// [self _seekToPosition:_scrubberView.value manual:true];
// }
//
// _scrubberView.trimStartValue = trimStartValue;
// _scrubberView.trimEndValue = trimEndValue;
// [_scrubberView setTrimApplied:true];
// [self updatePlayerRange:trimEndValue];
// }
// }
// else if (_shouldResetScrubber)
// {
// trimStartValue = 0.0;
// trimEndValue = _videoDuration;
//
// _scrubberView.trimStartValue = trimStartValue;
// _scrubberView.trimEndValue = trimEndValue;
//
// [_scrubberView setTrimApplied:false];
// [self updatePlayerRange:trimEndValue];
// }
TGVideoEditAdjustments *updatedAdjustments = [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:_videoDimensions cropRect:cropRect cropOrientation:adjustments.cropOrientation cropRotation:adjustments.cropRotation cropLockedAspectRatio:adjustments.cropLockedAspectRatio cropMirrored:adjustments.cropMirrored trimStartValue:trimStartValue trimEndValue:trimEndValue toolValues:adjustments.toolValues paintingData:adjustments.paintingData sendAsGif:sendAsGif preset:adjustments.preset];
[self.item.editingContext setAdjustments:updatedAdjustments forItem:self.item.editableMediaItem];
@ -1596,6 +1566,23 @@
return timestamps;
}
- (SSignal *)_placeholderThumbnails:(NSArray *)timestamps {
NSMutableArray *thumbnails = [[NSMutableArray alloc] init];
UIImage *image = _immediateThumbnail;
if (image == nil)
return [SSignal complete];
UIImage *blurredImage = TGBlurredRectangularImage(image, true, image.size, image.size, NULL, nil);
for (__unused NSNumber *value in timestamps) {
if (thumbnails.count == 0)
[thumbnails addObject:image];
else
[thumbnails addObject:blurredImage];
}
return [SSignal single:thumbnails];
}
- (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber requestThumbnailImagesForTimestamps:(NSArray *)timestamps size:(CGSize)size isSummaryThumbnails:(bool)isSummaryThumbnails
{
if (timestamps.count == 0)
@ -1605,17 +1592,42 @@
TGMediaEditingContext *editingContext = self.item.editingContext;
id<TGMediaEditableItem> editableItem = self.editableMediaItem;
SSignal *thumbnailsSignal = nil;
if ([self.item.asset isKindOfClass:[TGMediaAsset class]] && ![self itemIsLivePhoto])
thumbnailsSignal = [TGMediaAssetImageSignals videoThumbnailsForAsset:self.item.asset size:size timestamps:timestamps];
else if (avAsset != nil)
thumbnailsSignal = [avAsset mapToSignal:^SSignal *(AVAsset *avAsset) {
return [TGMediaAssetImageSignals videoThumbnailsForAVAsset:avAsset size:size timestamps:timestamps];
}];
// SSignal *thumbnailsSignal = nil;
// if ([self.item.asset isKindOfClass:[TGMediaAsset class]] && ![self itemIsLivePhoto])
// thumbnailsSignal = [TGMediaAssetImageSignals videoThumbnailsForAsset:self.item.asset size:size timestamps:timestamps];
// else if (avAsset != nil)
// thumbnailsSignal = [avAsset mapToSignal:^SSignal *(AVAsset *avAsset) {
// return [TGMediaAssetImageSignals videoThumbnailsForAVAsset:avAsset size:size timestamps:timestamps];
// }];
__strong TGMediaPickerGalleryVideoItemView *weakSelf = self;
SSignal *thumbnailsSignal = nil;
if (_cachedThumbnails != nil) {
thumbnailsSignal = [SSignal single:_cachedThumbnails];
} else if ([self.item.asset isKindOfClass:[TGMediaAsset class]] && ![self itemIsLivePhoto]) {
thumbnailsSignal = [[self _placeholderThumbnails:timestamps] then:[[TGMediaAssetImageSignals videoThumbnailsForAsset:(TGMediaAsset *)self.item.asset size:size timestamps:timestamps] onNext:^(NSArray *images) {
__strong TGMediaPickerGalleryVideoItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf->_cachedThumbnails == nil)
strongSelf->_cachedThumbnails = images;
}]];
} else if ([self.item.asset isKindOfClass:[TGCameraCapturedVideo class]]) {
thumbnailsSignal = [[((TGCameraCapturedVideo *)self.item.asset).avAsset takeLast] mapToSignal:^SSignal *(AVAsset *avAsset) {
return [[self _placeholderThumbnails:timestamps] then:[[TGMediaAssetImageSignals videoThumbnailsForAVAsset:avAsset size:size timestamps:timestamps] onNext:^(NSArray *images) {
__strong TGMediaPickerGalleryVideoItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf->_cachedThumbnails == nil)
strongSelf->_cachedThumbnails = images;
}]];
}];
}
_requestingThumbnails = true;
__weak TGMediaPickerGalleryVideoItemView *weakSelf = self;
[_thumbnailsDisposable setDisposable:[[[thumbnailsSignal map:^NSArray *(NSArray *images) {
id<TGMediaEditAdjustments> adjustments = [editingContext adjustmentsForItem:editableItem];
if (adjustments.toolsApplied) {

View file

@ -45,8 +45,6 @@
- (void)setThumbnailImage:(UIImage *)image forTimestamp:(NSTimeInterval)timestamp index:(NSInteger)index isSummaryThubmnail:(bool)isSummaryThumbnail;
- (void)setRecipientName:(NSString *)recipientName;
- (CGPoint)scrubberPositionForPosition:(NSTimeInterval)position;
- (void)_updateScrubberAnimationsAndResetCurrentPosition:(bool)resetCurrentPosition;

View file

@ -81,9 +81,6 @@ typedef enum
NSInteger _zoomedPivotTimestampIndex;
NSArray *_zoomedTimestamps;
NSMutableArray *_zoomedThumbnailViews;
UIImageView *_arrowView;
UILabel *_recipientLabel;
}
@end
@ -417,33 +414,10 @@ typedef enum
_tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
_tapGestureRecognizer.enabled = false;
[_trimView addGestureRecognizer:_tapGestureRecognizer];
_arrowView = [[UIImageView alloc] initWithImage:TGComponentsImageNamed(@"PhotoPickerArrow")];
_arrowView.alpha = 0.45f;
_arrowView.hidden = true;
[self addSubview:_arrowView];
_recipientLabel = [[UILabel alloc] init];
_recipientLabel.backgroundColor = [UIColor clearColor];
_recipientLabel.font = TGBoldSystemFontOfSize(13.0f);
_recipientLabel.textColor = UIColorRGBA(0xffffff, 0.45f);
_recipientLabel.hidden = true;
[self addSubview:_recipientLabel];
}
return self;
}
- (void)setRecipientName:(NSString *)recipientName
{
_recipientLabel.text = recipientName;
_recipientLabel.hidden = recipientName.length == 0;
_arrowView.hidden = _recipientLabel.hidden;
[_recipientLabel sizeToFit];
[self _layoutRecipientLabel];
}
- (void)setHasDotPicker:(bool)hasDotPicker {
_hasDotPicker = hasDotPicker;
_tapGestureRecognizer.enabled = hasDotPicker;
@ -1052,11 +1026,9 @@ typedef enum
- (void)_updateTimeLabels
{
_currentTimeLabel.text = @"";
_currentTimeLabel.text = self.disableTimeDisplay ? @"" : [TGMediaPickerGalleryVideoScrubber _stringFromTotalSeconds:(NSInteger)self.value];
NSString *text = [NSString stringWithFormat:@"%@ / %@", [TGMediaPickerGalleryVideoScrubber _stringFromTotalSeconds:(NSInteger)self.value], [TGMediaPickerGalleryVideoScrubber _stringFromTotalSeconds:(NSInteger)self.duration]];
_inverseTimeLabel.text = self.disableTimeDisplay ? @"" : text;
_inverseTimeLabel.text = self.disableTimeDisplay ? @"" : [TGMediaPickerGalleryVideoScrubber _stringFromTotalSeconds:(NSInteger)self.duration];
}
#pragma mark - Scrubber Handle
@ -1466,18 +1438,6 @@ typedef enum
}
}
- (void)_layoutRecipientLabel
{
if (self.frame.size.width < FLT_EPSILON)
return;
CGFloat screenWidth = MAX(self.frame.size.width, self.frame.size.height);
CGFloat recipientWidth = MIN(_recipientLabel.frame.size.width, screenWidth - 100.0f);
_arrowView.frame = CGRectMake(14.0f, 6.0f, _arrowView.frame.size.width, _arrowView.frame.size.height);
_recipientLabel.frame = CGRectMake(CGRectGetMaxX(_arrowView.frame) + 6.0f, _arrowView.frame.origin.y - 2.0f, recipientWidth, _recipientLabel.frame.size.height);
}
- (void)setFrame:(CGRect)frame
{
if (isnan(frame.origin.x) || isnan(frame.origin.y) || isnan(frame.size.width) || isnan(frame.size.height))
@ -1501,8 +1461,6 @@ typedef enum
_zoomedThumbnailWrapperView.frame = _summaryThumbnailWrapperView.frame;
[self _updateScrubberAnimationsAndResetCurrentPosition:true];
[self _layoutRecipientLabel];
}
+ (NSString *)_stringFromTotalSeconds:(NSInteger)totalSeconds

View file

@ -48,7 +48,7 @@ const CGFloat TGPhotoCounterButtonMaskFade = 18;
{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(38.0f, 38.0f), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.7f).CGColor);
CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.3f).CGColor);
CGContextFillEllipseInRect(context, CGRectMake(3.5f, 1.0f, 31.0f, 31.0f));

View file

@ -126,7 +126,7 @@
CGRect rect = CGRectMake(0, 0, 39.0f, 39.0f);
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.6f).CGColor);
CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.3f).CGColor);
CGContextFillEllipseInRect(context, CGRectInset(rect, 3, 3));
muteBackground = UIGraphicsGetImageFromCurrentImageContext();

View file

@ -1216,7 +1216,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
if (![editorController isKindOfClass:[TGPhotoEditorController class]])
return;
if (hasStickers) {
if (!hasStickers) {
[editorController setMinimalVideoDuration:duration];
}

View file

@ -405,7 +405,7 @@ private func loadLegacyMessages(account: TemporaryAccount, basePath: String, acc
if let resourcePath = resourcePath, let image = UIImage(contentsOfFile: resourcePath) {
dimensions = image.size
}
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(dimensions), resource: resource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(dimensions), resource: resource, progressiveSizes: []))
}
}
@ -414,7 +414,7 @@ private func loadLegacyMessages(account: TemporaryAccount, basePath: String, acc
if let image = UIImage(contentsOfFile: fullSizePath) {
let resource: TelegramMediaResource = LocalFileMediaResource(fileId: arc4random64())
copyLocalFiles.append((resource, fullSizePath))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(image.size), resource: resource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(image.size), resource: resource, progressiveSizes: []))
}
}
@ -431,7 +431,7 @@ private func loadLegacyMessages(account: TemporaryAccount, basePath: String, acc
} else if imageUrl.hasPrefix("file://"), let path = URL(string: imageUrl)?.path {
copyLocalFiles.append((resource, path))
}
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(sizeValue.cgSizeValue), resource: resource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(sizeValue.cgSizeValue), resource: resource, progressiveSizes: []))
}
}
@ -488,7 +488,7 @@ private func loadLegacyMessages(account: TemporaryAccount, basePath: String, acc
resource = updatedResource
copyLocalFiles.append((resource, pathFromLegacyImageUrl(basePath: basePath, url: imageUrl)))
}
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(sizeValue.cgSizeValue), resource: resource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(sizeValue.cgSizeValue), resource: resource, progressiveSizes: []))
}
}

View file

@ -18,10 +18,10 @@ func loadLegacyUser(database: SqliteInterface, id: Int32) -> (TelegramUser, Tele
let photoBig = cursor.getString(at: 6)
var photo: [TelegramMediaImageRepresentation] = []
if let resource = resourceFromLegacyImageUrl(photoSmall) {
photo.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 80, height: 80), resource: resource))
photo.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 80, height: 80), resource: resource, progressiveSizes: []))
}
if let resource = resourceFromLegacyImageUrl(photoBig) {
photo.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 600, height: 600), resource: resource))
photo.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 600, height: 600), resource: resource, progressiveSizes: []))
}
let user = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: cursor.getInt32(at: 0)), accessHash: accessHash == 0 ? nil : .personal(accessHash), firstName: firstName.isEmpty ? nil : firstName, lastName: lastName.isEmpty ? nil : lastName, username: username.isEmpty ? nil : username, phone: phone.isEmpty ? nil : phone, photo: photo, botInfo: nil, restrictionInfo: nil, flags: [])

View file

@ -265,7 +265,7 @@ public func legacyEnqueueGifMessage(account: Account, data: Data) -> Signal<Enqu
if let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.4) {
let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnailData)
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailSize), resource: resource))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailSize), resource: resource, progressiveSizes: []))
}
var randomId: Int64 = 0
@ -312,7 +312,7 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -
let thumbnailImage = TGScaleImageToPixelSize(thumbnail, thumbnailSize)!
if let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.4) {
account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnailData)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailSize), resource: resource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailSize), resource: resource, progressiveSizes: []))
}
}
switch data {
@ -324,26 +324,9 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -
if let scaledImage = TGScaleImageToPixelSize(image, scaledSize) {
if let scaledImageData = compressImageToJPEG(scaledImage, quality: 0.6) {
let _ = try? scaledImageData.write(to: URL(fileURLWithPath: tempFilePath))
#if DEBUG
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
if false, let heicData = compressImage(scaledImage, quality: 0.65) {
print("scaledImageData \(scaledImageData.count), heicData \(heicData.count)")
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
let _ = try? heicData.write(to: URL(fileURLWithPath: tempFilePath + ".heic"))
let resource = LocalFileReferenceMediaResource(localFilePath: tempFilePath + ".heic", randomId: randomId)
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/heic", size: nil, attributes: [.FileName(fileName: "image.heic")])
var attributes: [MessageAttribute] = []
if let timer = item.timer, timer > 0 && timer <= 60 {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
}
messages.append(.message(text: caption ?? "", attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId))
}
}
#endif
let resource = LocalFileReferenceMediaResource(localFilePath: tempFilePath, randomId: randomId)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(scaledSize), resource: resource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(scaledSize), resource: resource, progressiveSizes: []))
var imageFlags: TelegramMediaImageFlags = []
@ -375,7 +358,7 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -
let size = CGSize(width: CGFloat(asset.pixelWidth), height: CGFloat(asset.pixelHeight))
let scaledSize = size.aspectFittedOrSmaller(CGSize(width: 1280.0, height: 1280.0))
let resource = PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier, uniqueId: arc4random64())
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(scaledSize), resource: resource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(scaledSize), resource: resource, progressiveSizes: []))
let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
var attributes: [MessageAttribute] = []
@ -442,7 +425,7 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -
let thumbnailImage = TGScaleImageToPixelSize(thumbnail, thumbnailSize)!
if let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.4) {
account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnailData)
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailSize), resource: resource))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailSize), resource: resource, progressiveSizes: []))
}
}

View file

@ -117,7 +117,7 @@ class LegacyPaintStickerView: UIView, TGPhotoPaintStickerRenderView {
let dimensions = self.file.dimensions ?? PixelDimensions(width: 512, height: 512)
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0))
let source = AnimatedStickerResourceSource(account: self.context.account, resource: self.file.resource)
self.animationNode?.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct)
self.animationNode?.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil))
self.cachedDisposable.set((source.cachedDataPath(width: 384, height: 384)
|> deliverOn(Queue.concurrentDefaultQueue())).start())

View file

@ -198,7 +198,7 @@ final class LegacyStickerImageDataSource: TGImageDataSource {
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if let legacyThumbnailUri = args["legacyThumbnailUri"] as? String, let data = Data(base64Encoded: legacyThumbnailUri, options: []) {
if let resource = PostboxDecoder(buffer: MemoryBuffer(data: data)).decodeRootObject() as? TelegramMediaResource {
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 140, height: 140), resource: resource))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 140, height: 140), resource: resource, progressiveSizes: []))
}
}

View file

@ -52,19 +52,7 @@ public func fetchPhotoLibraryResource(localIdentifier: String) -> Signal<MediaRe
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if let scaledImage = scaledImage, let data = scaledImage.jpegData(compressionQuality: 0.8) {
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
#if DEBUG
if false, let heicData = compressImage(scaledImage, quality: 0.8) {
//compressTinyThumbnail(scaledImage)
print("data \(data.count), heicData \(heicData.count)")
subscriber.putNext(.dataPart(resourceOffset: 0, data: heicData, range: 0 ..< heicData.count, complete: true))
subscriber.putCompletion()
return
}
#endif
}
if let scaledImage = scaledImage, let data = compressImageToJPEG(scaledImage, quality: 0.6) {
subscriber.putNext(.dataPart(resourceOffset: 0, data: data, range: 0 ..< data.count, complete: true))
subscriber.putCompletion()
} else {

View file

@ -0,0 +1,20 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "MozjpegBinding",
srcs = glob([
"Sources/*.m",
]),
headers = glob([
"Sources/*.h",
]),
exported_headers = glob([
"Public/**/*.h",
]),
deps = [
"//third-party/mozjpeg:mozjpeg",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
],
)

View file

@ -0,0 +1,22 @@
objc_library(
name = "MozjpegBinding",
module_name = "MozjpegBinding",
enable_modules = True,
srcs = glob([
"Sources/**/*.m",
"Sources/**/*.h",
]),
hdrs = glob([
"Public/**/*.h",
]),
includes = [
"Public",
],
deps = [
"//third-party/mozjpeg:mozjpeg",
],
visibility = [
"//visibility:public",
]
)

View file

@ -0,0 +1,5 @@
#import <UIKit/UIKit.h>
NSData * _Nullable compressJPEGData(UIImage * _Nonnull sourceImage);
NSArray<NSNumber *> * _Nonnull extractJPEGDataScans(NSData * _Nonnull data);
NSData * _Nullable compressMiniThumbnail(UIImage * _Nonnull image);

View file

@ -0,0 +1,256 @@
#import <MozjpegBinding/MozjpegBinding.h>
#import <mozjpeg/turbojpeg.h>
#import <mozjpeg/jpeglib.h>
static NSData *getHeaderPattern() {
static NSData *value = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
value = [[NSData alloc] initWithBase64EncodedString:@"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACgcHiMeGSgjISMtKygwPGRBPDc3PHtYXUlkkYCZlo+AjIqgtObDoKrarYqMyP/L2u71////m8H////6/+b9//j/2wBDASstLTw1PHZBQXb4pYyl+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj/wAARCAAAAAADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwA=" options:0];
});
return value;
}
static NSData *getFooterPattern() {
static NSData *value = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
value = [[NSData alloc] initWithBase64EncodedString:@"/9k=" options:0];
});
return value;
}
NSArray<NSNumber *> * _Nonnull extractJPEGDataScans(NSData * _Nonnull data) {
NSMutableArray<NSNumber *> *result = [[NSMutableArray alloc] init];
const uint8_t *dataBytes = data.bytes;
int offset = 0;
while (offset < data.length) {
bool found = false;
for (int i = offset + 2; i < data.length - 1; i++) {
if (dataBytes[i] == 0xffU && dataBytes[i + 1] == 0xdaU) {
if (offset != 0) {
[result addObject:@(i)];
}
offset = i;
found = true;
}
}
if (!found) {
break;
}
}
#if DEBUG
static NSString *sessionPrefix = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sessionPrefix = [NSString stringWithFormat:@"%u", arc4random()];
});
NSString *randomId = [NSString stringWithFormat:@"%u", arc4random()];
NSString *dirPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:sessionPrefix] stringByAppendingPathComponent:randomId];
[[NSFileManager defaultManager] createDirectoryAtPath:dirPath withIntermediateDirectories:true attributes:nil error:nil];
for (int i = 0; i < result.count + 1; i++) {
NSString *filePath = [dirPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%d.jpg", i]];
if (i == result.count) {
[data writeToFile:filePath atomically:true];
} else {
[[data subdataWithRange:NSMakeRange(0, [result[i] intValue])] writeToFile:filePath atomically:true];
}
}
NSLog(@"Path: %@", dirPath);
#endif
return result;
}
NSData * _Nullable compressJPEGData(UIImage * _Nonnull sourceImage) {
int width = (int)(sourceImage.size.width * sourceImage.scale);
int height = (int)(sourceImage.size.height * sourceImage.scale);
int targetBytesPerRow = ((4 * (int)width) + 15) & (~15);
uint8_t *targetMemory = malloc((int)(targetBytesPerRow * height));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
CGContextRef targetContext = CGBitmapContextCreate(targetMemory, width, height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
UIGraphicsPushContext(targetContext);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(targetContext, CGRectMake(0, 0, width, height), sourceImage.CGImage);
UIGraphicsPopContext();
int bufferBytesPerRow = ((3 * (int)width) + 15) & (~15);
uint8_t *buffer = malloc(bufferBytesPerRow * height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint32_t *color = ((uint32_t *)&targetMemory[y * targetBytesPerRow + x * 4]);
uint32_t r = ((*color >> 16) & 0xff);
uint32_t g = ((*color >> 8) & 0xff);
uint32_t b = (*color & 0xff);
buffer[y * bufferBytesPerRow + x * 3 + 0] = r;
buffer[y * bufferBytesPerRow + x * 3 + 1] = g;
buffer[y * bufferBytesPerRow + x * 3 + 2] = b;
}
}
CGContextRelease(targetContext);
free(targetMemory);
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
uint8_t *outBuffer = NULL;
unsigned long outSize = 0;
jpeg_mem_dest(&cinfo, &outBuffer, &outSize);
cinfo.image_width = (uint32_t)width;
cinfo.image_height = (uint32_t)height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_c_set_int_param(&cinfo, JINT_COMPRESS_PROFILE, JCP_FASTEST);
jpeg_set_defaults(&cinfo);
cinfo.arith_code = FALSE;
cinfo.dct_method = JDCT_ISLOW;
cinfo.optimize_coding = TRUE;
jpeg_set_quality(&cinfo, 78, 1);
jpeg_simple_progression(&cinfo);
jpeg_start_compress(&cinfo, 1);
JSAMPROW rowPointer[1];
while (cinfo.next_scanline < cinfo.image_height) {
rowPointer[0] = (JSAMPROW)(buffer + cinfo.next_scanline * bufferBytesPerRow);
jpeg_write_scanlines(&cinfo, rowPointer, 1);
}
jpeg_finish_compress(&cinfo);
NSData *result = [[NSData alloc] initWithBytes:outBuffer length:outSize];
jpeg_destroy_compress(&cinfo);
free(buffer);
return result;
}
NSData * _Nullable compressMiniThumbnail(UIImage * _Nonnull image) {
CGSize size = CGSizeMake(40.0f, 40.0f);
CGSize fittedSize = image.size;
if (fittedSize.width > size.width) {
fittedSize = CGSizeMake(size.width, (int)((fittedSize.height * size.width / MAX(fittedSize.width, 1.0f))));
}
if (fittedSize.height > size.height) {
fittedSize = CGSizeMake((int)((fittedSize.width * size.height / MAX(fittedSize.height, 1.0f))), size.height);
}
int width = (int)fittedSize.width;
int height = (int)fittedSize.height;
int targetBytesPerRow = ((4 * (int)width) + 15) & (~15);
uint8_t *targetMemory = malloc((int)(targetBytesPerRow * height));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
CGContextRef targetContext = CGBitmapContextCreate(targetMemory, width, height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
UIGraphicsPushContext(targetContext);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(targetContext, CGRectMake(0, 0, width, height), image.CGImage);
UIGraphicsPopContext();
int bufferBytesPerRow = ((3 * (int)width) + 15) & (~15);
uint8_t *buffer = malloc(bufferBytesPerRow * height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint32_t *color = ((uint32_t *)&targetMemory[y * targetBytesPerRow + x * 4]);
uint32_t r = ((*color >> 16) & 0xff);
uint32_t g = ((*color >> 8) & 0xff);
uint32_t b = (*color & 0xff);
buffer[y * bufferBytesPerRow + x * 3 + 0] = r;
buffer[y * bufferBytesPerRow + x * 3 + 1] = g;
buffer[y * bufferBytesPerRow + x * 3 + 2] = b;
}
}
CGContextRelease(targetContext);
free(targetMemory);
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
uint8_t *outBuffer = NULL;
unsigned long outSize = 0;
jpeg_mem_dest(&cinfo, &outBuffer, &outSize);
cinfo.image_width = (uint32_t)width;
cinfo.image_height = (uint32_t)height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_c_set_int_param(&cinfo, JINT_COMPRESS_PROFILE, JCP_FASTEST);
jpeg_set_defaults(&cinfo);
cinfo.arith_code = FALSE;
cinfo.dct_method = JDCT_ISLOW;
cinfo.optimize_coding = FALSE;
jpeg_set_quality(&cinfo, 20, 1);
jpeg_start_compress(&cinfo, 1);
JSAMPROW rowPointer[1];
while (cinfo.next_scanline < cinfo.image_height) {
rowPointer[0] = (JSAMPROW)(buffer + cinfo.next_scanline * bufferBytesPerRow);
jpeg_write_scanlines(&cinfo, rowPointer, 1);
}
jpeg_finish_compress(&cinfo);
NSMutableData *serializedData = nil;
NSData *headerPattern = getHeaderPattern();
NSData *footerPattern = getFooterPattern();
if (outBuffer[164] == height && outBuffer[166] == width && headerPattern != nil && footerPattern != nil) {
outBuffer[164] = 0;
outBuffer[166] = 0;
if (memcmp(headerPattern.bytes, outBuffer, headerPattern.length) == 0) {
if (memcmp(footerPattern.bytes, outBuffer + outSize - footerPattern.length, footerPattern.length) == 0) {
serializedData = [[NSMutableData alloc] init];
uint8_t version = 1;
[serializedData appendBytes:&version length:1];
uint8_t outWidth = (uint8_t)width;
uint8_t outHeight = (uint8_t)height;
[serializedData appendBytes:&outHeight length:1];
[serializedData appendBytes:&outWidth length:1];
unsigned long contentSize = outSize - headerPattern.length - footerPattern.length;
[serializedData appendBytes:outBuffer + headerPattern.length length:contentSize];
}
}
}
jpeg_destroy_compress(&cinfo);
free(buffer);
return serializedData;
}

View file

@ -700,14 +700,14 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
case .emailConfirmation:
if let path = getAppBundle().path(forResource: "TwoFactorSetupMail", ofType: "tgs") {
let animatedStickerNode = AnimatedStickerNode()
animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 272, height: 272, playbackMode: .once, mode: .direct)
animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 272, height: 272, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
animatedStickerNode.visibility = true
self.animatedStickerNode = animatedStickerNode
}
case .passwordHint:
if let path = getAppBundle().path(forResource: "TwoFactorSetupHint", ofType: "tgs") {
let animatedStickerNode = AnimatedStickerNode()
animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 272, height: 272, playbackMode: .once, mode: .direct)
animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 272, height: 272, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
animatedStickerNode.visibility = true
self.animatedStickerNode = animatedStickerNode
}

View file

@ -112,7 +112,7 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
buttonText = self.presentationData.strings.TwoFactorSetup_Intro_Action
if let path = getAppBundle().path(forResource: "TwoFactorSetupIntro", ofType: "tgs") {
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, playbackMode: .once, mode: .direct)
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
self.animationSize = CGSize(width: 124.0, height: 124.0)
self.animationNode.visibility = true
}
@ -122,7 +122,7 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
buttonText = self.presentationData.strings.TwoFactorSetup_Done_Action
if let path = getAppBundle().path(forResource: "TwoFactorSetupDone", ofType: "tgs") {
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, mode: .direct)
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, mode: .direct(cachePathPrefix: nil))
self.animationSize = CGSize(width: 124.0, height: 124.0)
self.animationNode.visibility = true
}

View file

@ -766,11 +766,6 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
}))
}
// items.append(ActionSheetButtonItem(title: self.presentationData.strings.ProfilePhoto_OpenInEditor, color: .accent, action: { [weak self] in
// dismissAction()
// self?.openEntryEdit(rawEntry)
// }))
let deleteTitle: String
if let _ = rawEntry.videoRepresentations.last {
deleteTitle = self.presentationData.strings.Settings_RemoveVideo

View file

@ -734,7 +734,7 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi
if let data = image.jpegData(compressionQuality: 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64())
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [])
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}

View file

@ -877,7 +877,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.UserInfo_TelegramCall, action: {
dismissAction()
let callResult = context.sharedContext.callManager?.requestCall(account: context.account, peerId: user.id, isVideo: false, endCurrentIfAny: false)
let callResult = context.sharedContext.callManager?.requestCall(context: context, peerId: user.id, isVideo: false, endCurrentIfAny: false)
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == user.id {
context.sharedContext.navigateToCurrentCall()
@ -888,7 +888,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
} |> deliverOnMainQueue).start(next: { peer, current in
if let peer = peer, let current = current {
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
let _ = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peer.id, isVideo: false, endCurrentIfAny: true)
let _ = context.sharedContext.callManager?.requestCall(context: context, peerId: peer.id, isVideo: false, endCurrentIfAny: true)
})]), nil)
}
})

View file

@ -1460,7 +1460,7 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
if let data = image.jpegData(compressionQuality: 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64())
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [])
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}

View file

@ -597,7 +597,7 @@ private func userInfoEntries(account: Account, presentationData: PresentationDat
var callsAvailable = true
if let cachedUserData = cachedPeerData as? CachedUserData {
callsAvailable = cachedUserData.callsAvailable
callsAvailable = cachedUserData.voiceCallsAvailable
}
entries.append(UserInfoEntry.info(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer: user, presence: view.peerPresences[user.id], cachedData: cachedPeerData, state: ItemListAvatarAndNameInfoItemState(editingName: editingName, updatingName: nil), displayCall: user.botInfo == nil && callsAvailable))
@ -873,7 +873,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
return
}
let callResult = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: false)
let callResult = context.sharedContext.callManager?.requestCall(context: context, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: false)
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == peer.id {
context.sharedContext.navigateToCurrentCall()
@ -884,7 +884,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
} |> deliverOnMainQueue).start(next: { peer, current in
if let peer = peer, let current = current {
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
let _ = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: true)
let _ = context.sharedContext.callManager?.requestCall(context: context, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: true)
})]), nil)
}
})

View file

@ -73,7 +73,7 @@ class PeersNearbyHeaderItemNode: ListViewItemNode {
self.animationNode = AnimatedStickerNode()
if let path = getAppBundle().path(forResource: "Compass", ofType: "tgs") {
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 192, height: 192, playbackMode: .once, mode: .direct)
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 192, height: 192, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
self.animationNode.visibility = true
}

View file

@ -24,10 +24,110 @@ private enum ResourceFileData {
}
public func largestRepresentationForPhoto(_ photo: TelegramMediaImage) -> TelegramMediaImageRepresentation? {
if let progressiveRepresentation = progressiveImageRepresentation(photo.representations) {
return progressiveRepresentation
}
return photo.representationForDisplayAtSize(PixelDimensions(width: 1280, height: 1280))
}
private let progressiveRangeMap: [(Int, [Int])] = [
(100, [0]),
(400, [2]),
(600, [4, 5]),
(Int(Int32.max), [4, 5, 8, 9])
]
public func representationFetchRangeForDisplayAtSize(representation: TelegramMediaImageRepresentation, dimension: Int) -> Range<Int>? {
if representation.progressiveSizes.count > 1 {
var largestByteSize = Int(representation.progressiveSizes[0])
for (maxDimension, byteSizes) in progressiveRangeMap {
largestByteSize = Int(representation.progressiveSizes[byteSizes.last!])
if maxDimension >= dimension {
break
}
}
return 0 ..< largestByteSize
}
return nil
}
public func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false, useMiniThumbnailIfAvailable: Bool = false) -> Signal<Tuple4<Data?, Data?, ChatMessagePhotoQuality, Bool>, NoError> {
if let progressiveRepresentation = progressiveImageRepresentation(photoReference.media.representations), progressiveRepresentation.progressiveSizes.count == 10 {
enum SizeSource {
case miniThumbnail(data: Data)
case image(size: Int)
}
var sources: [SizeSource] = []
if let miniThumbnail = photoReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) {
sources.append(.miniThumbnail(data: miniThumbnail))
}
let thumbnailByteSize = Int(progressiveRepresentation.progressiveSizes[0])
var largestByteSize = Int(progressiveRepresentation.progressiveSizes[0])
for (maxDimension, byteSizes) in progressiveRangeMap {
if Int(fullRepresentationSize.width) > 100 && maxDimension <= 100 {
continue
}
sources.append(contentsOf: byteSizes.map { sizeIndex -> SizeSource in
return .image(size: Int(progressiveRepresentation.progressiveSizes[sizeIndex]))
})
largestByteSize = Int(progressiveRepresentation.progressiveSizes[byteSizes.last!])
if maxDimension >= Int(fullRepresentationSize.width) {
break
}
}
return Signal { subscriber in
let signals: [Signal<(SizeSource, Data?), NoError>] = sources.map { source -> Signal<(SizeSource, Data?), NoError> in
switch source {
case let .miniThumbnail(data):
return .single((source, data))
case let .image(size):
return postbox.mediaBox.resourceData(progressiveRepresentation.resource, size: Int(progressiveRepresentation.progressiveSizes.last!), in: 0 ..< size, mode: .incremental, notifyAboutIncomplete: true, attemptSynchronously: synchronousLoad)
|> map { (data, _) -> (SizeSource, Data?) in
return (source, data)
}
}
}
let dataDisposable = combineLatest(signals).start(next: { results in
var foundData = false
loop: for i in (0 ..< results.count).reversed() {
let isLastSize = i == results.count - 1
switch results[i].0 {
case .image:
if let data = results[i].1, data.count != 0 {
subscriber.putNext(Tuple4(nil, data, .full, isLastSize))
foundData = true
if isLastSize {
subscriber.putCompletion()
}
break loop
}
case let .miniThumbnail(thumbnailData):
subscriber.putNext(Tuple4(thumbnailData, nil, .blurred, false))
foundData = true
break loop
}
}
if !foundData {
subscriber.putNext(Tuple4(nil, nil, .blurred, false))
}
})
var fetchDisposable: Disposable?
if autoFetchFullSize {
fetchDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: photoReference.resourceReference(progressiveRepresentation.resource), range: (0 ..< largestByteSize, .default), statsCategory: .image).start()
} else if useMiniThumbnailIfAvailable {
fetchDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: photoReference.resourceReference(progressiveRepresentation.resource), range: (0 ..< thumbnailByteSize, .default), statsCategory: .image).start()
}
return ActionDisposable {
dataDisposable.dispose()
fetchDisposable?.dispose()
}
}
}
if let smallestRepresentation = smallestImageRepresentation(photoReference.media.representations), let largestRepresentation = photoReference.media.representationForDisplayAtSize(PixelDimensions(width: Int32(fullRepresentationSize.width), height: Int32(fullRepresentationSize.height))), let fullRepresentation = largestImageRepresentation(photoReference.media.representations) {
let maybeFullSize = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
let maybeLargestSize = postbox.mediaBox.resourceData(fullRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
@ -1158,7 +1258,9 @@ public func mediaGridMessagePhoto(account: Account, photoReference: ImageMediaRe
let useMiniThumbnailIfAvailable: Bool = fullRepresentationSize.width < 40.0
var updatedFullRepresentationSize = fullRepresentationSize
if useMiniThumbnailIfAvailable, let largest = largestImageRepresentation(photoReference.media.representations) {
updatedFullRepresentationSize = largest.dimensions.cgSize
if progressiveImageRepresentation(photoReference.media.representations) == nil {
updatedFullRepresentationSize = largest.dimensions.cgSize
}
}
let signal = chatMessagePhotoDatas(postbox: account.postbox, photoReference: photoReference, fullRepresentationSize: updatedFullRepresentationSize, autoFetchFullSize: true, tryAdditionalRepresentations: useMiniThumbnailIfAvailable, synchronousLoad: synchronousLoad, useMiniThumbnailIfAvailable: useMiniThumbnailIfAvailable)
@ -1541,9 +1643,31 @@ public func internalMediaGridMessageVideo(postbox: Postbox, videoReference: File
}
}
public func chatMessagePhotoStatus(context: AccountContext, messageId: MessageId, photoReference: ImageMediaReference) -> Signal<MediaResourceStatus, NoError> {
public func chatMessagePhotoStatus(context: AccountContext, messageId: MessageId, photoReference: ImageMediaReference, displayAtSize: Int = 1000) -> Signal<MediaResourceStatus, NoError> {
if let largestRepresentation = largestRepresentationForPhoto(photoReference.media) {
return context.fetchManager.fetchStatus(category: .image, location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: largestRepresentation.resource)
if let range = representationFetchRangeForDisplayAtSize(representation: largestRepresentation, dimension: displayAtSize) {
return combineLatest(
context.fetchManager.fetchStatus(category: .image, location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: largestRepresentation.resource),
context.account.postbox.mediaBox.resourceRangesStatus(largestRepresentation.resource)
)
|> map { status, rangeStatus -> MediaResourceStatus in
if rangeStatus.contains(integersIn: range) {
return .Local
}
switch status {
case .Local:
return .Local
case .Remote:
return .Remote
case let .Fetching(isActive, progress):
return .Fetching(isActive: isActive, progress: max(progress, 0.0))
}
}
|> distinctUntilChanged
} else {
return context.fetchManager.fetchStatus(category: .image, location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: largestRepresentation.resource)
}
} else {
return .never()
}
@ -1560,15 +1684,19 @@ public func standaloneChatMessagePhotoInteractiveFetched(account: Account, photo
}
}
public func chatMessagePhotoInteractiveFetched(context: AccountContext, photoReference: ImageMediaReference, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<FetchResourceSourceType, FetchResourceError> {
public func chatMessagePhotoInteractiveFetched(context: AccountContext, photoReference: ImageMediaReference, displayAtSize: Int = 1000, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<FetchResourceSourceType, FetchResourceError> {
if let largestRepresentation = largestRepresentationForPhoto(photoReference.media) {
return fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: photoReference.resourceReference(largestRepresentation.resource), statsCategory: .image, reportResultStatus: true)
var fetchRange: (Range<Int>, MediaBoxFetchPriority)?
if let range = representationFetchRangeForDisplayAtSize(representation: largestRepresentation, dimension: displayAtSize) {
fetchRange = (range, .default)
}
return fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: photoReference.resourceReference(largestRepresentation.resource), range: fetchRange, statsCategory: .image, reportResultStatus: true)
|> mapToSignal { type -> Signal<FetchResourceSourceType, FetchResourceError> in
if case .remote = type, let peerType = storeToDownloadsPeerType {
return storeDownloadedMedia(storeManager: context.downloadedMediaStoreManager, media: photoReference.abstract, peerType: peerType)
|> castError(FetchResourceError.self)
|> mapToSignal { _ -> Signal<FetchResourceSourceType, FetchResourceError> in
return .complete()
}
|> then(.single(type))
}

View file

@ -239,6 +239,11 @@ public final class MediaBox {
return "\(self.basePath)/\(cacheString)/\(fileNameForId(id)):\(representation.uniqueId)"
}
public func shortLivedResourceCachePathPrefix(_ id: MediaResourceId) -> String {
let cacheString = "short-cache"
return "\(self.basePath)/\(cacheString)/\(fileNameForId(id))"
}
public func storeResourceData(_ id: MediaResourceId, data: Data, synchronous: Bool = false) {
let begin = {
let paths = self.storePathsForId(id)
@ -593,10 +598,40 @@ public final class MediaBox {
}
}
public func resourceData(_ resource: MediaResource, size: Int, in range: Range<Int>, mode: ResourceDataRangeMode = .complete) -> Signal<(Data, Bool), NoError> {
public func resourceData(_ resource: MediaResource, size: Int, in range: Range<Int>, mode: ResourceDataRangeMode = .complete, notifyAboutIncomplete: Bool = false, attemptSynchronously: Bool = false) -> Signal<(Data, Bool), NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
if attemptSynchronously {
let paths = self.storePathsForId(resource.id)
if let completeSize = fileSize(paths.complete) {
self.timeBasedCleanup.touch(paths: [
paths.complete
])
if let file = ManagedFile(queue: nil, path: paths.complete, mode: .read) {
let clippedLowerBound = min(completeSize, max(0, range.lowerBound))
let clippedUpperBound = min(completeSize, max(0, range.upperBound))
if clippedLowerBound < clippedUpperBound {
file.seek(position: Int64(clippedLowerBound))
let data = file.readData(count: clippedUpperBound)
subscriber.putNext((data, true))
} else {
subscriber.putNext((Data(), isComplete: true))
}
subscriber.putCompletion()
return EmptyDisposable
} else {
if let data = MediaBoxPartialFile.extractPartialData(path: paths.partial, metaPath: paths.partial + ".meta", range: Int32(range.lowerBound) ..< Int32(range.upperBound)) {
subscriber.putNext((data, true))
subscriber.putCompletion()
return EmptyDisposable
}
}
}
}
self.dataQueue.async {
guard let (fileContext, releaseContext) = self.fileContext(for: resource) else {
subscriber.putCompletion()
@ -608,24 +643,25 @@ public final class MediaBox {
let dataDisposable = fileContext.data(range: range, waitUntilAfterInitialFetch: false, next: { result in
if let file = ManagedFile(queue: self.dataQueue, path: result.path, mode: .read), let fileSize = file.getSize() {
if result.complete {
if result.offset + result.size <= fileSize {
if fileSize >= result.offset + result.size {
file.seek(position: Int64(result.offset))
let resultData = file.readData(count: result.size)
subscriber.putNext((resultData, true))
subscriber.putCompletion()
} else {
assertionFailure("data.count >= result.offset + result.size")
}
let clippedLowerBound = min(result.offset, fileSize)
let clippedUpperBound = min(result.offset + result.size, fileSize)
if clippedUpperBound == clippedLowerBound {
subscriber.putNext((Data(), true))
subscriber.putCompletion()
} else if clippedUpperBound <= fileSize {
file.seek(position: Int64(clippedLowerBound))
let resultData = file.readData(count: clippedUpperBound - clippedLowerBound)
subscriber.putNext((resultData, true))
subscriber.putCompletion()
} else {
assertionFailure()
}
} else {
switch mode {
case .complete:
break
case .incremental:
break
case .complete, .incremental:
if notifyAboutIncomplete {
subscriber.putNext((Data(), false))
}
case .partial:
subscriber.putNext((Data(), false))
}

View file

@ -216,6 +216,23 @@ final class MediaBoxPartialFile {
self.currentFetch?.1.dispose()
}
static func extractPartialData(path: String, metaPath: String, range: Range<Int32>) -> Data? {
guard let metadataFd = ManagedFile(queue: nil, path: metaPath, mode: .read) else {
return nil
}
guard let fd = ManagedFile(queue: nil, path: path, mode: .read) else {
return nil
}
guard let fileMap = MediaBoxFileMap(fd: metadataFd) else {
return nil
}
guard let clippedRange = fileMap.contains(range) else {
return nil
}
fd.seek(position: Int64(clippedRange.lowerBound))
return fd.readData(count: Int(clippedRange.upperBound - clippedRange.lowerBound))
}
var storedSize: Int32 {
assert(self.queue.isCurrent())
return self.fileMap.sum
@ -582,7 +599,7 @@ final class MediaBoxPartialFile {
private func immediateStatus(size: Int32?) -> MediaResourceStatus {
let status: MediaResourceStatus
if self.fullRangeRequests.isEmpty {
if self.fullRangeRequests.isEmpty && self.currentFetch == nil {
if let truncationSize = self.fileMap.truncationSize, self.fileMap.sum == truncationSize {
status = .Local
} else {
@ -633,6 +650,7 @@ final class MediaBoxPartialFile {
if intervals.isEmpty {
if let (_, disposable) = self.currentFetch {
self.currentFetch = nil
self.updateStatuses()
disposable.dispose()
}
} else {
@ -642,6 +660,7 @@ final class MediaBoxPartialFile {
let promise = Promise<[(Range<Int>, MediaBoxFetchPriority)]>()
let disposable = MetaDisposable()
self.currentFetch = (promise, disposable)
self.updateStatuses()
disposable.set((fetch(promise.get())
|> deliverOn(self.queue)).start(next: { [weak self] data in
if let strongSelf = self {

View file

@ -4,11 +4,21 @@ import AsyncDisplayKit
import Display
public enum SemanticStatusNodeState: Equatable {
public struct ProgressAppearance: Equatable {
public var inset: CGFloat
public var lineWidth: CGFloat
public init(inset: CGFloat, lineWidth: CGFloat) {
self.inset = inset
self.lineWidth = lineWidth
}
}
case none
case download
case play
case pause
case progress(value: CGFloat?, cancelEnabled: Bool)
case progress(value: CGFloat?, cancelEnabled: Bool, appearance: ProgressAppearance?)
case customIcon(UIImage)
}
@ -224,12 +234,14 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo
let transitionFraction: CGFloat
let value: CGFloat?
let displayCancel: Bool
let appearance: SemanticStatusNodeState.ProgressAppearance?
let timestamp: Double
init(transitionFraction: CGFloat, value: CGFloat?, displayCancel: Bool, timestamp: Double) {
init(transitionFraction: CGFloat, value: CGFloat?, displayCancel: Bool, appearance: SemanticStatusNodeState.ProgressAppearance?, timestamp: Double) {
self.transitionFraction = transitionFraction
self.value = value
self.displayCancel = displayCancel
self.appearance = appearance
self.timestamp = timestamp
super.init()
@ -252,22 +264,49 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo
context.setStrokeColor(foregroundColor.withAlphaComponent(foregroundColor.alpha * self.transitionFraction).cgColor)
}
var progress = self.value ?? 0.1
var startAngle = -CGFloat.pi / 2.0
var endAngle = CGFloat(progress) * 2.0 * CGFloat.pi + startAngle
if progress > 1.0 {
progress = 2.0 - progress
let tmp = startAngle
startAngle = endAngle
endAngle = tmp
var progress: CGFloat
var startAngle: CGFloat
var endAngle: CGFloat
if let value = self.value {
progress = value
startAngle = -CGFloat.pi / 2.0
endAngle = CGFloat(progress) * 2.0 * CGFloat.pi + startAngle
if progress > 1.0 {
progress = 2.0 - progress
let tmp = startAngle
startAngle = endAngle
endAngle = tmp
}
progress = min(1.0, progress)
} else {
progress = CGFloat(1.0 + self.timestamp.remainder(dividingBy: 2.0))
startAngle = -CGFloat.pi / 2.0
endAngle = CGFloat(progress) * 2.0 * CGFloat.pi + startAngle
if progress > 1.0 {
progress = 2.0 - progress
let tmp = startAngle
startAngle = endAngle
endAngle = tmp
}
progress = min(1.0, progress)
}
progress = min(1.0, progress)
let lineWidth: CGFloat = max(1.6, 2.25 * factor)
let lineWidth: CGFloat
if let appearance = self.appearance {
lineWidth = appearance.lineWidth
} else {
lineWidth = max(1.6, 2.25 * factor)
}
let pathDiameter: CGFloat
pathDiameter = diameter - lineWidth - 2.5 * 2.0
if let appearance = self.appearance {
pathDiameter = diameter - lineWidth - appearance.inset * 2.0
} else {
pathDiameter = diameter - lineWidth - 2.5 * 2.0
}
var angle = self.timestamp.truncatingRemainder(dividingBy: Double.pi * 2.0)
angle *= 4.0
@ -317,15 +356,17 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo
var value: CGFloat?
let displayCancel: Bool
let appearance: SemanticStatusNodeState.ProgressAppearance?
var transition: SemanticStatusNodeProgressTransition?
var isAnimating: Bool {
return true
}
init(value: CGFloat?, displayCancel: Bool) {
init(value: CGFloat?, displayCancel: Bool, appearance: SemanticStatusNodeState.ProgressAppearance?) {
self.value = value
self.displayCancel = displayCancel
self.appearance = appearance
}
func drawingState(transitionFraction: CGFloat) -> SemanticStatusNodeStateDrawingState {
@ -341,7 +382,7 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo
} else {
resolvedValue = nil
}
return DrawingState(transitionFraction: transitionFraction, value: resolvedValue, displayCancel: self.displayCancel, timestamp: timestamp)
return DrawingState(transitionFraction: transitionFraction, value: resolvedValue, displayCancel: self.displayCancel, appearance: self.appearance, timestamp: timestamp)
}
func updateValue(value: CGFloat?) {
@ -386,12 +427,12 @@ private extension SemanticStatusNodeState {
} else {
return SemanticStatusNodeIconContext(icon: icon)
}
case let .progress(value, cancelEnabled):
case let .progress(value, cancelEnabled, appearance):
if let current = current as? SemanticStatusNodeProgressContext, current.displayCancel == cancelEnabled {
current.updateValue(value: value)
return current
} else {
return SemanticStatusNodeProgressContext(value: value, displayCancel: cancelEnabled)
return SemanticStatusNodeProgressContext(value: value, displayCancel: cancelEnabled, appearance: appearance)
}
}
}

View file

@ -72,9 +72,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case alternativeFolderTabs(Bool)
case playerEmbedding(Bool)
case playlistPlayback(Bool)
case videoCalls(Bool)
case videoCallsReference(Bool)
case videoCallsInfo(PresentationTheme, String)
case enableHighBitrateVideoCalls(Bool)
case hostInfo(PresentationTheme, String)
case versionInfo(PresentationTheme)
@ -90,7 +88,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.experiments.rawValue
case .clearTips, .reimport, .resetData, .resetDatabase, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs, .playerEmbedding, .playlistPlayback:
return DebugControllerSection.experiments.rawValue
case .videoCalls, .videoCallsReference, .videoCallsInfo:
case .enableHighBitrateVideoCalls:
return DebugControllerSection.videoExperiments.rawValue
case .hostInfo, .versionInfo:
return DebugControllerSection.info.rawValue
@ -149,12 +147,8 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 24
case .playlistPlayback:
return 25
case .videoCalls:
case .enableHighBitrateVideoCalls:
return 26
case .videoCallsReference:
return 27
case .videoCallsInfo:
return 28
case .hostInfo:
return 29
case .versionInfo:
@ -576,28 +570,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .videoCalls(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Experimental Feature", value: value, sectionId: self.section, style: .blocks, updated: { value in
case let .enableHighBitrateVideoCalls(value):
return ItemListSwitchItem(presentationData: presentationData, title: "HD Video Calls", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings
settings.videoCalls = value
settings.enableHighBitrateVideoCalls = value
return settings
})
}).start()
})
case let .videoCallsReference(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Reference Implementation", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings
settings.videoCallsReference = value
return settings
})
}).start()
})
case let .videoCallsInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .hostInfo(theme, string):
return ItemListTextItem(presentationData: presentationData, text: .plain(string), sectionId: self.section)
case let .versionInfo(theme):
@ -643,11 +625,7 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
entries.append(.alternativeFolderTabs(experimentalSettings.foldersTabAtBottom))
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
entries.append(.videoCalls(experimentalSettings.videoCalls))
if experimentalSettings.videoCalls {
entries.append(.videoCallsReference(experimentalSettings.videoCallsReference))
}
entries.append(.videoCallsInfo(presentationData.theme, "Enables experimental transmission of electromagnetic radiation synchronized with pressure waves. Needs to be enabled on both sides."))
entries.append(.enableHighBitrateVideoCalls(experimentalSettings.enableHighBitrateVideoCalls))
if let backupHostOverride = networkSettings?.backupHostOverride {
entries.append(.hostInfo(presentationData.theme, "Host: \(backupHostOverride)"))

View file

@ -998,7 +998,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
if let data = image.jpegData(compressionQuality: 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64())
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [])
updateState { state in
var state = state
state.updatingAvatar = .image(representation, true)
@ -1025,7 +1025,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
if let data = image.jpegData(compressionQuality: 0.6) {
let photoResource = LocalFileMediaResource(fileId: arc4random64())
context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource, progressiveSizes: [])
updateState { state in
var state = state
state.updatingAvatar = .image(representation, true)

View file

@ -175,7 +175,7 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE
let apply: () -> Void = {
let settings = WallpaperSettings(blur: mode.contains(.blur), motion: mode.contains(.motion), color: nil, intensity: nil)
let wallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource), TelegramMediaImageRepresentation(dimensions: PixelDimensions(croppedImage.size), resource: resource)], settings)
let wallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource, progressiveSizes: []), TelegramMediaImageRepresentation(dimensions: PixelDimensions(croppedImage.size), resource: resource, progressiveSizes: [])], settings)
updateWallpaper(wallpaper)
DispatchQueue.main.async {
completion()

View file

@ -359,7 +359,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|> mapToSignal { wallpaper -> Signal<TelegramWallpaper?, NoError> in
if let wallpaper = wallpaper, case let .file(file) = wallpaper.wallpaper {
var convertedRepresentations: [ImageRepresentationWithReference] = []
convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 100, height: 100), resource: file.file.resource), reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource)))
convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 100, height: 100), resource: file.file.resource, progressiveSizes: []), reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource)))
return wallpaperDatas(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false)
|> mapToSignal { _, fullSizeData, complete -> Signal<TelegramWallpaper?, NoError> in
guard complete, let fullSizeData = fullSizeData else {

View file

@ -456,7 +456,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
for representation in file.file.previewRepresentations {
convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: .wallpaper(wallpaper: .slug(file.slug), resource: representation.resource)))
}
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource)))
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource, progressiveSizes: []), reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource)))
wallpaperSignal = patternWallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: convertedRepresentations, mode: .screen, autoFetchFullSize: true)
} else if let bottomColor = backgroundColors.1 {

View file

@ -109,10 +109,10 @@ final class ThemeGridSearchItemNode: GridItemNode {
var representations: [TelegramMediaImageRepresentation] = []
if let thumbnailResource = thumbnailResource, let thumbnailDimensions = thumbnailDimensions {
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource, progressiveSizes: []))
}
if let imageResource = imageResource, let imageDimensions = imageDimensions {
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(imageDimensions), resource: imageResource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(imageDimensions), resource: imageResource, progressiveSizes: []))
}
if !representations.isEmpty {
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: [])

View file

@ -231,7 +231,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
for representation in file.file.previewRepresentations {
convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: .wallpaper(wallpaper: .slug(file.slug), resource: representation.resource)))
}
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource)))
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource, progressiveSizes: []), reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource)))
let signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>
let fileReference = FileMediaReference.standalone(media: file.file)

View file

@ -273,7 +273,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
for representation in file.file.previewRepresentations {
convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: reference(for: representation.resource, media: file.file, message: message)))
}
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: reference(for: file.file.resource, media: file.file, message: message)))
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource, progressiveSizes: []), reference: reference(for: file.file.resource, media: file.file, message: message)))
if wallpaper.isPattern {
var patternColors: [UIColor] = []
@ -448,9 +448,9 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
var representations: [TelegramMediaImageRepresentation] = []
if let thumbnailResource = thumbnailResource, let thumbnailDimensions = thumbnailDimensions {
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource, progressiveSizes: []))
}
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(imageDimensions), resource: imageResource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(imageDimensions), resource: imageResource, progressiveSizes: []))
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
signal = chatMessagePhoto(postbox: context.account.postbox, photoReference: .standalone(media: tmpImage))

View file

@ -290,6 +290,8 @@ public final class ShareController: ViewController {
private let presetText: String?
private let switchableAccounts: [AccountWithInfo]
private let immediatePeerId: PeerId?
private let openStats: (() -> Void)?
private let shares: Int?
private let peers = Promise<([(RenderedPeer, PeerPresence?)], Peer)>()
private let peersDisposable = MetaDisposable()
@ -300,11 +302,11 @@ public final class ShareController: ViewController {
public var dismissed: ((Bool) -> Void)?
public convenience init(context: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil) {
self.init(sharedContext: context.sharedContext, currentContext: context, subject: subject, presetText: presetText, preferredAction: preferredAction, showInChat: showInChat, externalShare: externalShare, immediateExternalShare: immediateExternalShare, switchableAccounts: switchableAccounts, immediatePeerId: immediatePeerId)
public convenience init(context: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, openStats: (() -> Void)? = nil, shares: Int? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil) {
self.init(sharedContext: context.sharedContext, currentContext: context, subject: subject, presetText: presetText, preferredAction: preferredAction, showInChat: showInChat, openStats: openStats, shares: shares, externalShare: externalShare, immediateExternalShare: immediateExternalShare, switchableAccounts: switchableAccounts, immediatePeerId: immediatePeerId)
}
public init(sharedContext: SharedAccountContext, currentContext: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil) {
public init(sharedContext: SharedAccountContext, currentContext: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, openStats: (() -> Void)? = nil, shares: Int? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil) {
self.sharedContext = sharedContext
self.currentContext = currentContext
self.currentAccount = currentContext.account
@ -314,6 +316,8 @@ public final class ShareController: ViewController {
self.immediateExternalShare = immediateExternalShare
self.switchableAccounts = switchableAccounts
self.immediatePeerId = immediatePeerId
self.openStats = openStats
self.shares = shares
self.presentationData = self.sharedContext.currentPresentationData.with { $0 }
@ -437,7 +441,7 @@ public final class ShareController: ViewController {
return
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId)
}, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, shares: self.shares)
self.controllerNode.dismiss = { [weak self] shared in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
self?.dismissed?(shared)
@ -714,6 +718,11 @@ public final class ShareController: ViewController {
strongSelf.view.endEditing(true)
strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
if case .messages = self.subject, let openStats = self.openStats {
self.controllerNode.openStats = {
openStats()
}
}
self.displayNodeDidLoad()
self.peersDisposable.set((self.peers.get()

View file

@ -32,6 +32,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
private let externalShare: Bool
private let immediateExternalShare: Bool
private var immediatePeerId: PeerId?
private let shares: Int?
private let defaultAction: ShareControllerAction?
private let requestLayout: (ContainedViewLayoutTransition) -> Void
@ -61,6 +62,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
var share: ((String, [PeerId]) -> Signal<ShareState, NoError>)?
var shareExternal: (() -> Signal<ShareExternalState, NoError>)?
var switchToAnotherAccount: (() -> Void)?
var openStats: (() -> Void)?
let ready = Promise<Bool>()
private var didSetReady = false
@ -78,12 +80,13 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
private let presetText: String?
init(sharedContext: SharedAccountContext, presetText: String?, defaultAction: ShareControllerAction?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, presentError: @escaping (String?, String) -> Void, externalShare: Bool, immediateExternalShare: Bool, immediatePeerId: PeerId?) {
init(sharedContext: SharedAccountContext, presetText: String?, defaultAction: ShareControllerAction?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, presentError: @escaping (String?, String) -> Void, externalShare: Bool, immediateExternalShare: Bool, immediatePeerId: PeerId?, shares: Int?) {
self.sharedContext = sharedContext
self.presentationData = sharedContext.currentPresentationData.with { $0 }
self.externalShare = externalShare
self.immediateExternalShare = immediateExternalShare
self.immediatePeerId = immediatePeerId
self.shares = shares
self.presentError = presentError
self.presetText = presetText
@ -670,7 +673,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
let animated = self.peersContentNode == nil
let peersContentNode = SharePeersContainerNode(sharedContext: self.sharedContext, context: context, switchableAccounts: switchableAccounts, theme: self.presentationData.theme, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, peers: peers, accountPeer: accountPeer, controllerInteraction: self.controllerInteraction!, externalShare: self.externalShare, switchToAnotherAccount: { [weak self] in
self?.switchToAnotherAccount?()
}, extendedInitialReveal: self.presetText != nil)
}, extendedInitialReveal: self.presetText != nil, statsCount: self.shares ?? 0)
self.peersContentNode = peersContentNode
peersContentNode.openSearch = { [weak self] in
let _ = (recentlySearchedPeers(postbox: context.account.postbox)
@ -736,6 +739,14 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
peersContentNode.openShare = {
openShare(false)
}
if let openStats = self.openStats {
peersContentNode.openStats = { [weak self] in
openStats()
self?.animateOut(shared: true, completion: {
self?.dismiss?(true)
})
}
}
if self.immediateExternalShare {
openShare(true)
} else {

View file

@ -81,6 +81,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
private let controllerInteraction: ShareControllerInteraction
private let switchToAnotherAccount: () -> Void
private let extendedInitialReveal: Bool
private let statsCount: Int?
let accountPeer: Peer
private let foundPeers = Promise<[RenderedPeer]>([])
@ -96,11 +97,13 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
private let contentSeparatorNode: ASDisplayNode
private let searchButtonNode: HighlightableButtonNode
private let shareButtonNode: HighlightableButtonNode
private let statsButtonNode: HighlightableButtonNode
private var contentOffsetUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
var openSearch: (() -> Void)?
var openShare: (() -> Void)?
var openStats: (() -> Void)?
private var ensurePeerVisibleOnLayout: PeerId?
private var validLayout: (CGSize, CGFloat)?
@ -108,7 +111,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
let peersValue = Promise<[(RenderedPeer, PeerPresence?)]>()
init(sharedContext: SharedAccountContext, context: AccountContext, switchableAccounts: [AccountWithInfo], theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, peers: [(RenderedPeer, PeerPresence?)], accountPeer: Peer, controllerInteraction: ShareControllerInteraction, externalShare: Bool, switchToAnotherAccount: @escaping () -> Void, extendedInitialReveal: Bool) {
init(sharedContext: SharedAccountContext, context: AccountContext, switchableAccounts: [AccountWithInfo], theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, peers: [(RenderedPeer, PeerPresence?)], accountPeer: Peer, controllerInteraction: ShareControllerInteraction, externalShare: Bool, switchToAnotherAccount: @escaping () -> Void, extendedInitialReveal: Bool, statsCount: Int?) {
self.sharedContext = sharedContext
self.context = context
self.theme = theme
@ -118,6 +121,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
self.accountPeer = accountPeer
self.switchToAnotherAccount = switchToAnotherAccount
self.extendedInitialReveal = extendedInitialReveal
self.statsCount = statsCount
self.peersValue.set(.single(peers))
@ -174,6 +178,13 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
self.shareButtonNode = HighlightableButtonNode()
self.shareButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Share/ShareIcon"), color: self.theme.actionSheet.controlAccentColor), for: [])
self.statsButtonNode = HighlightableButtonNode()
self.statsButtonNode.setAttributedTitle(NSAttributedString(string: "\(statsCount ?? 0) Shares", font: Font.regular(17.0), textColor: self.theme.actionSheet.controlAccentColor), for: .normal)
self.statsButtonNode.isHidden = statsCount == nil
self.contentTitleNode.isHidden = !self.statsButtonNode.isHidden
self.contentSubtitleNode.isHidden = !self.statsButtonNode.isHidden
self.contentSeparatorNode = ASDisplayNode()
self.contentSeparatorNode.isLayerBacked = true
self.contentSeparatorNode.displaysAsynchronously = false
@ -192,6 +203,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
self.addSubnode(self.contentTitleAccountNode)
self.addSubnode(self.searchButtonNode)
self.addSubnode(self.shareButtonNode)
self.addSubnode(self.statsButtonNode)
self.addSubnode(self.contentSeparatorNode)
let previousItems = Atomic<[SharePeerEntry]?>(value: [])
@ -213,6 +225,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
self.searchButtonNode.addTarget(self, action: #selector(self.searchPressed), forControlEvents: .touchUpInside)
self.shareButtonNode.addTarget(self, action: #selector(self.sharePressed), forControlEvents: .touchUpInside)
self.statsButtonNode.addTarget(self, action: #selector(self.statsPressed), forControlEvents: .touchUpInside)
self.contentTitleAccountNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.accountTapGesture(_:))))
}
@ -341,6 +354,9 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
self.contentSubtitleNode.frame = originalSubtitleFrame
transition.updateFrame(node: self.contentSubtitleNode, frame: subtitleFrame)
let statsSize = self.statsButtonNode.measure(CGSize(width: size.width - 44.0 * 2.0 - 8.0 * 2.0, height: titleAreaHeight))
transition.updateFrame(node: self.statsButtonNode, frame: CGRect(origin: CGPoint(x: floor((size.width - statsSize.width) / 2.0), y: titleOffset + 22.0), size: statsSize))
let titleButtonSize = CGSize(width: 44.0, height: 44.0)
let searchButtonFrame = CGRect(origin: CGPoint(x: 12.0, y: titleOffset + 12.0), size: titleButtonSize)
transition.updateFrame(node: self.searchButtonNode, frame: searchButtonFrame)
@ -376,25 +392,34 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
}
func updateSelectedPeers() {
var subtitleText = self.strings.ShareMenu_SelectChats
if !self.controllerInteraction.selectedPeers.isEmpty {
subtitleText = self.controllerInteraction.selectedPeers.reduce("", { string, peer in
let text: String
if peer.peerId == self.accountPeer.id {
text = self.strings.DialogList_SavedMessages
} else {
text = peer.chatMainPeer?.displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder) ?? ""
}
if !string.isEmpty {
return string + ", " + text
} else {
return string + text
}
})
if let _ = self.openStats, self.controllerInteraction.selectedPeers.isEmpty {
self.statsButtonNode.isHidden = false
self.contentTitleNode.isHidden = true
self.contentSubtitleNode.isHidden = true
} else {
self.statsButtonNode.isHidden = true
self.contentTitleNode.isHidden = false
self.contentSubtitleNode.isHidden = false
var subtitleText = self.strings.ShareMenu_SelectChats
if !self.controllerInteraction.selectedPeers.isEmpty {
subtitleText = self.controllerInteraction.selectedPeers.reduce("", { string, peer in
let text: String
if peer.peerId == self.accountPeer.id {
text = self.strings.DialogList_SavedMessages
} else {
text = peer.chatMainPeer?.displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder) ?? ""
}
if !string.isEmpty {
return string + ", " + text
} else {
return string + text
}
})
}
self.contentSubtitleNode.attributedText = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: self.theme.actionSheet.secondaryTextColor)
}
self.contentSubtitleNode.attributedText = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: self.theme.actionSheet.secondaryTextColor)
self.contentGridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ShareControllerPeerGridItemNode {
itemNode.updateSelection(animated: true)
@ -410,6 +435,10 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
self.openShare?()
}
@objc func statsPressed() {
self.openStats?()
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let nodes: [ASDisplayNode] = [self.searchButtonNode, self.shareButtonNode, self.contentTitleAccountNode]
for node in nodes {

View file

@ -5,7 +5,6 @@ import SwiftSignalKit
import Postbox
import TelegramCore
import SyncCore
import MapKit
import TelegramPresentationData
import TelegramUIPreferences
import TelegramStringFormatting

View file

@ -5,7 +5,6 @@ import SwiftSignalKit
import Postbox
import TelegramCore
import SyncCore
import MapKit
import TelegramPresentationData
import TelegramUIPreferences
import TelegramStringFormatting

View file

@ -0,0 +1,265 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import SyncCore
import TelegramPresentationData
import TelegramUIPreferences
import TelegramStringFormatting
import ItemListUI
import ItemListPeerItem
import PresentationDataUtils
import AccountContext
import PresentationDataUtils
import AppBundle
import GraphUI
private final class MessageStatsControllerArguments {
let context: AccountContext
let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>
let openMessage: (MessageId) -> Void
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void) {
self.context = context
self.loadDetailedGraph = loadDetailedGraph
self.openMessage = openMessage
}
}
private enum StatsSection: Int32 {
case overview
case interactions
case publicForwards
}
private enum StatsEntry: ItemListNodeEntry {
case overviewTitle(PresentationTheme, String)
case overview(PresentationTheme, MessageStats, Int32?)
case interactionsTitle(PresentationTheme, String)
case interactionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case publicForwardsTitle(PresentationTheme, String)
case publicForward(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Message)
var section: ItemListSectionId {
switch self {
case .overviewTitle, .overview:
return StatsSection.overview.rawValue
case .interactionsTitle, .interactionsGraph:
return StatsSection.interactions.rawValue
case .publicForwardsTitle, .publicForward:
return StatsSection.publicForwards.rawValue
}
}
var stableId: Int32 {
switch self {
case .overviewTitle:
return 0
case .overview:
return 1
case .interactionsTitle:
return 2
case .interactionsGraph:
return 3
case .publicForwardsTitle:
return 4
case let .publicForward(index, _, _, _, _):
return 5 + index
}
}
static func ==(lhs: StatsEntry, rhs: StatsEntry) -> Bool {
switch lhs {
case let .overviewTitle(lhsTheme, lhsText):
if case let .overviewTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText{
return true
} else {
return false
}
case let .overview(lhsTheme, lhsStats, lhsPublicShares):
if case let .overview(rhsTheme, rhsStats, rhsPublicShares) = rhs, lhsTheme === rhsTheme, lhsStats == rhsStats, lhsPublicShares == rhsPublicShares {
return true
} else {
return false
}
case let .interactionsTitle(lhsTheme, lhsText):
if case let .interactionsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .interactionsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .interactionsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .publicForwardsTitle(lhsTheme, lhsText):
if case let .publicForwardsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .publicForward(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsMessage):
if case let .publicForward(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsMessage) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsMessage.id == rhsMessage.id {
return true
} else {
return false
}
}
}
static func <(lhs: StatsEntry, rhs: StatsEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! MessageStatsControllerArguments
switch self {
case let .overviewTitle(_, text),
let .interactionsTitle(_, text),
let .publicForwardsTitle(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .overview(_, stats, publicShares):
return MessageStatsOverviewItem(presentationData: presentationData, stats: stats, publicShares: publicShares, sectionId: self.section, style: .blocks)
case let .interactionsGraph(_, _, _, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, getDetailsData: { date, completion in
let _ = arguments.loadDetailedGraph(graph, Int64(date.timeIntervalSince1970) * 1000).start(next: { graph in
if let graph = graph, case let .Loaded(_, data) = graph {
completion(data)
}
})
}, sectionId: self.section, style: .blocks)
case let .publicForward(_, _, _, _, message):
var views: Int = 0
for attribute in message.attributes {
if let viewsAttribute = attribute as? ViewCountMessageAttribute {
views = viewsAttribute.count
break
}
}
var text: String = ""
text += "\(views) views"
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .military, dateFormat: .dayFirst, dateSeparator: ".", decimalSeparator: ",", groupingSeparator: ""), nameDisplayOrder: .firstLast, context: arguments.context, peer: message.peers[message.id.peerId]!, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(text), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
arguments.openMessage(message.id)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: nil)
// return StatsMessageItem(context: arguments.context, presentationData: presentationData, message: message, views: 0, forwards: 0, sectionId: self.section, style: .blocks, action: {
// arguments.openMessage(message.id)
// })
}
}
}
private func messageStatsControllerEntries(data: MessageStats?, messages: SearchMessagesResult?, presentationData: PresentationData) -> [StatsEntry] {
var entries: [StatsEntry] = []
if let data = data {
entries.append(.overviewTitle(presentationData.theme, presentationData.strings.Stats_MessageOverview.uppercased()))
entries.append(.overview(presentationData.theme, data, messages?.totalCount))
if !data.interactionsGraph.isEmpty {
entries.append(.interactionsTitle(presentationData.theme, presentationData.strings.Stats_MessageInteractionsTitle.uppercased()))
entries.append(.interactionsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.interactionsGraph, .twoAxisStep))
}
if let messages = messages, !messages.messages.isEmpty {
entries.append(.publicForwardsTitle(presentationData.theme, presentationData.strings.Stats_MessagePublicForwardsTitle.uppercased()))
var index: Int32 = 0
for message in messages.messages {
entries.append(.publicForward(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, message))
index += 1
}
}
}
return entries
}
public func messageStatsController(context: AccountContext, messageId: MessageId, cachedPeerData: CachedPeerData) -> ViewController {
var navigateToMessageImpl: ((MessageId) -> Void)?
let actionsDisposable = DisposableSet()
let dataPromise = Promise<MessageStats?>(nil)
let messagesPromise = Promise<(SearchMessagesResult, SearchMessagesState)?>(nil)
var datacenterId: Int32 = 0
if let cachedData = cachedPeerData as? CachedChannelData {
datacenterId = cachedData.statsDatacenterId
}
let statsContext = MessageStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, messageId: messageId)
let dataSignal: Signal<MessageStats?, NoError> = statsContext.state
|> map { state in
return state.stats
}
dataPromise.set(.single(nil) |> then(dataSignal))
let arguments = MessageStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal<StatsGraph?, NoError> in
return statsContext.loadDetailedGraph(graph, x: x)
}, openMessage: { messageId in
navigateToMessageImpl?(messageId)
})
let longLoadingSignal: Signal<Bool, NoError> = .single(false) |> then(.single(true) |> delay(2.0, queue: Queue.mainQueue()))
let previousData = Atomic<MessageStats?>(value: nil)
let searchSignal = searchMessages(account: context.account, location: .publicForwards(messageId: messageId, datacenterId: Int(datacenterId)), query: "", state: nil)
|> map(Optional.init)
|> afterNext { result in
if let result = result {
for message in result.0.messages {
if let peer = message.peers[message.id.peerId], let peerReference = PeerReference(peer) {
let _ = updatedRemotePeer(postbox: context.account.postbox, network: context.account.network, peer: peerReference).start()
}
}
}
}
messagesPromise.set(.single(nil) |> then(searchSignal))
let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get(), messagesPromise.get(), longLoadingSignal)
|> deliverOnMainQueue
|> map { presentationData, data, search, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
let previous = previousData.swap(data)
var emptyStateItem: ItemListControllerEmptyStateItem?
if data == nil {
if longLoading {
emptyStateItem = StatsEmptyStateItem(theme: presentationData.theme, strings: presentationData.strings)
} else {
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
}
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Stats_MessageTitle), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: messageStatsControllerEntries(data: data, messages: search?.0, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false)
return (controllerState, (listState, arguments))
}
|> afterDisposed {
actionsDisposable.dispose()
let _ = statsContext.state
}
let controller = ItemListController(context: context, state: signal)
controller.contentOffsetChanged = { [weak controller] _, _ in
controller?.forEachItemNode({ itemNode in
if let itemNode = itemNode as? StatsGraphItemNode {
itemNode.resetInteraction()
}
})
}
controller.didDisappear = { [weak controller] _ in
controller?.clearItemNodesHighlight(animated: true)
}
navigateToMessageImpl = { [weak controller] messageId in
if let navigationController = controller?.navigationController as? NavigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(messageId.peerId), subject: .message(messageId), keepStack: .always, useExisting: false, purposefulAction: {}, peekData: nil))
}
}
return controller
}

View file

@ -0,0 +1,294 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import TelegramCore
import SyncCore
import TelegramPresentationData
import ItemListUI
import PresentationDataUtils
class MessageStatsOverviewItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let stats: MessageStats
let publicShares: Int32?
let sectionId: ItemListSectionId
let style: ItemListStyle
init(presentationData: ItemListPresentationData, stats: MessageStats, publicShares: Int32?, sectionId: ItemListSectionId, style: ItemListStyle) {
self.presentationData = presentationData
self.stats = stats
self.publicShares = publicShares
self.sectionId = sectionId
self.style = style
}
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 = MessageStatsOverviewItemNode()
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? MessageStatsOverviewItemNode {
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()
})
}
}
}
}
}
var selectable: Bool = false
}
class MessageStatsOverviewItemNode: ListViewItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode
private let leftValueLabel: ImmediateTextNode
private let centerValueLabel: ImmediateTextNode
private let rightValueLabel: ImmediateTextNode
private let leftTitleLabel: ImmediateTextNode
private let centerTitleLabel: ImmediateTextNode
private let rightTitleLabel: ImmediateTextNode
private var item: MessageStatsOverviewItem?
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.maskNode = ASImageNode()
self.leftValueLabel = ImmediateTextNode()
self.centerValueLabel = ImmediateTextNode()
self.rightValueLabel = ImmediateTextNode()
self.leftTitleLabel = ImmediateTextNode()
self.centerTitleLabel = ImmediateTextNode()
self.rightTitleLabel = ImmediateTextNode()
super.init(layerBacked: false, dynamicBounce: false)
self.clipsToBounds = true
self.addSubnode(self.leftValueLabel)
self.addSubnode(self.centerValueLabel)
self.addSubnode(self.rightValueLabel)
self.addSubnode(self.leftTitleLabel)
self.addSubnode(self.centerTitleLabel)
self.addSubnode(self.rightTitleLabel)
}
func asyncLayout() -> (_ item: MessageStatsOverviewItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeLeftValueLabelLayout = TextNode.asyncLayout(self.leftValueLabel)
let makeRightValueLabelLayout = TextNode.asyncLayout(self.rightValueLabel)
let makeCenterValueLabelLayout = TextNode.asyncLayout(self.centerValueLabel)
let makeLeftTitleLabelLayout = TextNode.asyncLayout(self.leftTitleLabel)
let makeRightTitleLabelLayout = TextNode.asyncLayout(self.rightTitleLabel)
let makeCenterTitleLabelLayout = TextNode.asyncLayout(self.centerTitleLabel)
let currentItem = self.item
return { item, params, neighbors in
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor
let topInset: CGFloat = 14.0
let sideInset: CGFloat = 16.0
var height: CGFloat = topInset * 2.0
let leftInset = params.leftInset
let rightInset = params.rightInset
var updatedTheme: PresentationTheme?
if currentItem?.presentationData.theme !== item.presentationData.theme {
updatedTheme = item.presentationData.theme
}
switch item.style {
case .plain:
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor
insets = itemListNeighborsPlainInsets(neighbors)
case .blocks:
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
insets = itemListNeighborsGroupedInsets(neighbors)
}
let valueFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize)
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
let leftValueLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let rightValueLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let centerValueLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let leftTitleLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let rightTitleLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let centerTitleLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
leftValueLabelLayoutAndApply = makeLeftValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: compactNumericCountString(item.stats.views), font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
centerValueLabelLayoutAndApply = makeCenterValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.publicShares.flatMap { compactNumericCountString(Int($0)) } ?? "", font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
rightValueLabelLayoutAndApply = makeRightValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.publicShares.flatMap { "\( compactNumericCountString(item.stats.forwards - Int($0)))" } ?? "", font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
leftTitleLabelLayoutAndApply = makeLeftTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Views", font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
centerTitleLabelLayoutAndApply = makeCenterTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Public Shares", font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
rightTitleLabelLayoutAndApply = makeRightTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Private Shares", font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
height += rightValueLabelLayoutAndApply!.0.size.height + rightTitleLabelLayoutAndApply!.0.size.height
let contentSize = CGSize(width: params.width, height: height)
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
if let strongSelf = self {
strongSelf.item = item
let _ = leftValueLabelLayoutAndApply?.1()
let _ = centerValueLabelLayoutAndApply?.1()
let _ = rightValueLabelLayoutAndApply?.1()
let _ = leftTitleLabelLayoutAndApply?.1()
let _ = centerTitleLabelLayoutAndApply?.1()
let _ = rightTitleLabelLayoutAndApply?.1()
if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
}
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)
}
if strongSelf.maskNode.supernode != nil {
strongSelf.maskNode.removeFromSupernode()
}
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)
}
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
}
let bottomStripeInset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = leftInset
default:
bottomStripeInset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
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.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
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))
}
let maxLeftWidth = max(leftValueLabelLayoutAndApply?.0.size.width ?? 0.0, leftTitleLabelLayoutAndApply?.0.size.width ?? 0.0)
let maxCenterWidth = max(centerValueLabelLayoutAndApply?.0.size.width ?? 0.0, centerTitleLabelLayoutAndApply?.0.size.width ?? 0.0)
let maxRightWidth = max(rightValueLabelLayoutAndApply?.0.size.width ?? 0.0, rightTitleLabelLayoutAndApply?.0.size.width ?? 0.0)
let horizontalSpacing = min(60, (params.width - leftInset - rightInset - sideInset * 2.0 - maxLeftWidth - maxCenterWidth - maxRightWidth) / 2.0)
var x: CGFloat = leftInset + (params.width - maxLeftWidth - maxCenterWidth - maxRightWidth - horizontalSpacing * 2.0) / 2.0
if let leftValueLabelLayout = leftValueLabelLayoutAndApply?.0, let leftTitleLabelLayout = leftTitleLabelLayoutAndApply?.0 {
strongSelf.leftValueLabel.frame = CGRect(origin: CGPoint(x: x, y: topInset), size: leftValueLabelLayout.size)
strongSelf.leftTitleLabel.frame = CGRect(origin: CGPoint(x: x, y: strongSelf.leftValueLabel.frame.maxY), size: leftTitleLabelLayout.size)
x += max(leftValueLabelLayout.size.width, leftTitleLabelLayout.size.width) + horizontalSpacing
}
if let centerValueLabelLayout = centerValueLabelLayoutAndApply?.0, let centerTitleLabelLayout = centerTitleLabelLayoutAndApply?.0 {
strongSelf.centerValueLabel.frame = CGRect(origin: CGPoint(x: x, y: topInset), size: centerValueLabelLayout.size)
strongSelf.centerTitleLabel.frame = CGRect(origin: CGPoint(x: x, y: strongSelf.centerValueLabel.frame.maxY), size: centerTitleLabelLayout.size)
x += max(centerValueLabelLayout.size.width, centerTitleLabelLayout.size.width) + horizontalSpacing
}
if let rightValueLabelLayout = rightValueLabelLayoutAndApply?.0, let rightTitleLabelLayout = rightTitleLabelLayoutAndApply?.0 {
strongSelf.rightValueLabel.frame = CGRect(origin: CGPoint(x: x, y: topInset), size: rightValueLabelLayout.size)
strongSelf.rightTitleLabel.frame = CGRect(origin: CGPoint(x: x, y: strongSelf.rightValueLabel.frame.maxY), size: rightTitleLabelLayout.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

@ -55,7 +55,7 @@ final class StatsEmptyStateItemNode: ItemListControllerEmptyStateItemNode {
self.animationNode = AnimatedStickerNode()
if let path = getAppBundle().path(forResource: "Charts", ofType: "tgs") {
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 192, height: 192, playbackMode: .loop, mode: .direct)
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 192, height: 192, playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
self.animationNode.visibility = true
}

View file

@ -173,7 +173,7 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
let leftInset = 16.0 + params.leftInset
let rightInset = 16.0 + params.rightInset
var totalLeftInset = leftInset
let additionalRightInset: CGFloat = 93.0
let additionalRightInset: CGFloat = 128.0
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
@ -229,9 +229,9 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: label, font: labelFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - totalLeftInset - rightInset - additionalRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (viewsLayout, viewsApply) = makeViewsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_MessageViews(item.views), font: labelFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .right, cutout: nil, insets: UIEdgeInsets()))
let (viewsLayout, viewsApply) = makeViewsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_MessageViews(item.views), font: labelFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 128.0, height: CGFloat.greatestFiniteMagnitude), alignment: .right, cutout: nil, insets: UIEdgeInsets()))
let (forwardsLayout, forwardsApply) = makeForwardsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_MessageForwards(item.forwards), font: labelFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .right, cutout: nil, insets: UIEdgeInsets()))
let (forwardsLayout, forwardsApply) = makeForwardsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_MessageForwards(item.forwards), font: labelFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 128.0, height: CGFloat.greatestFiniteMagnitude), alignment: .right, cutout: nil, insets: UIEdgeInsets()))
let verticalInset: CGFloat = 11.0
let titleSpacing: CGFloat = 3.0

View file

@ -93,7 +93,7 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
let dimensions = item.file.dimensions ?? PixelDimensions(width: 512, height: 512)
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0))
self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct)
self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil))
self.animationNode?.visibility = true
self.animationNode?.addSubnode(self.textNode)
} else {

View file

@ -8,7 +8,8 @@ public final class CachedUserData: CachedPeerData {
public let pinnedMessageId: MessageId?
public let isBlocked: Bool
public let commonGroupCount: Int32
public let callsAvailable: Bool
public let voiceCallsAvailable: Bool
public let videoCallsAvailable: Bool
public let callsPrivate: Bool
public let canPinMessages: Bool
public let hasScheduledMessages: Bool
@ -24,21 +25,23 @@ public final class CachedUserData: CachedPeerData {
self.pinnedMessageId = nil
self.isBlocked = false
self.commonGroupCount = 0
self.callsAvailable = false
self.voiceCallsAvailable = false
self.videoCallsAvailable = false
self.callsPrivate = false
self.canPinMessages = false
self.hasScheduledMessages = false
self.messageIds = Set()
}
public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, callsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool) {
public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool) {
self.about = about
self.botInfo = botInfo
self.peerStatusSettings = peerStatusSettings
self.pinnedMessageId = pinnedMessageId
self.isBlocked = isBlocked
self.commonGroupCount = commonGroupCount
self.callsAvailable = callsAvailable
self.voiceCallsAvailable = voiceCallsAvailable
self.videoCallsAvailable = videoCallsAvailable
self.callsPrivate = callsPrivate
self.canPinMessages = canPinMessages
self.hasScheduledMessages = hasScheduledMessages
@ -66,7 +69,8 @@ public final class CachedUserData: CachedPeerData {
}
self.isBlocked = decoder.decodeInt32ForKey("b", orElse: 0) != 0
self.commonGroupCount = decoder.decodeInt32ForKey("cg", orElse: 0)
self.callsAvailable = decoder.decodeInt32ForKey("ca", orElse: 0) != 0
self.voiceCallsAvailable = decoder.decodeInt32ForKey("ca", orElse: 0) != 0
self.videoCallsAvailable = decoder.decodeInt32ForKey("vca", orElse: 0) != 0
self.callsPrivate = decoder.decodeInt32ForKey("cp", orElse: 0) != 0
self.canPinMessages = decoder.decodeInt32ForKey("cpm", orElse: 0) != 0
self.hasScheduledMessages = decoder.decodeBoolForKey("hsm", orElse: false)
@ -105,7 +109,8 @@ public final class CachedUserData: CachedPeerData {
}
encoder.encodeInt32(self.isBlocked ? 1 : 0, forKey: "b")
encoder.encodeInt32(self.commonGroupCount, forKey: "cg")
encoder.encodeInt32(self.callsAvailable ? 1 : 0, forKey: "ca")
encoder.encodeInt32(self.voiceCallsAvailable ? 1 : 0, forKey: "ca")
encoder.encodeInt32(self.videoCallsAvailable ? 1 : 0, forKey: "vca")
encoder.encodeInt32(self.callsPrivate ? 1 : 0, forKey: "cp")
encoder.encodeInt32(self.canPinMessages ? 1 : 0, forKey: "cpm")
encoder.encodeBool(self.hasScheduledMessages, forKey: "hsm")
@ -123,46 +128,50 @@ public final class CachedUserData: CachedPeerData {
return false
}
return other.about == self.about && other.botInfo == self.botInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.callsAvailable == other.callsAvailable && self.callsPrivate == other.callsPrivate && self.hasScheduledMessages == other.hasScheduledMessages
return other.about == self.about && other.botInfo == self.botInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.voiceCallsAvailable == other.voiceCallsAvailable && self.videoCallsAvailable == other.videoCallsAvailable && self.callsPrivate == other.callsPrivate && self.hasScheduledMessages == other.hasScheduledMessages
}
public func withUpdatedAbout(_ about: String?) -> CachedUserData {
return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
}
public func withUpdatedBotInfo(_ botInfo: BotInfo?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
}
public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
}
public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
}
public func withUpdatedIsBlocked(_ isBlocked: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
}
public func withUpdatedCommonGroupCount(_ commonGroupCount: Int32) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
}
public func withUpdatedCallsAvailable(_ callsAvailable: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
public func withUpdatedVoiceCallsAvailable(_ voiceCallsAvailable: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
}
public func withUpdatedVideoCallsAvailable(_ videoCallsAvailable: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
}
public func withUpdatedCallsPrivate(_ callsPrivate: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
}
public func withUpdatedCanPinMessages(_ canPinMessages: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
}
public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages)
}
}

View file

@ -0,0 +1,20 @@
import Foundation
import Postbox
public class ForwardCountMessageAttribute: MessageAttribute {
public let count: Int
public var associatedMessageIds: [MessageId] = []
public init(count: Int) {
self.count = count
}
required public init(decoder: PostboxDecoder) {
self.count = Int(decoder.decodeInt32ForKey("c", orElse: 0))
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(Int32(self.count), forKey: "c")
}
}

View file

@ -303,21 +303,25 @@ public final class TelegramMediaImage: Media, Equatable, Codable {
public final class TelegramMediaImageRepresentation: PostboxCoding, Equatable, CustomStringConvertible {
public let dimensions: PixelDimensions
public let resource: TelegramMediaResource
public let progressiveSizes: [Int32]
public init(dimensions: PixelDimensions, resource: TelegramMediaResource) {
public init(dimensions: PixelDimensions, resource: TelegramMediaResource, progressiveSizes: [Int32]) {
self.dimensions = dimensions
self.resource = resource
self.progressiveSizes = progressiveSizes
}
public init(decoder: PostboxDecoder) {
self.dimensions = PixelDimensions(width: decoder.decodeInt32ForKey("dx", orElse: 0), height: decoder.decodeInt32ForKey("dy", orElse: 0))
self.resource = decoder.decodeObjectForKey("r") as? TelegramMediaResource ?? EmptyMediaResource()
self.progressiveSizes = decoder.decodeInt32ArrayForKey("ps")
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.dimensions.width, forKey: "dx")
encoder.encodeInt32(self.dimensions.height, forKey: "dy")
encoder.encodeObject(self.resource, forKey: "r")
encoder.encodeInt32Array(self.progressiveSizes, forKey: "ps")
}
public var description: String {
@ -331,6 +335,9 @@ public final class TelegramMediaImageRepresentation: PostboxCoding, Equatable, C
if !self.resource.id.isEqual(to: other.resource.id) {
return false
}
if self.progressiveSizes != other.progressiveSizes {
return false
}
return true
}
}

View file

@ -1,7 +1,6 @@
import Foundation
import Postbox
public class ViewCountMessageAttribute: MessageAttribute {
public let count: Int

View file

@ -254,6 +254,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1512627963] = { return Api.Update.parse_updateDialogFilterOrder($0) }
dict[889491791] = { return Api.Update.parse_updateDialogFilters($0) }
dict[643940105] = { return Api.Update.parse_updatePhoneCallSignalingData($0) }
dict[1708307556] = { return Api.Update.parse_updateChannelParticipant($0) }
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) }
@ -290,6 +291,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[2009052699] = { return Api.PhotoSize.parse_photoSize($0) }
dict[-374917894] = { return Api.PhotoSize.parse_photoCachedSize($0) }
dict[-525288402] = { return Api.PhotoSize.parse_photoStrippedSize($0) }
dict[1520986705] = { return Api.PhotoSize.parse_photoSizeProgressive($0) }
dict[-244016606] = { return Api.messages.Stickers.parse_stickersNotModified($0) }
dict[-463889475] = { return Api.messages.Stickers.parse_stickers($0) }
dict[-1096616924] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) }
@ -703,6 +705,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-395967805] = { return Api.messages.AllStickers.parse_allStickersNotModified($0) }
dict[-302170017] = { return Api.messages.AllStickers.parse_allStickers($0) }
dict[-1655957568] = { return Api.PhoneConnection.parse_phoneConnection($0) }
dict[1667228533] = { return Api.PhoneConnection.parse_phoneConnectionWebrtc($0) }
dict[-206688531] = { return Api.help.UserInfo.parse_userInfoEmpty($0) }
dict[32192344] = { return Api.help.UserInfo.parse_userInfo($0) }
dict[-1194283041] = { return Api.AccountDaysTTL.parse_accountDaysTTL($0) }

View file

@ -6037,6 +6037,7 @@ public extension Api {
case updateDialogFilterOrder(order: [Int32])
case updateDialogFilters
case updatePhoneCallSignalingData(phoneCallId: Int64, data: Buffer)
case updateChannelParticipant(flags: Int32, channelId: Int32, date: Int32, userId: Int32, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, qts: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -6717,6 +6718,18 @@ public extension Api {
serializeInt64(phoneCallId, buffer: buffer, boxed: false)
serializeBytes(data, buffer: buffer, boxed: false)
break
case .updateChannelParticipant(let flags, let channelId, let date, let userId, let prevParticipant, let newParticipant, let qts):
if boxed {
buffer.appendInt32(1708307556)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(channelId, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
serializeInt32(userId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {prevParticipant!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {newParticipant!.serialize(buffer, true)}
serializeInt32(qts, buffer: buffer, boxed: false)
break
}
}
@ -6884,6 +6897,8 @@ public extension Api {
return ("updateDialogFilters", [])
case .updatePhoneCallSignalingData(let phoneCallId, let data):
return ("updatePhoneCallSignalingData", [("phoneCallId", phoneCallId), ("data", data)])
case .updateChannelParticipant(let flags, let channelId, let date, let userId, let prevParticipant, let newParticipant, let qts):
return ("updateChannelParticipant", [("flags", flags), ("channelId", channelId), ("date", date), ("userId", userId), ("prevParticipant", prevParticipant), ("newParticipant", newParticipant), ("qts", qts)])
}
}
@ -8229,6 +8244,39 @@ public extension Api {
return nil
}
}
public static func parse_updateChannelParticipant(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
var _4: Int32?
_4 = reader.readInt32()
var _5: Api.ChannelParticipant?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant
} }
var _6: Api.ChannelParticipant?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant
} }
var _7: Int32?
_7 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.Update.updateChannelParticipant(flags: _1!, channelId: _2!, date: _3!, userId: _4!, prevParticipant: _5, newParticipant: _6, qts: _7!)
}
else {
return nil
}
}
}
public enum PopularContact: TypeConstructorDescription {
@ -9090,6 +9138,7 @@ public extension Api {
case photoSize(type: String, location: Api.FileLocation, w: Int32, h: Int32, size: Int32)
case photoCachedSize(type: String, location: Api.FileLocation, w: Int32, h: Int32, bytes: Buffer)
case photoStrippedSize(type: String, bytes: Buffer)
case photoSizeProgressive(type: String, location: Api.FileLocation, w: Int32, h: Int32, sizes: [Int32])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -9126,6 +9175,20 @@ public extension Api {
serializeString(type, buffer: buffer, boxed: false)
serializeBytes(bytes, buffer: buffer, boxed: false)
break
case .photoSizeProgressive(let type, let location, let w, let h, let sizes):
if boxed {
buffer.appendInt32(1520986705)
}
serializeString(type, buffer: buffer, boxed: false)
location.serialize(buffer, true)
serializeInt32(w, buffer: buffer, boxed: false)
serializeInt32(h, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(sizes.count))
for item in sizes {
serializeInt32(item, buffer: buffer, boxed: false)
}
break
}
}
@ -9139,6 +9202,8 @@ public extension Api {
return ("photoCachedSize", [("type", type), ("location", location), ("w", w), ("h", h), ("bytes", bytes)])
case .photoStrippedSize(let type, let bytes):
return ("photoStrippedSize", [("type", type), ("bytes", bytes)])
case .photoSizeProgressive(let type, let location, let w, let h, let sizes):
return ("photoSizeProgressive", [("type", type), ("location", location), ("w", w), ("h", h), ("sizes", sizes)])
}
}
@ -9217,6 +9282,33 @@ public extension Api {
return nil
}
}
public static func parse_photoSizeProgressive(_ reader: BufferReader) -> PhotoSize? {
var _1: String?
_1 = parseString(reader)
var _2: Api.FileLocation?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.FileLocation
}
var _3: Int32?
_3 = reader.readInt32()
var _4: Int32?
_4 = reader.readInt32()
var _5: [Int32]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.PhotoSize.photoSizeProgressive(type: _1!, location: _2!, w: _3!, h: _4!, sizes: _5!)
}
else {
return nil
}
}
}
public enum GlobalPrivacySettings: TypeConstructorDescription {
@ -19749,6 +19841,7 @@ public extension Api {
}
public enum PhoneConnection: TypeConstructorDescription {
case phoneConnection(id: Int64, ip: String, ipv6: String, port: Int32, peerTag: Buffer)
case phoneConnectionWebrtc(flags: Int32, id: Int64, ip: String, ipv6: String, port: Int32, username: String, password: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -19762,6 +19855,18 @@ public extension Api {
serializeInt32(port, buffer: buffer, boxed: false)
serializeBytes(peerTag, buffer: buffer, boxed: false)
break
case .phoneConnectionWebrtc(let flags, let id, let ip, let ipv6, let port, let username, let password):
if boxed {
buffer.appendInt32(1667228533)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false)
serializeString(ip, buffer: buffer, boxed: false)
serializeString(ipv6, buffer: buffer, boxed: false)
serializeInt32(port, buffer: buffer, boxed: false)
serializeString(username, buffer: buffer, boxed: false)
serializeString(password, buffer: buffer, boxed: false)
break
}
}
@ -19769,6 +19874,8 @@ public extension Api {
switch self {
case .phoneConnection(let id, let ip, let ipv6, let port, let peerTag):
return ("phoneConnection", [("id", id), ("ip", ip), ("ipv6", ipv6), ("port", port), ("peerTag", peerTag)])
case .phoneConnectionWebrtc(let flags, let id, let ip, let ipv6, let port, let username, let password):
return ("phoneConnectionWebrtc", [("flags", flags), ("id", id), ("ip", ip), ("ipv6", ipv6), ("port", port), ("username", username), ("password", password)])
}
}
@ -19795,6 +19902,35 @@ public extension Api {
return nil
}
}
public static func parse_phoneConnectionWebrtc(_ reader: BufferReader) -> PhoneConnection? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: String?
_3 = parseString(reader)
var _4: String?
_4 = parseString(reader)
var _5: Int32?
_5 = reader.readInt32()
var _6: String?
_6 = parseString(reader)
var _7: String?
_7 = parseString(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.PhoneConnection.phoneConnectionWebrtc(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, username: _6!, password: _7!)
}
else {
return nil
}
}
}
public enum AccountDaysTTL: TypeConstructorDescription {

View file

@ -5369,6 +5369,34 @@ public extension Api {
})
}
public static func getPromoData() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.help.PromoData>) {
let buffer = Buffer()
buffer.appendInt32(-1063816159)
return (FunctionDescription(name: "help.getPromoData", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PromoData? in
let reader = BufferReader(buffer)
var result: Api.help.PromoData?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.help.PromoData
}
return result
})
}
public static func hidePromoData(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(505748629)
peer.serialize(buffer, true)
return (FunctionDescription(name: "help.hidePromoData", parameters: [("peer", peer)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
public static func editUserInfo(userId: Api.InputUser, message: String, entities: [Api.MessageEntity]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.help.UserInfo>) {
let buffer = Buffer()
buffer.appendInt32(1723407216)
@ -5403,34 +5431,6 @@ public extension Api {
})
}
public static func getPromoData() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.help.PromoData>) {
let buffer = Buffer()
buffer.appendInt32(-1063816159)
return (FunctionDescription(name: "help.getPromoData", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PromoData? in
let reader = BufferReader(buffer)
var result: Api.help.PromoData?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.help.PromoData
}
return result
})
}
public static func hidePromoData(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(505748629)
peer.serialize(buffer, true)
return (FunctionDescription(name: "help.hidePromoData", parameters: [("peer", peer)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
public static func dismissSuggestion(suggestion: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(125807007)
@ -6802,14 +6802,11 @@ public extension Api {
})
}
public static func uploadProfilePhoto(flags: Int32, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.photos.Photo>) {
public static func updateProfilePhoto(id: Api.InputPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.photos.Photo>) {
let buffer = Buffer()
buffer.appendInt32(-1980559511)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {file!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {video!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "photos.uploadProfilePhoto", parameters: [("flags", flags), ("file", file), ("video", video), ("videoStartTs", videoStartTs)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in
buffer.appendInt32(1926525996)
id.serialize(buffer, true)
return (FunctionDescription(name: "photos.updateProfilePhoto", parameters: [("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in
let reader = BufferReader(buffer)
var result: Api.photos.Photo?
if let signature = reader.readInt32() {
@ -6819,11 +6816,14 @@ public extension Api {
})
}
public static func updateProfilePhoto(id: Api.InputPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.photos.Photo>) {
public static func uploadProfilePhoto(flags: Int32, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.photos.Photo>) {
let buffer = Buffer()
buffer.appendInt32(1926525996)
id.serialize(buffer, true)
return (FunctionDescription(name: "photos.updateProfilePhoto", parameters: [("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in
buffer.appendInt32(-1980559511)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {file!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {video!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "photos.uploadProfilePhoto", parameters: [("flags", flags), ("file", file), ("video", video), ("videoStartTs", videoStartTs)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in
let reader = BufferReader(buffer)
var result: Api.photos.Photo?
if let signature = reader.readInt32() {

View file

@ -7,7 +7,7 @@ static_library(
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/Display:Display#shared",
"//submodules/Display:Display#shared",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/PhotoResources:PhotoResources",
@ -21,6 +21,7 @@ static_library(
"//submodules/AppBundle:AppBundle",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/TelegramCallsUI/CallsEmoji:CallsEmoji",
"//submodules/SemanticStatusNode:SemanticStatusNode",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View file

@ -22,6 +22,7 @@ swift_library(
"//submodules/AppBundle:AppBundle",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/TelegramCallsUI/CallsEmoji:CallsEmoji",
"//submodules/SemanticStatusNode:SemanticStatusNode",
],
visibility = [
"//visibility:public",

View file

@ -4,6 +4,7 @@ import Display
import AsyncDisplayKit
import SwiftSignalKit
import AppBundle
import SemanticStatusNode
private let labelFont = Font.regular(13.0)
@ -32,18 +33,22 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
var appearance: Appearance
var image: Image
var isEnabled: Bool
var hasProgress: Bool
init(appearance: Appearance, image: Image, isEnabled: Bool = true) {
init(appearance: Appearance, image: Image, isEnabled: Bool = true, hasProgress: Bool = false) {
self.appearance = appearance
self.image = image
self.isEnabled = isEnabled
self.hasProgress = hasProgress
}
}
private let contentContainer: ASDisplayNode
private let effectView: UIVisualEffectView
private let contentBackgroundNode: ASImageNode
private let contentNode: ASImageNode
private let overlayHighlightNode: ASImageNode
private var statusNode: SemanticStatusNode?
private let textNode: ImmediateTextNode
private let largeButtonSize: CGFloat = 72.0
@ -60,6 +65,9 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
self.effectView.clipsToBounds = true
self.effectView.isUserInteractionEnabled = false
self.contentBackgroundNode = ASImageNode()
self.contentBackgroundNode.isUserInteractionEnabled = false
self.contentNode = ASImageNode()
self.contentNode.isUserInteractionEnabled = false
@ -79,6 +87,7 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
self.addSubnode(self.textNode)
self.contentContainer.view.addSubview(self.effectView)
self.contentContainer.addSubnode(self.contentBackgroundNode)
self.contentContainer.addSubnode(self.contentNode)
self.contentContainer.addSubnode(self.overlayHighlightNode)
@ -88,9 +97,13 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
}
if highlighted {
strongSelf.overlayHighlightNode.alpha = 1.0
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .spring)
transition.updateSublayerTransformScale(node: strongSelf, scale: 0.9)
} else {
strongSelf.overlayHighlightNode.alpha = 0.0
strongSelf.overlayHighlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
let transition: ContainedViewLayoutTransition = .animated(duration: 0.5, curve: .spring)
transition.updateSublayerTransformScale(node: strongSelf, scale: 1.0)
}
}
}
@ -101,12 +114,34 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
let isSmall = self.largeButtonSize > size.width
self.effectView.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.largeButtonSize, height: self.largeButtonSize))
self.contentBackgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.largeButtonSize, height: self.largeButtonSize))
self.contentNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.largeButtonSize, height: self.largeButtonSize))
self.overlayHighlightNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.largeButtonSize, height: self.largeButtonSize))
if self.currentContent != content {
self.currentContent = content
if content.hasProgress {
if self.statusNode == nil {
let statusNode = SemanticStatusNode(backgroundNodeColor: .white, foregroundNodeColor: .clear)
self.statusNode = statusNode
self.contentContainer.insertSubnode(statusNode, belowSubnode: self.contentNode)
statusNode.transitionToState(.progress(value: nil, cancelEnabled: false, appearance: SemanticStatusNodeState.ProgressAppearance(inset: 4.0, lineWidth: 3.0)), animated: false, completion: {})
}
if let statusNode = self.statusNode {
statusNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.largeButtonSize, height: self.largeButtonSize))
if transition.isAnimated {
statusNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
} else if let statusNode = self.statusNode {
self.statusNode = nil
transition.updateAlpha(node: statusNode, alpha: 0.0, completion: { [weak statusNode] _ in
statusNode?.removeFromSupernode()
})
}
switch content.appearance {
case .blurred:
self.effectView.isHidden = false
@ -117,19 +152,29 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
self.alpha = content.isEnabled ? 1.0 : 0.7
self.isUserInteractionEnabled = content.isEnabled
let contentBackgroundImage: UIImage? = nil
let contentImage = generateImage(CGSize(width: self.largeButtonSize, height: self.largeButtonSize), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
var fillColor: UIColor = .clear
var imageColor: UIColor = .white
var drawOverMask = false
context.setBlendMode(.normal)
var imageScale: CGFloat = 1.0
switch content.appearance {
case let .blurred(isFilled):
if isFilled {
fillColor = .white
drawOverMask = true
if content.hasProgress {
fillColor = .clear
imageColor = .black
drawOverMask = false
context.setBlendMode(.copy)
} else {
if isFilled {
fillColor = .white
drawOverMask = true
context.setBlendMode(.copy)
}
}
let smallButtonSize: CGFloat = 60.0
imageScale = self.largeButtonSize / smallButtonSize
@ -149,19 +194,19 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
switch content.image {
case .camera:
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallCameraButton"), color: .white)
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallCameraButton"), color: imageColor)
case .mute:
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallMuteButton"), color: .white)
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallMuteButton"), color: imageColor)
case .flipCamera:
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallSwitchCameraButton"), color: .white)
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallSwitchCameraButton"), color: imageColor)
case .bluetooth:
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallBluetoothButton"), color: .white)
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallBluetoothButton"), color: imageColor)
case .speaker:
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallSpeakerButton"), color: .white)
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallSpeakerButton"), color: imageColor)
case .accept:
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallAcceptButton"), color: .white)
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallAcceptButton"), color: imageColor)
case .end:
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallDeclineButton"), color: .white)
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallDeclineButton"), color: imageColor)
}
if let image = image {
@ -180,6 +225,14 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
}
}
})
if transition.isAnimated, let contentBackgroundImage = contentBackgroundImage, let previousContent = self.contentBackgroundNode.image {
self.contentBackgroundNode.image = contentBackgroundImage
self.contentBackgroundNode.layer.animate(from: previousContent.cgImage!, to: contentBackgroundImage.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
} else {
self.contentBackgroundNode.image = contentBackgroundImage
}
if transition.isAnimated, let contentImage = contentImage, let previousContent = self.contentNode.image {
self.contentNode.image = contentImage
self.contentNode.layer.animate(from: previousContent.cgImage!, to: contentImage.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)

View file

@ -17,9 +17,9 @@ enum CallControllerButtonsSpeakerMode {
enum CallControllerButtonsMode: Equatable {
enum VideoState: Equatable {
case notAvailable
case possible(Bool)
case outgoingRequested
case incomingRequested
case possible(isEnabled: Bool, isInitializing: Bool)
case outgoingRequested(isInitializing: Bool)
case incomingRequested(sendsVideo: Bool)
case active
}
@ -52,7 +52,7 @@ private enum ButtonDescription: Equatable {
case accept
case end(EndType)
case enableCamera(Bool, Bool)
case enableCamera(Bool, Bool, Bool)
case switchCamera
case soundOutput(SoundOutput)
case mute(Bool)
@ -110,6 +110,10 @@ final class CallControllerButtonsNode: ASDisplayNode {
private var appliedMode: CallControllerButtonsMode?
func videoButtonFrame() -> CGRect? {
return self.buttonNodes[.enableCamera]?.frame
}
private func updateButtonsLayout(strings: PresentationStrings, mode: CallControllerButtonsMode, width: CGFloat, bottomInset: CGFloat, animated: Bool) -> CGFloat {
let transition: ContainedViewLayoutTransition
if animated {
@ -171,12 +175,12 @@ final class CallControllerButtonsNode: ASDisplayNode {
mappedState = .outgoingRinging
case let .active(_, videoStateValue):
switch videoStateValue {
case .incomingRequested:
mappedState = .incomingRinging
videoState = .outgoingRequested
case .outgoingRequested:
mappedState = .outgoingRinging
videoState = .outgoingRequested
case let .incomingRequested(sendsVideo):
mappedState = .active
videoState = .incomingRequested(sendsVideo: sendsVideo)
case let .outgoingRequested(isInitializing):
mappedState = .active
videoState = .outgoingRequested(isInitializing: isInitializing)
case .active, .possible, .notAvailable:
mappedState = .active
}
@ -204,14 +208,17 @@ final class CallControllerButtonsNode: ASDisplayNode {
case .active, .possible, .incomingRequested, .outgoingRequested:
let isCameraActive: Bool
let isCameraEnabled: Bool
if case let .possible(value) = videoState {
let isCameraInitializing: Bool
if case let .possible(value, isInitializing) = videoState {
isCameraActive = false
isCameraEnabled = value
isCameraInitializing = isInitializing
} else {
isCameraActive = !self.isCameraPaused
isCameraEnabled = true
isCameraInitializing = false
}
topButtons.append(.enableCamera(isCameraActive, isCameraEnabled))
topButtons.append(.enableCamera(isCameraActive, isCameraEnabled, isCameraInitializing))
topButtons.append(.mute(self.isMuted))
if case .possible = videoState {
topButtons.append(.soundOutput(soundOutput))
@ -256,12 +263,19 @@ final class CallControllerButtonsNode: ASDisplayNode {
case .active, .incomingRequested, .outgoingRequested:
let isCameraActive: Bool
let isCameraEnabled: Bool
if case let .possible(value) = videoState {
var isCameraInitializing: Bool
if case .incomingRequested = videoState {
isCameraActive = false
isCameraEnabled = true
isCameraInitializing = false
} else if case let .possible(value, isInitializing) = videoState {
isCameraActive = false
isCameraEnabled = value
isCameraInitializing = isInitializing
} else {
isCameraActive = !self.isCameraPaused
isCameraEnabled = true
isCameraInitializing = false
}
var topButtons: [ButtonDescription] = []
@ -278,7 +292,11 @@ final class CallControllerButtonsNode: ASDisplayNode {
soundOutput = .bluetooth
}
topButtons.append(.enableCamera(isCameraActive, isCameraEnabled))
if case let .outgoingRequested(isInitializing) = videoState {
isCameraInitializing = isInitializing
}
topButtons.append(.enableCamera(isCameraActive, isCameraEnabled, isCameraInitializing))
topButtons.append(.mute(isMuted))
topButtons.append(.switchCamera)
topButtons.append(.end(.end))
@ -298,6 +316,19 @@ final class CallControllerButtonsNode: ASDisplayNode {
var topButtons: [ButtonDescription] = []
var bottomButtons: [ButtonDescription] = []
let isCameraActive: Bool
let isCameraEnabled: Bool
var isCameraInitializing: Bool
if case let .possible(value, isInitializing) = videoState {
isCameraActive = false
isCameraEnabled = value
isCameraInitializing = isInitializing
} else {
isCameraActive = false
isCameraEnabled = true
isCameraInitializing = false
}
let soundOutput: ButtonDescription.SoundOutput
switch speakerMode {
case .none, .builtin:
@ -310,7 +341,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
soundOutput = .bluetooth
}
topButtons.append(.enableCamera(false, true))
topButtons.append(.enableCamera(isCameraActive, isCameraEnabled, isCameraInitializing))
topButtons.append(.mute(self.isMuted))
topButtons.append(.soundOutput(soundOutput))
@ -379,11 +410,12 @@ final class CallControllerButtonsNode: ASDisplayNode {
case .end:
buttonText = strings.Call_End
}
case let .enableCamera(isActivated, isEnabled):
case let .enableCamera(isActivated, isEnabled, isInitializing):
buttonContent = CallControllerButtonItemNode.Content(
appearance: .blurred(isFilled: isActivated),
image: .camera,
isEnabled: isEnabled
isEnabled: isEnabled,
hasProgress: isInitializing
)
buttonText = strings.Call_Camera
case .switchCamera:

View file

@ -34,11 +34,11 @@ private final class CallVideoNode: ASDisplayNode {
private(set) var isReady: Bool = false
private var isReadyTimer: SwiftSignalKit.Timer?
private let isFlippedUpdated: () -> Void
private let isFlippedUpdated: (CallVideoNode) -> Void
private(set) var currentOrientation: PresentationCallVideoView.Orientation
init(videoView: PresentationCallVideoView, isReadyUpdated: @escaping () -> Void, orientationUpdated: @escaping () -> Void, isFlippedUpdated: @escaping () -> Void) {
init(videoView: PresentationCallVideoView, assumeReadyAfterTimeout: Bool, isReadyUpdated: @escaping () -> Void, orientationUpdated: @escaping () -> Void, isFlippedUpdated: @escaping (CallVideoNode) -> Void) {
self.isReadyUpdated = isReadyUpdated
self.isFlippedUpdated = isFlippedUpdated
@ -46,46 +46,60 @@ private final class CallVideoNode: ASDisplayNode {
self.videoTransformContainer.clipsToBounds = true
self.videoView = videoView
videoView.view.clipsToBounds = true
videoView.view.backgroundColor = .black
self.currentOrientation = videoView.getOrientation()
super.init()
self.backgroundColor = .black
self.videoTransformContainer.view.addSubview(self.videoView.view)
self.addSubnode(self.videoTransformContainer)
self.videoView.setOnFirstFrameReceived { [weak self] in
guard let strongSelf = self else {
return
}
if !strongSelf.isReady {
strongSelf.isReady = true
strongSelf.isReadyTimer?.invalidate()
strongSelf.isReadyUpdated()
Queue.mainQueue().async {
guard let strongSelf = self else {
return
}
if !strongSelf.isReady {
strongSelf.isReady = true
strongSelf.isReadyTimer?.invalidate()
strongSelf.isReadyUpdated()
}
}
}
self.videoView.setOnOrientationUpdated { [weak self] orientation in
guard let strongSelf = self else {
return
}
if strongSelf.currentOrientation != orientation {
strongSelf.currentOrientation = orientation
orientationUpdated()
Queue.mainQueue().async {
guard let strongSelf = self else {
return
}
if strongSelf.currentOrientation != orientation {
strongSelf.currentOrientation = orientation
orientationUpdated()
}
}
}
self.isReadyTimer = SwiftSignalKit.Timer(timeout: 3.0, repeat: false, completion: { [weak self] in
guard let strongSelf = self else {
return
self.videoView.setOnIsMirroredUpdated { [weak self] _ in
Queue.mainQueue().async {
guard let strongSelf = self else {
return
}
strongSelf.isFlippedUpdated(strongSelf)
}
if !strongSelf.isReady {
strongSelf.isReady = true
strongSelf.isReadyUpdated()
}
}, queue: .mainQueue())
}
if assumeReadyAfterTimeout {
self.isReadyTimer = SwiftSignalKit.Timer(timeout: 3.0, repeat: false, completion: { [weak self] in
guard let strongSelf = self else {
return
}
if !strongSelf.isReady {
strongSelf.isReady = true
strongSelf.isReadyUpdated()
}
}, queue: .mainQueue())
}
self.isReadyTimer?.start()
}
@ -93,8 +107,43 @@ private final class CallVideoNode: ASDisplayNode {
self.isReadyTimer?.invalidate()
}
func updateLayout(size: CGSize, cornerRadius: CGFloat, transition: ContainedViewLayoutTransition) {
func animateRadialMask(from fromRect: CGRect, to toRect: CGRect) {
let maskLayer = CAShapeLayer()
maskLayer.frame = fromRect
let path = CGMutablePath()
path.addEllipse(in: CGRect(origin: CGPoint(), size: fromRect.size))
maskLayer.path = path
self.layer.mask = maskLayer
let topLeft = CGPoint(x: 0.0, y: 0.0)
let topRight = CGPoint(x: self.bounds.width, y: 0.0)
let bottomLeft = CGPoint(x: 0.0, y: self.bounds.height)
let bottomRight = CGPoint(x: self.bounds.width, y: self.bounds.height)
func distance(_ v1: CGPoint, _ v2: CGPoint) -> CGFloat {
let dx = v1.x - v2.x
let dy = v1.y - v2.y
return sqrt(dx * dx + dy * dy)
}
var maxRadius = distance(toRect.center, topLeft)
maxRadius = max(maxRadius, distance(toRect.center, topRight))
maxRadius = max(maxRadius, distance(toRect.center, bottomLeft))
maxRadius = max(maxRadius, distance(toRect.center, bottomRight))
maxRadius = ceil(maxRadius)
let targetFrame = CGRect(origin: CGPoint(x: toRect.center.x - maxRadius, y: toRect.center.y - maxRadius), size: CGSize(width: maxRadius * 2.0, height: maxRadius * 2.0))
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)
transition.updatePosition(layer: maskLayer, position: targetFrame.center)
transition.updateTransformScale(layer: maskLayer, scale: maxRadius * 2.0 / fromRect.width, completion: { [weak self] _ in
self?.layer.mask = nil
})
}
func updateLayout(size: CGSize, cornerRadius: CGFloat, transition: ContainedViewLayoutTransition) {
self.currentCornerRadius = cornerRadius
var rotationAngle: CGFloat
@ -227,9 +276,13 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
private var incomingVideoNodeValue: CallVideoNode?
private var incomingVideoViewRequested: Bool = false
private var candidateOutgoingVideoNodeValue: CallVideoNode?
private var outgoingVideoNodeValue: CallVideoNode?
private var outgoingVideoViewRequested: Bool = false
private var isRequestingVideo: Bool = false
private var animateRequestedVideoOnce: Bool = false
private var expandedVideoNode: CallVideoNode?
private var minimizedVideoNode: CallVideoNode?
private var disableAnimationForExpandedVideoOnce: Bool = false
@ -396,7 +449,19 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
switch callState.state {
case .active:
if strongSelf.outgoingVideoNodeValue == nil {
strongSelf.call.requestVideo()
switch callState.videoState {
case .possible:
strongSelf.isRequestingVideo = true
strongSelf.updateButtonsMode()
default:
break
}
switch callState.videoState {
case .incomingRequested:
strongSelf.call.acceptVideo()
default:
strongSelf.call.requestVideo()
}
} else {
strongSelf.isVideoPaused = !strongSelf.isVideoPaused
strongSelf.outgoingVideoNodeValue?.updateIsBlurred(isBlurred: strongSelf.isVideoPaused)
@ -417,7 +482,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
return
}
strongSelf.call.switchVideoCamera()
if let outgoingVideoNode = strongSelf.outgoingVideoNodeValue {
if let _ = strongSelf.outgoingVideoNodeValue {
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
@ -487,7 +552,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
}
private func setupAudioOutputs() {
if self.outgoingVideoNodeValue != nil {
if self.outgoingVideoNodeValue != nil || self.candidateOutgoingVideoNodeValue != nil {
if let audioOutputState = self.audioOutputState, let currentOutput = audioOutputState.currentOutput {
switch currentOutput {
case .headphones:
@ -508,7 +573,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
var statusReception: Int32?
switch callState.videoState {
case .active:
case .active, .incomingRequested(true):
if !self.incomingVideoViewRequested {
self.incomingVideoViewRequested = true
self.call.makeIncomingVideoView(completion: { [weak self] incomingVideoView in
@ -516,7 +581,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
return
}
if let incomingVideoView = incomingVideoView {
let incomingVideoNode = CallVideoNode(videoView: incomingVideoView, isReadyUpdated: {
let incomingVideoNode = CallVideoNode(videoView: incomingVideoView, assumeReadyAfterTimeout: false, isReadyUpdated: {
guard let strongSelf = self else {
return
}
@ -530,13 +595,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
}, isFlippedUpdated: {
guard let strongSelf = self else {
return
}
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
}, isFlippedUpdated: { _ in
})
strongSelf.incomingVideoNodeValue = incomingVideoNode
strongSelf.expandedVideoNode = incomingVideoNode
@ -552,40 +611,80 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
}
switch callState.videoState {
case .active, .outgoingRequested, .incomingRequested:
case .active, .outgoingRequested, .incomingRequested(false):
if !self.outgoingVideoViewRequested {
self.outgoingVideoViewRequested = true
let delayUntilInitialized = self.isRequestingVideo
self.call.makeOutgoingVideoView(completion: { [weak self] outgoingVideoView in
guard let strongSelf = self else {
return
}
if let outgoingVideoView = outgoingVideoView {
outgoingVideoView.view.backgroundColor = .black
outgoingVideoView.view.clipsToBounds = true
let outgoingVideoNode = CallVideoNode(videoView: outgoingVideoView, isReadyUpdated: {}, orientationUpdated: {
let applyNode: () -> Void = {
guard let strongSelf = self, let outgoingVideoNode = strongSelf.candidateOutgoingVideoNodeValue else {
return
}
strongSelf.candidateOutgoingVideoNodeValue = nil
if strongSelf.isRequestingVideo {
strongSelf.isRequestingVideo = false
strongSelf.animateRequestedVideoOnce = true
}
strongSelf.outgoingVideoNodeValue = outgoingVideoNode
strongSelf.minimizedVideoNode = outgoingVideoNode
if let expandedVideoNode = strongSelf.expandedVideoNode {
strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: expandedVideoNode)
} else {
strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: strongSelf.dimNode)
}
strongSelf.updateButtonsMode(transition: .animated(duration: 0.4, curve: .spring))
}
let outgoingVideoNode = CallVideoNode(videoView: outgoingVideoView, assumeReadyAfterTimeout: true, isReadyUpdated: {
if delayUntilInitialized {
Queue.mainQueue().after(0.4, {
applyNode()
})
}
}, orientationUpdated: {
guard let strongSelf = self else {
return
}
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
}, isFlippedUpdated: {
guard let strongSelf = self else {
}, isFlippedUpdated: { videoNode in
guard let _ = self else {
return
}
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
/*if videoNode === strongSelf.minimizedVideoNode, let tempView = videoNode.view.snapshotView(afterScreenUpdates: true) {
videoNode.view.superview?.insertSubview(tempView, aboveSubview: videoNode.view)
videoNode.view.frame = videoNode.frame
let transitionOptions: UIView.AnimationOptions = [.transitionFlipFromRight, .showHideTransitionViews]
UIView.transition(with: tempView, duration: 1.0, options: transitionOptions, animations: {
tempView.isHidden = true
}, completion: { [weak tempView] _ in
tempView?.removeFromSuperview()
})
videoNode.view.isHidden = true
UIView.transition(with: videoNode.view, duration: 1.0, options: transitionOptions, animations: {
videoNode.view.isHidden = false
})
}*/
})
strongSelf.outgoingVideoNodeValue = outgoingVideoNode
strongSelf.minimizedVideoNode = outgoingVideoNode
if let expandedVideoNode = strongSelf.expandedVideoNode {
strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: expandedVideoNode)
} else {
strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: strongSelf.dimNode)
}
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.4, curve: .spring))
strongSelf.candidateOutgoingVideoNodeValue = outgoingVideoNode
strongSelf.setupAudioOutputs()
if !delayUntilInitialized {
applyNode()
}
strongSelf.setupAudioOutputs()
}
@ -679,7 +778,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
}
}
switch callState.videoState {
case .notAvailable, .active, .possible:
case .notAvailable, .active, .possible, .outgoingRequested:
statusValue = .timer({ value in
if isReconnecting {
return strings.Call_StatusConnecting
@ -695,8 +794,8 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
text += "\n\(self.statusNode.subtitle)"
}
statusValue = .text(string: text, displayLogo: true)
case .outgoingRequested:
statusValue = .text(string: self.presentationData.strings.Call_StatusRequesting, displayLogo: false)
/*case .outgoingRequested:
statusValue = .text(string: self.presentationData.strings.Call_StatusRequesting, displayLogo: false)*/
}
}
if self.shouldStayHiddenUntilConnection {
@ -732,7 +831,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
private var buttonsTerminationMode: CallControllerButtonsMode?
private func updateButtonsMode() {
private func updateButtonsMode(transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .spring)) {
guard let callState = self.callState else {
return
}
@ -765,11 +864,15 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
default:
break
}
mappedVideoState = .possible(isEnabled)
mappedVideoState = .possible(isEnabled: isEnabled, isInitializing: false)
case .outgoingRequested:
mappedVideoState = .outgoingRequested
case .incomingRequested:
mappedVideoState = .incomingRequested
if self.outgoingVideoNodeValue != nil {
mappedVideoState = .outgoingRequested(isInitializing: self.isRequestingVideo)
} else {
mappedVideoState = .possible(isEnabled: true, isInitializing: self.isRequestingVideo)
}
case let .incomingRequested(sendsVideo):
mappedVideoState = .incomingRequested(sendsVideo: sendsVideo)
case .active:
mappedVideoState = .active
}
@ -793,7 +896,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
}
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: transition)
}
}
@ -919,6 +1022,10 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
var uiDisplayTransition: CGFloat = self.isUIHidden ? 0.0 : 1.0
uiDisplayTransition *= 1.0 - self.pictureInPictureTransitionFraction
let previousVideoButtonFrame = self.buttonsNode.videoButtonFrame().flatMap { frame -> CGRect in
return self.buttonsNode.view.convert(frame, to: self.view)
}
let buttonsHeight: CGFloat
if let buttonsMode = self.buttonsMode {
buttonsHeight = self.buttonsNode.updateLayout(strings: self.presentationData.strings, mode: buttonsMode, constrainedWidth: layout.size.width, bottomInset: layout.intrinsicInsets.bottom, transition: transition)
@ -995,7 +1102,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
let videoPausedSize = self.videoPausedNode.updateLayout(CGSize(width: layout.size.width - 16.0, height: 100.0))
transition.updateFrame(node: self.videoPausedNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - videoPausedSize.width) / 2.0), y: floor((layout.size.height - videoPausedSize.height) / 2.0)), size: videoPausedSize))
transition.updateFrame(node: self.buttonsNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonsOriginY), size: CGSize(width: layout.size.width, height: buttonsHeight)))
transition.updateAlpha(node: self.buttonsNode, alpha: overlayAlpha)
@ -1014,8 +1121,10 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
}
if let minimizedVideoNode = self.minimizedVideoNode {
var minimizedVideoTransition = transition
var didAppear = false
if minimizedVideoNode.frame.isEmpty {
minimizedVideoTransition = .immediate
didAppear = true
}
if let expandedVideoNode = self.expandedVideoNode, expandedVideoNode.isReady {
if self.minimizedVideoDraggingPosition == nil {
@ -1031,10 +1140,23 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
}
minimizedVideoTransition.updateFrame(node: minimizedVideoNode, frame: previewVideoFrame)
minimizedVideoNode.updateLayout(size: minimizedVideoNode.frame.size, cornerRadius: interpolate(from: 14.0, to: 24.0, value: self.pictureInPictureTransitionFraction), transition: minimizedVideoTransition)
if transition.isAnimated && didAppear {
minimizedVideoNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
}
}
} else {
minimizedVideoNode.frame = fullscreenVideoFrame
minimizedVideoNode.updateLayout(size: layout.size, cornerRadius: 0.0, transition: minimizedVideoTransition)
if self.animateRequestedVideoOnce {
self.animateRequestedVideoOnce = false
let videoButtonFrame = self.buttonsNode.videoButtonFrame().flatMap { frame -> CGRect in
return self.buttonsNode.view.convert(frame, to: self.view)
}
if let previousVideoButtonFrame = previousVideoButtonFrame, let videoButtonFrame = videoButtonFrame {
minimizedVideoNode.animateRadialMask(from: previousVideoButtonFrame, to: videoButtonFrame)
}
}
}
self.animationForExpandedVideoSnapshotView = nil
}

View file

@ -29,7 +29,7 @@ public final class CallKitIntegration {
return self.audioSessionActivePromise.get()
}
init?(enableVideoCalls: Bool, startCall: @escaping (Account, UUID, String, Bool) -> Signal<Bool, NoError>, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal<Bool, NoError>, setCallMuted: @escaping (UUID, Bool) -> Void, audioSessionActivationChanged: @escaping (Bool) -> Void) {
init?(startCall: @escaping (Account, UUID, String, Bool) -> Signal<Bool, NoError>, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal<Bool, NoError>, setCallMuted: @escaping (UUID, Bool) -> Void, audioSessionActivationChanged: @escaping (Bool) -> Void) {
if !CallKitIntegration.isAvailable {
return nil
}
@ -40,7 +40,7 @@ public final class CallKitIntegration {
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
if sharedProviderDelegate == nil {
sharedProviderDelegate = CallKitProviderDelegate(enableVideoCalls: enableVideoCalls)
sharedProviderDelegate = CallKitProviderDelegate()
}
(sharedProviderDelegate as? CallKitProviderDelegate)?.setup(audioSessionActivePromise: self.audioSessionActivePromise, startCall: startCall, answerCall: answerCall, endCall: endCall, setCallMuted: setCallMuted, audioSessionActivationChanged: audioSessionActivationChanged)
} else {
@ -112,8 +112,8 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
fileprivate var audioSessionActivePromise: ValuePromise<Bool>?
init(enableVideoCalls: Bool) {
self.provider = CXProvider(configuration: CallKitProviderDelegate.providerConfiguration(enableVideoCalls: enableVideoCalls))
override init() {
self.provider = CXProvider(configuration: CallKitProviderDelegate.providerConfiguration())
super.init()
@ -129,7 +129,7 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
self.audioSessionActivationChanged = audioSessionActivationChanged
}
private static func providerConfiguration(enableVideoCalls: Bool) -> CXProviderConfiguration {
private static func providerConfiguration() -> CXProviderConfiguration {
let providerConfiguration = CXProviderConfiguration(localizedName: "Telegram")
providerConfiguration.supportsVideo = false

View file

@ -169,6 +169,7 @@ public final class PresentationCallImpl: PresentationCall {
public let isOutgoing: Bool
public var isVideo: Bool
public var isVideoPossible: Bool
public let enableHighBitrateVideoCalls: Bool
public let peer: Peer?
private let serializedData: String?
@ -257,7 +258,8 @@ public final class PresentationCallImpl: PresentationCall {
currentNetworkType: NetworkType,
updatedNetworkType: Signal<NetworkType, NoError>,
startWithVideo: Bool,
isVideoPossible: Bool
isVideoPossible: Bool,
enableHighBitrateVideoCalls: Bool
) {
self.account = account
self.audioSession = audioSession
@ -286,6 +288,7 @@ public final class PresentationCallImpl: PresentationCall {
self.isVideoPossible = isVideoPossible
self.peer = peer
self.isVideo = startWithVideo
self.enableHighBitrateVideoCalls = enableHighBitrateVideoCalls
if self.isVideo {
self.videoCapturer = OngoingCallVideoCapturer()
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .outgoingRequested, remoteVideoState: .active))
@ -463,8 +466,8 @@ public final class PresentationCallImpl: PresentationCall {
mappedVideoState = .possible
case .outgoingRequested:
mappedVideoState = .outgoingRequested
case .incomingRequested:
mappedVideoState = .incomingRequested
case let .incomingRequested(sendsVideo):
mappedVideoState = .incomingRequested(sendsVideo: sendsVideo)
case .active:
mappedVideoState = .active
self.videoWasActive = true
@ -569,7 +572,7 @@ public final class PresentationCallImpl: PresentationCall {
if let _ = audioSessionControl, !wasActive || previousControl == nil {
let logName = "\(id.id)_\(id.accessHash)"
let ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, auxiliaryServers: auxiliaryServers, initialNetworkType: self.currentNetworkType, updatedNetworkType: self.updatedNetworkType, serializedData: self.serializedData, dataSaving: dataSaving, derivedState: self.derivedState, key: key, isOutgoing: sessionState.isOutgoing, video: self.videoCapturer, connections: connections, maxLayer: maxLayer, version: version, allowP2P: allowsP2P, audioSessionActive: self.audioSessionActive.get(), logName: logName)
let ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, initialNetworkType: self.currentNetworkType, updatedNetworkType: self.updatedNetworkType, serializedData: self.serializedData, dataSaving: dataSaving, derivedState: self.derivedState, key: key, isOutgoing: sessionState.isOutgoing, video: self.videoCapturer, connections: connections, maxLayer: maxLayer, version: version, allowP2P: allowsP2P, enableHighBitrateVideoCalls: self.enableHighBitrateVideoCalls, audioSessionActive: self.audioSessionActive.get(), logName: logName)
self.ongoingContext = ongoingContext
ongoingContext.setIsMuted(self.isMutedValue)
@ -811,6 +814,7 @@ public final class PresentationCallImpl: PresentationCall {
if let view = view {
let setOnFirstFrameReceived = view.setOnFirstFrameReceived
let setOnOrientationUpdated = view.setOnOrientationUpdated
let setOnIsMirroredUpdated = view.setOnIsMirroredUpdated
completion(PresentationCallVideoView(
view: view.view,
setOnFirstFrameReceived: { f in
@ -849,6 +853,11 @@ public final class PresentationCallImpl: PresentationCall {
}
f?(mappedValue)
}
},
setOnIsMirroredUpdated: { f in
setOnIsMirroredUpdated { value in
f?(value)
}
}
))
} else {
@ -867,6 +876,7 @@ public final class PresentationCallImpl: PresentationCall {
if let view = view {
let setOnFirstFrameReceived = view.setOnFirstFrameReceived
let setOnOrientationUpdated = view.setOnOrientationUpdated
let setOnIsMirroredUpdated = view.setOnIsMirroredUpdated
completion(PresentationCallVideoView(
view: view.view,
setOnFirstFrameReceived: { f in
@ -905,8 +915,12 @@ public final class PresentationCallImpl: PresentationCall {
}
f?(mappedValue)
}
},
setOnIsMirroredUpdated: { f in
setOnIsMirroredUpdated { value in
f?(value)
}
}
))
} else {
completion(nil)

View file

@ -82,7 +82,6 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
private let accountManager: AccountManager
private let audioSession: ManagedAudioSession
private let callKitIntegration: CallKitIntegration?
private var isVideoPossible: Bool
private var currentCallValue: PresentationCallImpl?
private var currentCall: PresentationCallImpl? {
@ -117,15 +116,14 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
return OngoingCallContext.maxLayer
}
public static func voipVersions(includeExperimental: Bool, includeReference: Bool) -> [String] {
public static func voipVersions(includeExperimental: Bool, includeReference: Bool) -> [(version: String, supportsVideo: Bool)] {
return OngoingCallContext.versions(includeExperimental: includeExperimental, includeReference: includeReference)
}
public init(accountManager: AccountManager, enableVideoCalls: Bool, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), isMediaPlaying: @escaping () -> Bool, resumeMediaPlayback: @escaping () -> Void, audioSession: ManagedAudioSession, activeAccounts: Signal<[Account], NoError>) {
public init(accountManager: AccountManager, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), isMediaPlaying: @escaping () -> Bool, resumeMediaPlayback: @escaping () -> Void, audioSession: ManagedAudioSession, activeAccounts: Signal<[Account], NoError>) {
self.getDeviceAccessData = getDeviceAccessData
self.accountManager = accountManager
self.audioSession = audioSession
self.isVideoPossible = enableVideoCalls
self.isMediaPlaying = isMediaPlaying
self.resumeMediaPlayback = resumeMediaPlayback
@ -136,7 +134,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
var setCallMutedImpl: ((UUID, Bool) -> Void)?
var audioSessionActivationChangedImpl: ((Bool) -> Void)?
self.callKitIntegration = CallKitIntegration(enableVideoCalls: enableVideoCalls, startCall: { account, uuid, handle, isVideo in
self.callKitIntegration = CallKitIntegration(startCall: { account, uuid, handle, isVideo in
if let startCallImpl = startCallImpl {
return startCallImpl(account, uuid, handle, isVideo)
} else {
@ -214,7 +212,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
startCallImpl = { [weak self] account, uuid, handle, isVideo in
if let strongSelf = self, let userId = Int32(handle) {
return strongSelf.startCall(account: account, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), isVideo: isVideo, isVideoPossible: strongSelf.isVideoPossible, internalId: uuid)
return strongSelf.startCall(account: account, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), isVideo: isVideo, internalId: uuid)
|> take(1)
|> map { result -> Bool in
return result
@ -283,7 +281,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
private func ringingStatesUpdated(_ ringingStates: [(Account, Peer, CallSessionRingingState, Bool, NetworkType)], enableCallKit: Bool) {
if let firstState = ringingStates.first {
if self.currentCall == nil {
self.currentCallDisposable.set((combineLatest(firstState.0.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState, PreferencesKeys.appConfiguration]) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings]) |> take(1))
self.currentCallDisposable.set((combineLatest(firstState.0.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState, PreferencesKeys.appConfiguration]) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1))
|> deliverOnMainQueue).start(next: { [weak self] preferences, sharedData in
guard let strongSelf = self else {
return
@ -292,6 +290,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
let configuration = preferences.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue
let derivedState = preferences.values[ApplicationSpecificPreferencesKeys.voipDerivedState] as? VoipDerivedState ?? .default
let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings
let experimentalSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings] as? ExperimentalUISettings ?? .defaultSettings
let appConfiguration = preferences.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? AppConfiguration.defaultValue
let call = PresentationCallImpl(
@ -313,7 +312,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
currentNetworkType: firstState.4,
updatedNetworkType: firstState.0.networkType,
startWithVideo: firstState.2.isVideo,
isVideoPossible: strongSelf.isVideoPossible
isVideoPossible: firstState.2.isVideoPossible,
enableHighBitrateVideoCalls: experimentalSettings.enableHighBitrateVideoCalls
)
strongSelf.updateCurrentCall(call)
strongSelf.currentCallPromise.set(.single(call))
@ -339,8 +339,9 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
}
}
public func requestCall(account: Account, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult {
let isVideoPossible = self.isVideoPossible
public func requestCall(context: AccountContext, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult {
let account = context.account
if let call = self.currentCall, !endCurrentIfAny {
return .alreadyInProgress(call.peerId)
}
@ -405,7 +406,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
guard let strongSelf = self else {
return
}
let _ = strongSelf.startCall(account: account, peerId: peerId, isVideo: isVideo, isVideoPossible: isVideoPossible).start()
let _ = strongSelf.startCall(account: account, peerId: peerId, isVideo: isVideo).start()
}
if let currentCall = self.currentCall {
self.startCallDisposable.set((currentCall.hangUp()
@ -423,7 +424,6 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
account: Account,
peerId: PeerId,
isVideo: Bool,
isVideoPossible: Bool,
internalId: CallSessionInternalId = CallSessionInternalId()
) -> Signal<Bool, NoError> {
let (presentationData, present, openSettings) = self.getDeviceAccessData()
@ -459,9 +459,28 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
if !accessEnabled {
return .single(false)
}
return (combineLatest(queue: .mainQueue(), account.callSessionManager.request(peerId: peerId, isVideo: isVideo, internalId: internalId), networkType |> take(1), account.postbox.peerView(id: peerId) |> map { peerView -> Bool in
let request = account.postbox.transaction { transaction -> VideoCallsConfiguration in
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration) as? AppConfiguration ?? AppConfiguration.defaultValue
return VideoCallsConfiguration(appConfiguration: appConfiguration)
}
|> mapToSignal { callsConfiguration -> Signal<CallSessionInternalId, NoError> in
let isVideoPossible: Bool
switch callsConfiguration.videoCallsSupport {
case .disabled:
isVideoPossible = isVideo
case .full:
isVideoPossible = true
case .onlyVideo:
isVideoPossible = isVideo
}
return account.callSessionManager.request(peerId: peerId, isVideo: isVideo, enableVideo: isVideoPossible, internalId: internalId)
}
return (combineLatest(queue: .mainQueue(), request, networkType |> take(1), account.postbox.peerView(id: peerId) |> map { peerView -> Bool in
return peerView.peerIsContact
} |> take(1), account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState, PreferencesKeys.appConfiguration]) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings]) |> take(1))
} |> take(1), account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState, PreferencesKeys.appConfiguration]) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1))
|> deliverOnMainQueue
|> beforeNext { internalId, currentNetworkType, isContact, preferences, sharedData in
if let strongSelf = self, accessEnabled {
@ -474,6 +493,19 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings
let appConfiguration = preferences.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? AppConfiguration.defaultValue
let callsConfiguration = VideoCallsConfiguration(appConfiguration: appConfiguration)
let isVideoPossible: Bool
switch callsConfiguration.videoCallsSupport {
case .disabled:
isVideoPossible = isVideo
case .full:
isVideoPossible = true
case .onlyVideo:
isVideoPossible = isVideo
}
let experimentalSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings] as? ExperimentalUISettings ?? .defaultSettings
let call = PresentationCallImpl(
account: account,
audioSession: strongSelf.audioSession,
@ -496,7 +528,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
currentNetworkType: currentNetworkType,
updatedNetworkType: account.networkType,
startWithVideo: isVideo,
isVideoPossible: isVideoPossible
isVideoPossible: isVideoPossible,
enableHighBitrateVideoCalls: experimentalSettings.enableHighBitrateVideoCalls
)
strongSelf.updateCurrentCall(call)
strongSelf.currentCallPromise.set(.single(call))

View file

@ -94,6 +94,7 @@ enum AccountStateMutationOperation {
case UpdatePinnedItemIds(PeerGroupId, AccountStateUpdatePinnedItemIdsOperation)
case ReadMessageContents((PeerId?, [Int32]))
case UpdateMessageImpressionCount(MessageId, Int32)
case UpdateMessageForwardsCount(MessageId, Int32)
case UpdateInstalledStickerPacks(AccountStateUpdateStickerPacksOperation)
case UpdateRecentGifs
case UpdateChatInputState(PeerId, SynchronizeableChatInputState?)
@ -435,6 +436,10 @@ struct AccountMutableState {
self.addOperation(.UpdateMessageImpressionCount(id, count))
}
mutating func addUpdateMessageForwardsCount(id: MessageId, count: Int32) {
self.addOperation(.UpdateMessageForwardsCount(id, count))
}
mutating func addUpdateInstalledStickerPacks(_ operation: AccountStateUpdateStickerPacksOperation) {
self.addOperation(.UpdateInstalledStickerPacks(operation))
}
@ -469,7 +474,7 @@ struct AccountMutableState {
mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter:
break
case let .AddMessages(messages, location):
for message in messages {

View file

@ -35,6 +35,7 @@ private var declaredEncodables: Void = {
declareEncodable(CloudDocumentMediaResource.self, f: { CloudDocumentMediaResource(decoder: $0) })
declareEncodable(TelegramMediaWebpage.self, f: { TelegramMediaWebpage(decoder: $0) })
declareEncodable(ViewCountMessageAttribute.self, f: { ViewCountMessageAttribute(decoder: $0) })
declareEncodable(ForwardCountMessageAttribute.self, f: { ForwardCountMessageAttribute(decoder: $0) })
declareEncodable(NotificationInfoMessageAttribute.self, f: { NotificationInfoMessageAttribute(decoder: $0) })
declareEncodable(TelegramMediaAction.self, f: { TelegramMediaAction(decoder: $0) })
declareEncodable(TelegramPeerNotificationSettings.self, f: { TelegramPeerNotificationSettings(decoder: $0) })

View file

@ -1253,6 +1253,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
updatedState.addReadMessagesContents((PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), messages))
case let .updateChannelMessageViews(channelId, id, views):
updatedState.addUpdateMessageImpressionCount(id: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), namespace: Namespaces.Message.Cloud, id: id), count: views)
/*case let .updateChannelMessageForwards(channelId, id, forwards):
updatedState.addUpdateMessageForwardsCount(id: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), namespace: Namespaces.Message.Cloud, id: id), count: forwards)*/
case let .updateNewStickerSet(stickerset):
updatedState.addUpdateInstalledStickerPacks(.add(stickerset))
case let .updateStickerSetsOrder(flags, order):
@ -1896,6 +1898,8 @@ private func pollChannel(network: Network, peer: Peer, state: AccountMutableStat
updatedState.addReadMessagesContents((peer.id, messages))
case let .updateChannelMessageViews(_, id, views):
updatedState.addUpdateMessageImpressionCount(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), count: views)
/*case let .updateChannelMessageForwards(_, id, views):
updatedState.addUpdateMessageForwardsCount(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), count: views)*/
case let .updateChannelWebPage(_, apiWebpage, _, _):
switch apiWebpage {
case let .webPageEmpty(id):
@ -2090,7 +2094,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddScheduledMessages: OptimizeAddMessagesState?
for operation in operations {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
}
@ -2773,6 +2777,21 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
case let .UpdateMessageForwardsCount(id, count):
transaction.updateMessage(id, update: { currentMessage in
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType)
}
var attributes = currentMessage.attributes
loop: for j in 0 ..< attributes.count {
if let attribute = attributes[j] as? ForwardCountMessageAttribute {
attributes[j] = ForwardCountMessageAttribute(count: max(attribute.count, Int(count)))
break loop
}
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
case let .UpdateInstalledStickerPacks(operation):
stickerPackOperations.append(operation)
case .UpdateRecentGifs:

View file

@ -599,20 +599,34 @@ public final class AccountViewTracker {
return account.postbox.transaction { transaction -> Void in
for i in 0 ..< messageIds.count {
if i < viewCounts.count {
transaction.updateMessage(messageIds[i], update: { currentMessage in
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
var attributes = currentMessage.attributes
loop: for j in 0 ..< attributes.count {
if let attribute = attributes[j] as? ViewCountMessageAttribute {
if attribute.count >= Int(viewCounts[i]) {
return .skip
let views = viewCounts[i]
do {
transaction.updateMessage(messageIds[i], update: { currentMessage in
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
var attributes = currentMessage.attributes
loop: for j in 0 ..< attributes.count {
if let attribute = attributes[j] as? ViewCountMessageAttribute {
attributes[j] = ViewCountMessageAttribute(count: max(attribute.count, Int(views)))
}
attributes[j] = ViewCountMessageAttribute(count: max(attribute.count, Int(viewCounts[i])))
break loop
}
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
}
/*if case let .messageViews(views, forwards) = viewCounts[i] {
transaction.updateMessage(messageIds[i], update: { currentMessage in
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
var attributes = currentMessage.attributes
loop: for j in 0 ..< attributes.count {
if let attribute = attributes[j] as? ViewCountMessageAttribute {
attributes[j] = ViewCountMessageAttribute(count: max(attribute.count, Int(views)))
}
if let _ = attributes[j] as? ForwardCountMessageAttribute {
attributes[j] = ForwardCountMessageAttribute(count: Int(forwards))
}
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
}*/
}
}
}

View file

@ -18,8 +18,8 @@ func imageRepresentationsForApiChatPhoto(_ photo: Api.ChatPhoto) -> [TelegramMed
case let .fileLocationToBeDeprecated(volumeId, localId):
fullSizeResource = CloudPeerPhotoSizeMediaResource(datacenterId: dcId, sizeSpec: .fullSize, volumeId: volumeId, localId: localId)
}
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 80, height: 80), resource: smallResource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: fullSizeResource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 80, height: 80), resource: smallResource, progressiveSizes: []))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: fullSizeResource, progressiveSizes: []))
case .chatPhotoEmpty:
break
}

View file

@ -108,10 +108,7 @@ public struct CallSessionRingingState: Equatable {
public let id: CallSessionInternalId
public let peerId: PeerId
public let isVideo: Bool
public static func ==(lhs: CallSessionRingingState, rhs: CallSessionRingingState) -> Bool {
return lhs.id == rhs.id && lhs.peerId == rhs.peerId && lhs.isVideo == rhs.isVideo
}
public let isVideoPossible: Bool
}
public enum DropCallReason {
@ -191,18 +188,79 @@ public struct CallSession {
public let state: CallSessionState
}
public struct CallSessionConnection {
public let id: Int64
public let ip: String
public let ipv6: String
public let port: Int32
public let peerTag: Data
public enum CallSessionConnection: Equatable {
public struct Reflector: Equatable {
public let id: Int64
public let ip: String
public let ipv6: String
public let port: Int32
public let peerTag: Data
public init(
id: Int64,
ip: String,
ipv6: String,
port: Int32,
peerTag: Data
) {
self.id = id
self.ip = ip
self.ipv6 = ipv6
self.port = port
self.peerTag = peerTag
}
}
public struct WebRtcReflector: Equatable {
public let id: Int64
public let hasStun: Bool
public let hasTurn: Bool
public let ip: String
public let ipv6: String
public let port: Int32
public let username: String
public let password: String
public init(
id: Int64,
hasStun: Bool,
hasTurn: Bool,
ip: String,
ipv6: String,
port: Int32,
username: String,
password: String
) {
self.id = id
self.hasStun = hasStun
self.hasTurn = hasTurn
self.ip = ip
self.ipv6 = ipv6
self.port = port
self.username = username
self.password = password
}
}
case reflector(Reflector)
case webRtcReflector(WebRtcReflector)
}
private func parseConnection(_ apiConnection: Api.PhoneConnection) -> CallSessionConnection {
switch apiConnection {
case let .phoneConnection(id, ip, ipv6, port, peerTag):
return CallSessionConnection(id: id, ip: ip, ipv6: ipv6, port: port, peerTag: peerTag.makeData())
return .reflector(CallSessionConnection.Reflector(id: id, ip: ip, ipv6: ipv6, port: port, peerTag: peerTag.makeData()))
case let .phoneConnectionWebrtc(flags, id, ip, ipv6, port, username, password):
return .webRtcReflector(CallSessionConnection.WebRtcReflector(
id: id,
hasStun: (flags & (1 << 1)) != 0,
hasTurn: (flags & (1 << 0)) != 0,
ip: ip,
ipv6: ipv6,
port: port,
username: username,
password: password
))
}
}
@ -219,6 +277,7 @@ private final class CallSessionContext {
let peerId: PeerId
let isOutgoing: Bool
var type: CallSession.CallType
let isVideoPossible: Bool
var state: CallSessionInternalState
let subscribers = Bag<(CallSession) -> Void>()
let signalingSubscribers = Bag<(Data) -> Void>()
@ -235,10 +294,11 @@ private final class CallSessionContext {
}
}
init(peerId: PeerId, isOutgoing: Bool, type: CallSession.CallType, state: CallSessionInternalState) {
init(peerId: PeerId, isOutgoing: Bool, type: CallSession.CallType, isVideoPossible: Bool, state: CallSessionInternalState) {
self.peerId = peerId
self.isOutgoing = isOutgoing
self.type = type
self.isVideoPossible = isVideoPossible
self.state = state
}
@ -247,8 +307,8 @@ private final class CallSessionContext {
}
}
private func selectVersionOnAccept(localVersions: [String], remoteVersions: [String]) -> [String]? {
let filteredVersions = localVersions.filter(remoteVersions.contains)
private func selectVersionOnAccept(localVersions: [CallSessionManagerImplementationVersion], remoteVersions: [String]) -> [String]? {
let filteredVersions = localVersions.map(\.version).filter(remoteVersions.contains)
if filteredVersions.isEmpty {
return nil
} else {
@ -256,12 +316,22 @@ private func selectVersionOnAccept(localVersions: [String], remoteVersions: [Str
}
}
public struct CallSessionManagerImplementationVersion: Hashable {
public var version: String
public var supportsVideo: Bool
public init(version: String, supportsVideo: Bool) {
self.version = version
self.supportsVideo = supportsVideo
}
}
private final class CallSessionManagerContext {
private let queue: Queue
private let postbox: Postbox
private let network: Network
private let maxLayer: Int32
private var versions: [String]
private var versions: [CallSessionManagerImplementationVersion]
private let addUpdates: (Api.Updates) -> Void
private let ringingSubscribers = Bag<([CallSessionRingingState]) -> Void>()
@ -270,7 +340,7 @@ private final class CallSessionManagerContext {
private let disposables = DisposableSet()
init(queue: Queue, postbox: Postbox, network: Network, maxLayer: Int32, versions: [String], addUpdates: @escaping (Api.Updates) -> Void) {
init(queue: Queue, postbox: Postbox, network: Network, maxLayer: Int32, versions: [CallSessionManagerImplementationVersion], addUpdates: @escaping (Api.Updates) -> Void) {
self.queue = queue
self.postbox = postbox
self.network = network
@ -284,10 +354,32 @@ private final class CallSessionManagerContext {
self.disposables.dispose()
}
func updateVersions(versions: [String]) {
func updateVersions(versions: [CallSessionManagerImplementationVersion]) {
self.versions = versions.reversed()
}
func filteredVersions(enableVideo: Bool) -> [String] {
return self.versions.compactMap { version -> String? in
if enableVideo {
return version.version
} else if !version.supportsVideo {
return version.version
} else {
return nil
}
}
}
func videoVersions() -> [String] {
return self.versions.compactMap { version -> String? in
if version.supportsVideo {
return version.version
} else {
return nil
}
}
}
func ringingStates() -> Signal<[CallSessionRingingState], NoError> {
let queue = self.queue
return Signal { [weak self] subscriber in
@ -366,7 +458,7 @@ private final class CallSessionManagerContext {
var ringingContexts: [CallSessionRingingState] = []
for (id, context) in self.contexts {
if case .ringing = context.state {
ringingContexts.append(CallSessionRingingState(id: id, peerId: context.peerId, isVideo: context.type == .video))
ringingContexts.append(CallSessionRingingState(id: id, peerId: context.peerId, isVideo: context.type == .video, isVideoPossible: context.isVideoPossible))
}
}
return ringingContexts
@ -398,8 +490,13 @@ private final class CallSessionManagerContext {
let b = Data(bytesNoCopy: bBytes, count: 256, deallocator: .free)
if randomStatus == 0 {
var isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) })
//#if DEBUG
isVideoPossible = true
//#endif
let internalId = CallSessionInternalId()
let context = CallSessionContext(peerId: peerId, isOutgoing: false, type: isVideo ? .video : .audio, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions))
let context = CallSessionContext(peerId: peerId, isOutgoing: false, type: isVideo ? .video : .audio, isVideoPossible: isVideoPossible, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions))
self.contexts[internalId] = context
let queue = self.queue
context.acknowledgeIncomingCallDisposable.set(self.network.request(Api.functions.phone.receivedCall(peer: .inputPhoneCall(id: stableId, accessHash: accessHash))).start(error: { [weak self] _ in
@ -529,11 +626,7 @@ private final class CallSessionManagerContext {
if let context = self.contexts[internalId] {
switch context.state {
case let .ringing(id, accessHash, gAHash, b, remoteVersions):
guard var acceptVersions = selectVersionOnAccept(localVersions: self.versions, remoteVersions: remoteVersions) else {
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
return
}
acceptVersions = self.versions
let acceptVersions = self.versions.map(\.version)
context.state = .accepting(id: id, accessHash: accessHash, gAHash: gAHash, b: b, disposable: (acceptCallSession(postbox: self.postbox, network: self.network, stableId: id, accessHash: accessHash, b: b, maxLayer: self.maxLayer, versions: acceptVersions) |> deliverOn(self.queue)).start(next: { [weak self] result in
if let strongSelf = self, let context = strongSelf.contexts[internalId] {
if case .accepting = context.state {
@ -738,7 +831,7 @@ private final class CallSessionManagerContext {
}
}
case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, gAHash, requestedProtocol):
let isVideo = (flags & (1 << 5)) != 0
let isVideo = (flags & (1 << 6)) != 0
let versions: [String]
switch requestedProtocol {
case let .phoneCallProtocol(_, _, _, libraryVersions):
@ -819,12 +912,12 @@ private final class CallSessionManagerContext {
return (key, keyId, keyVisualHash)
}
func request(peerId: PeerId, internalId: CallSessionInternalId, isVideo: Bool) -> CallSessionInternalId? {
func request(peerId: PeerId, internalId: CallSessionInternalId, isVideo: Bool, enableVideo: Bool) -> CallSessionInternalId? {
let aBytes = malloc(256)!
let randomStatus = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
let a = Data(bytesNoCopy: aBytes, count: 256, deallocator: .free)
if randomStatus == 0 {
self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, type: isVideo ? .video : .audio, state: .requesting(a: a, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer, versions: self.versions, isVideo: isVideo) |> deliverOn(queue)).start(next: { [weak self] result in
self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, type: isVideo ? .video : .audio, isVideoPossible: enableVideo || isVideo, state: .requesting(a: a, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer, versions: self.filteredVersions(enableVideo: enableVideo), isVideo: isVideo) |> deliverOn(queue)).start(next: { [weak self] result in
if let strongSelf = self, let context = strongSelf.contexts[internalId] {
if case .requesting = context.state {
switch result {
@ -858,7 +951,7 @@ public final class CallSessionManager {
private let queue = Queue()
private var contextRef: Unmanaged<CallSessionManagerContext>?
init(postbox: Postbox, network: Network, maxLayer: Int32, versions: [String], addUpdates: @escaping (Api.Updates) -> Void) {
init(postbox: Postbox, network: Network, maxLayer: Int32, versions: [CallSessionManagerImplementationVersion], addUpdates: @escaping (Api.Updates) -> Void) {
self.queue.async {
let context = CallSessionManagerContext(queue: self.queue, postbox: postbox, network: network, maxLayer: maxLayer, versions: versions, addUpdates: addUpdates)
self.contextRef = Unmanaged.passRetained(context)
@ -917,12 +1010,12 @@ public final class CallSessionManager {
}
}
public func request(peerId: PeerId, isVideo: Bool, internalId: CallSessionInternalId = CallSessionInternalId()) -> Signal<CallSessionInternalId, NoError> {
public func request(peerId: PeerId, isVideo: Bool, enableVideo: Bool, internalId: CallSessionInternalId = CallSessionInternalId()) -> Signal<CallSessionInternalId, NoError> {
return Signal { [weak self] subscriber in
let disposable = MetaDisposable()
self?.withContext { context in
if let internalId = context.request(peerId: peerId, internalId: internalId, isVideo: isVideo) {
if let internalId = context.request(peerId: peerId, internalId: internalId, isVideo: isVideo, enableVideo: enableVideo) {
subscriber.putNext(internalId)
subscriber.putCompletion()
}
@ -944,7 +1037,7 @@ public final class CallSessionManager {
}
}
public func updateVersions(versions: [String]) {
public func updateVersions(versions: [CallSessionManagerImplementationVersion]) {
self.withContext { context in
context.updateVersions(versions: versions)
}

View file

@ -69,6 +69,15 @@ public func imageRepresentationLargerThan(_ representations: [TelegramMediaImage
}
}
public func progressiveImageRepresentation(_ representations: [TelegramMediaImageRepresentation]) -> TelegramMediaImageRepresentation? {
for representation in representations {
if representation.progressiveSizes.count > 1 {
return representation
}
}
return nil
}
public func parseMediaData(data: Data) -> Media? {
let buffer = BufferReader(Buffer(data: data))
var parseBuffer: Buffer?

View file

@ -0,0 +1,188 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
import MtProtoKit
import SyncCore
public struct MessageStats: Equatable {
public let views: Int
public let forwards: Int
public let interactionsGraph: StatsGraph
init(views: Int, forwards: Int, interactionsGraph: StatsGraph) {
self.views = views
self.forwards = forwards
self.interactionsGraph = interactionsGraph
}
public static func == (lhs: MessageStats, rhs: MessageStats) -> Bool {
if lhs.views != rhs.views {
return false
}
if lhs.forwards != rhs.forwards {
return false
}
if lhs.interactionsGraph != rhs.interactionsGraph {
return false
}
return true
}
public func withUpdatedInteractionsGraph(_ interactionsGraph: StatsGraph) -> MessageStats {
return MessageStats(views: self.views, forwards: self.forwards, interactionsGraph: self.interactionsGraph)
}
}
public struct MessageStatsContextState: Equatable {
public var stats: MessageStats?
}
private func requestMessageStats(postbox: Postbox, network: Network, datacenterId: Int32, messageId: MessageId, dark: Bool = false) -> Signal<MessageStats?, NoError> {
return .single(nil)
/*return postbox.transaction { transaction -> (Peer, Message)? in
if let peer = transaction.getPeer(messageId.peerId), let message = transaction.getMessage(messageId) {
return (peer, message)
} else {
return nil
}
} |> mapToSignal { peerAndMessage -> Signal<MessageStats?, NoError> in
guard let (peer, message) = peerAndMessage, let inputChannel = apiInputChannel(peer) else {
return .never()
}
var flags: Int32 = 0
if dark {
flags |= (1 << 1)
}
let request = Api.functions.stats.getMessageStats(flags: flags, channel: inputChannel, msgId: messageId.id)
let signal: Signal<Api.stats.MessageStats, MTRpcError>
if network.datacenterId != datacenterId {
signal = network.download(datacenterId: Int(datacenterId), isMedia: false, tag: nil)
|> castError(MTRpcError.self)
|> mapToSignal { worker in
return worker.request(request)
}
} else {
signal = network.request(request)
}
var views: Int = 0
var forwards: Int = 0
for attribute in message.attributes {
if let viewsAttribute = attribute as? ViewCountMessageAttribute {
views = viewsAttribute.count
} else if let forwardsAttribute = attribute as? ForwardCountMessageAttribute {
forwards = forwardsAttribute.count
}
}
return signal
|> map { result -> MessageStats? in
if case let .messageStats(apiViewsGraph) = result {
return MessageStats(views: views, forwards: forwards, interactionsGraph: StatsGraph(apiStatsGraph: apiViewsGraph))
} else {
return nil
}
}
|> retryRequest
}*/
}
private final class MessageStatsContextImpl {
private let postbox: Postbox
private let network: Network
private let datacenterId: Int32
private let messageId: MessageId
private var _state: MessageStatsContextState {
didSet {
if self._state != oldValue {
self._statePromise.set(.single(self._state))
}
}
}
private let _statePromise = Promise<MessageStatsContextState>()
var state: Signal<MessageStatsContextState, NoError> {
return self._statePromise.get()
}
private let disposable = MetaDisposable()
private let disposables = DisposableDict<String>()
init(postbox: Postbox, network: Network, datacenterId: Int32, messageId: MessageId) {
assert(Queue.mainQueue().isCurrent())
self.postbox = postbox
self.network = network
self.datacenterId = datacenterId
self.messageId = messageId
self._state = MessageStatsContextState(stats: nil)
self._statePromise.set(.single(self._state))
self.load()
}
deinit {
assert(Queue.mainQueue().isCurrent())
self.disposable.dispose()
self.disposables.dispose()
}
private func load() {
assert(Queue.mainQueue().isCurrent())
self.disposable.set((requestMessageStats(postbox: self.postbox, network: self.network, datacenterId: self.datacenterId, messageId: self.messageId)
|> deliverOnMainQueue).start(next: { [weak self] stats in
if let strongSelf = self {
strongSelf._state = MessageStatsContextState(stats: stats)
strongSelf._statePromise.set(.single(strongSelf._state))
}
}))
}
func loadDetailedGraph(_ graph: StatsGraph, x: Int64) -> Signal<StatsGraph?, NoError> {
if let token = graph.token {
return requestGraph(network: self.network, datacenterId: self.datacenterId, token: token, x: x)
} else {
return .single(nil)
}
}
}
public final class MessageStatsContext {
private let impl: QueueLocalObject<MessageStatsContextImpl>
public var state: Signal<MessageStatsContextState, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.state.start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
public init(postbox: Postbox, network: Network, datacenterId: Int32, messageId: MessageId) {
self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: {
return MessageStatsContextImpl(postbox: postbox, network: network, datacenterId: datacenterId, messageId: messageId)
})
}
public func loadDetailedGraph(_ graph: StatsGraph, x: Int64) -> Signal<StatsGraph?, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.loadDetailedGraph(graph, x: x).start(next: { value in
subscriber.putNext(value)
subscriber.putCompletion()
}))
}
return disposable
}
}
}

View file

@ -400,7 +400,7 @@ private enum FetchResourceReference {
private final class MultipartFetchManager {
let parallelParts: Int
let defaultPartSize = 128 * 1024
let defaultPartSize: Int
var partAlignment = 4 * 1024
var resource: TelegramMediaResource
@ -448,10 +448,17 @@ private final class MultipartFetchManager {
self.consumerId = arc4random64()
self.completeSize = size
if let _ = size {
self.parallelParts = 4
if let size = size {
if size <= 512 * 1024 {
self.defaultPartSize = 16 * 1024
self.parallelParts = 4 * 4
} else {
self.defaultPartSize = 128 * 1024
self.parallelParts = 4
}
} else {
self.parallelParts = 1
self.defaultPartSize = 128 * 1024
}
if let info = parameters?.info as? TelegramCloudMediaResourceFetchInfo {

View file

@ -403,12 +403,12 @@ public struct NetworkInitializationArguments {
public let languagesCategory: String
public let appVersion: String
public let voipMaxLayer: Int32
public let voipVersions: [String]
public let voipVersions: [CallSessionManagerImplementationVersion]
public let appData: Signal<Data?, NoError>
public let autolockDeadine: Signal<Int32?, NoError>
public let encryptionProvider: EncryptionProvider
public init(apiId: Int32, apiHash: String, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, voipVersions: [String], appData: Signal<Data?, NoError>, autolockDeadine: Signal<Int32?, NoError>, encryptionProvider: EncryptionProvider) {
public init(apiId: Int32, apiHash: String, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, voipVersions: [CallSessionManagerImplementationVersion], appData: Signal<Data?, NoError>, autolockDeadine: Signal<Int32?, NoError>, encryptionProvider: EncryptionProvider) {
self.apiId = apiId
self.apiHash = apiHash
self.languagesCategory = languagesCategory

View file

@ -55,7 +55,7 @@ public func outgoingMessageWithChatContextResult(to peerId: PeerId, results: Cha
arc4random_buf(&randomId, 8)
let thumbnailResource = thumbnail.resource
let imageDimensions = thumbnail.dimensions ?? PixelDimensions(width: 128, height: 128)
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: [TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: thumbnailResource)], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: [TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: thumbnailResource, progressiveSizes: [])], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
return .message(text: caption, attributes: attributes, mediaReference: .standalone(media: tmpImage), replyToMessageId: nil, localGroupingKey: nil)
} else {
return .message(text: caption, attributes: attributes, mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)
@ -71,7 +71,7 @@ public func outgoingMessageWithChatContextResult(to peerId: PeerId, results: Cha
if thumbnail.mimeType.hasPrefix("video/") {
videoThumbnails.append(TelegramMediaFile.VideoThumbnail(dimensions: thumbnail.dimensions ?? PixelDimensions(width: 128, height: 128), resource: thumbnailResource))
} else {
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: thumbnail.dimensions ?? PixelDimensions(width: 128, height: 128), resource: thumbnailResource))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: thumbnail.dimensions ?? PixelDimensions(width: 128, height: 128), resource: thumbnailResource, progressiveSizes: []))
}
}
var fileName = "file"

View file

@ -161,7 +161,12 @@ public func updatePeerPhotoInternal(postbox: Postbox, network: Network, stateMan
case let .photoSize(_, location, w, h, _):
switch location {
case let .fileLocationToBeDeprecated(volumeId, localId):
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: CloudPeerPhotoSizeMediaResource(datacenterId: dcId, sizeSpec: w <= 200 ? .small : .fullSize, volumeId: volumeId, localId: localId)))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: CloudPeerPhotoSizeMediaResource(datacenterId: dcId, sizeSpec: w <= 200 ? .small : .fullSize, volumeId: volumeId, localId: localId), progressiveSizes: []))
}
case let .photoSizeProgressive(_, location, w, h, sizes):
switch location {
case let .fileLocationToBeDeprecated(volumeId, localId):
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: CloudPeerPhotoSizeMediaResource(datacenterId: dcId, sizeSpec: w <= 200 ? .small : .fullSize, volumeId: volumeId, localId: localId), progressiveSizes: sizes))
}
default:
break

View file

@ -30,8 +30,8 @@ public enum StatsGraph: Equatable {
switch self {
case .Empty:
return true
case let .Failed(error):
return error.lowercased().contains("not enough data")
case .Failed:
return true
default:
return false
}
@ -193,15 +193,16 @@ private func requestChannelStats(postbox: Postbox, network: Network, datacenterI
flags |= (1 << 1)
}
let request = Api.functions.stats.getBroadcastStats(flags: flags, channel: inputChannel)
let signal: Signal<Api.stats.BroadcastStats, MTRpcError>
if network.datacenterId != datacenterId {
signal = network.download(datacenterId: Int(datacenterId), isMedia: false, tag: nil)
|> castError(MTRpcError.self)
|> mapToSignal { worker in
return worker.request(Api.functions.stats.getBroadcastStats(flags: flags, channel: inputChannel))
return worker.request(request)
}
} else {
signal = network.request(Api.functions.stats.getBroadcastStats(flags: flags, channel: inputChannel))
signal = network.request(request)
}
return signal
@ -212,7 +213,7 @@ private func requestChannelStats(postbox: Postbox, network: Network, datacenterI
}
}
private func requestGraph(network: Network, datacenterId: Int32, token: String, x: Int64? = nil) -> Signal<StatsGraph?, NoError> {
func requestGraph(network: Network, datacenterId: Int32, token: String, x: Int64? = nil) -> Signal<StatsGraph?, NoError> {
var flags: Int32 = 0
if let _ = x {
flags |= (1 << 0)

View file

@ -708,10 +708,10 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
var representations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: arc4random64())
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource, progressiveSizes: []))
resources.append((resource, thumb.makeData()))
}
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size)))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), progressiveSizes: []))
let image = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudSecretImage, id: file.id), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
parsedMedia.append(image)
}
@ -734,7 +734,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: arc4random64())
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource, progressiveSizes: []))
resources.append((resource, thumb.makeData()))
}
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), partialReference: nil, resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
@ -749,7 +749,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: arc4random64())
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource, progressiveSizes: []))
resources.append((resource, thumb.makeData()))
}
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), partialReference: nil, resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
@ -767,7 +767,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
case let .photoSize(_, location, w, h, size):
switch location {
case let .fileLocation(dcId, volumeId, localId, secret):
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size), fileReference: nil)))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size), fileReference: nil), progressiveSizes: []))
case .fileLocationUnavailable:
break
}
@ -777,7 +777,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
case let .fileLocation(dcId, volumeId, localId, secret):
let resource = CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: bytes.size, fileReference: nil)
resources.append((resource, bytes.makeData()))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: []))
case .fileLocationUnavailable:
break
}
@ -906,10 +906,10 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
var representations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: arc4random64())
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource, progressiveSizes: []))
resources.append((resource, thumb.makeData()))
}
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size)))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), progressiveSizes: []))
let image = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudSecretImage, id: file.id), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
parsedMedia.append(image)
}
@ -933,7 +933,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: arc4random64())
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource, progressiveSizes: []))
resources.append((resource, thumb.makeData()))
}
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), partialReference: nil, resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
@ -964,7 +964,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: arc4random64())
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource, progressiveSizes: []))
resources.append((resource, thumb.makeData()))
}
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), partialReference: nil, resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
@ -982,7 +982,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
case let .photoSize(_, location, w, h, size):
switch location {
case let .fileLocation(dcId, volumeId, localId, secret):
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size), fileReference: nil)))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size), fileReference: nil), progressiveSizes: []))
case .fileLocationUnavailable:
break
}
@ -992,7 +992,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
case let .fileLocation(dcId, volumeId, localId, secret):
let resource = CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: bytes.size, fileReference: nil)
resources.append((resource, bytes.makeData()))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: []))
case .fileLocationUnavailable:
break
}
@ -1140,10 +1140,10 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
var representations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: arc4random64())
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource, progressiveSizes: []))
resources.append((resource, thumb.makeData()))
}
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size)))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), progressiveSizes: []))
let image = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudSecretImage, id: file.id), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
parsedMedia.append(image)
}
@ -1167,7 +1167,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: arc4random64())
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource, progressiveSizes: []))
resources.append((resource, thumb.makeData()))
}
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), partialReference: nil, resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
@ -1198,7 +1198,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: arc4random64())
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: thumbW, height: thumbH), resource: resource, progressiveSizes: []))
resources.append((resource, thumb.makeData()))
}
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), partialReference: nil, resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
@ -1216,7 +1216,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
case let .photoSize(_, location, w, h, size):
switch location {
case let .fileLocation(dcId, volumeId, localId, secret):
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size), fileReference: nil)))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size), fileReference: nil), progressiveSizes: []))
case .fileLocationUnavailable:
break
}
@ -1226,7 +1226,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
case let .fileLocation(dcId, volumeId, localId, secret):
let resource = CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: bytes.size, fileReference: nil)
resources.append((resource, bytes.makeData()))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: []))
case .fileLocationUnavailable:
break
}

View file

@ -10,6 +10,7 @@ public enum SearchMessagesLocation: Equatable {
case general
case group(PeerGroupId)
case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?)
case publicForwards(messageId: MessageId, datacenterId: Int?)
}
private struct SearchMessagesPeerState: Equatable {
@ -282,7 +283,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
}
}
|> mapToSignal { (nextRate, lowerBound, inputPeer) in
account.network.request(Api.functions.messages.searchGlobal(flags: 0, folderId: nil, q: query, offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit), automaticFloodWait: false)
return account.network.request(Api.functions.messages.searchGlobal(flags: 0, folderId: nil, q: query, offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit), automaticFloodWait: false)
|> map { result -> (Api.messages.Messages?, Api.messages.Messages?) in
return (result, nil)
}
@ -290,6 +291,46 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
return .single((nil, nil))
}
}
case let .publicForwards(messageId, datacenterId):
remoteSearchResult = .single((nil, nil))
/*remoteSearchResult = account.postbox.transaction { transaction -> (Api.InputChannel?, Int32, MessageIndex?, Api.InputPeer) in
let sourcePeer = transaction.getPeer(messageId.peerId)
let inputChannel = sourcePeer.flatMap { apiInputChannel($0) }
var lowerBound: MessageIndex?
if let state = state, let message = state.main.messages.last {
lowerBound = message.index
}
if let lowerBound = lowerBound, let peer = transaction.getPeer(lowerBound.id.peerId), let inputPeer = apiInputPeer(peer) {
return (inputChannel, state?.main.nextRate ?? 0, lowerBound, inputPeer)
} else {
return (inputChannel, 0, lowerBound, .inputPeerEmpty)
}
}
|> mapToSignal { (inputChannel, nextRate, lowerBound, inputPeer) in
guard let inputChannel = inputChannel else {
return .complete()
}
let request = Api.functions.stats.getMessagePublicForwards(channel: inputChannel, msgId: messageId.id, offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit)
let signal: Signal<Api.messages.Messages, MTRpcError>
if let datacenterId = datacenterId, account.network.datacenterId != datacenterId {
signal = account.network.download(datacenterId: datacenterId, isMedia: false, tag: nil)
|> castError(MTRpcError.self)
|> mapToSignal { worker in
return worker.request(request)
}
} else {
signal = account.network.request(request, automaticFloodWait: false)
}
return signal
|> map { result -> (Api.messages.Messages?, Api.messages.Messages?) in
return (result, nil)
}
|> `catch` { _ -> Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> in
return .single((nil, nil))
}
}*/
}
return remoteSearchResult
@ -572,11 +613,11 @@ public func searchMessageIdByTimestamp(account: Account, peerId: PeerId, timesta
} |> switchToLatest
}
enum UpdatedRemotePeerError {
public enum UpdatedRemotePeerError {
case generic
}
func updatedRemotePeer(postbox: Postbox, network: Network, peer: PeerReference) -> Signal<Peer, UpdatedRemotePeerError> {
public func updatedRemotePeer(postbox: Postbox, network: Network, peer: PeerReference) -> Signal<Peer, UpdatedRemotePeerError> {
if let inputUser = peer.inputUser {
return network.request(Api.functions.users.getUsers(id: [inputUser]))
|> mapError { _ -> UpdatedRemotePeerError in

View file

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt {
return 116
return 117
}
public func parseMessage(_ data: Data!) -> Any! {

View file

@ -102,7 +102,7 @@ public func standaloneUploadedImage(account: Account, peerId: PeerId, text: Stri
|> mapToSignal { result -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
switch result {
case let .encryptedFile(id, accessHash, size, dcId, _):
return .single(.result(.media(.standalone(media: TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64()), representations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: SecretFileMediaResource(fileId: id, accessHash: accessHash, containerSize: size, decryptedSize: Int32(data.count), datacenterId: Int(dcId), key: key))], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])))))
return .single(.result(.media(.standalone(media: TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64()), representations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: SecretFileMediaResource(fileId: id, accessHash: accessHash, containerSize: size, decryptedSize: Int32(data.count), datacenterId: Int(dcId), key: key), progressiveSizes: [])], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])))))
case .encryptedFileEmpty:
return .fail(.generic)
}

View file

@ -11,13 +11,19 @@ func telegramStickerPachThumbnailRepresentationFromApiSize(datacenterId: Int32,
switch location {
case let .fileLocationToBeDeprecated(volumeId, localId):
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, volumeId: volumeId, localId: localId)
return TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource)
return TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [])
}
case let .photoSize(_, location, w, h, _):
switch location {
case let .fileLocationToBeDeprecated(volumeId, localId):
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, volumeId: volumeId, localId: localId)
return TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource)
return TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [])
}
case let .photoSizeProgressive(_, location, w, h, sizes):
switch location {
case let .fileLocationToBeDeprecated(volumeId, localId):
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, volumeId: volumeId, localId: localId)
return TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: sizes)
}
case .photoStrippedSize:
return nil

View file

@ -521,6 +521,10 @@ extension StoreMessage {
attributes.append(ViewCountMessageAttribute(count: Int(views)))
}
/*if let forwards = forwards, namespace != Namespaces.Message.ScheduledCloud {
attributes.append(ForwardCountMessageAttribute(count: Int(forwards)))
}*/
if let editDate = editDate {
attributes.append(EditedMessageAttribute(date: editDate, isHidden: (flags & (1 << 21)) != 0))
}

View file

@ -129,13 +129,19 @@ func telegramMediaFileThumbnailRepresentationsFromApiSizes(datacenterId: Int32,
switch location {
case let .fileLocationToBeDeprecated(volumeId, localId):
let resource = CloudDocumentSizeMediaResource(datacenterId: datacenterId, documentId: documentId, accessHash: accessHash, sizeSpec: type, volumeId: volumeId, localId: localId, fileReference: fileReference)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: []))
}
case let .photoSize(type, location, w, h, _):
switch location {
case let .fileLocationToBeDeprecated(volumeId, localId):
let resource = CloudDocumentSizeMediaResource(datacenterId: datacenterId, documentId: documentId, accessHash: accessHash, sizeSpec: type, volumeId: volumeId, localId: localId, fileReference: fileReference)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: []))
}
case let .photoSizeProgressive(type, location, w, h, sizes):
switch location {
case let .fileLocationToBeDeprecated(volumeId, localId):
let resource = CloudDocumentSizeMediaResource(datacenterId: datacenterId, documentId: documentId, accessHash: accessHash, sizeSpec: type, volumeId: volumeId, localId: localId, fileReference: fileReference)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: sizes))
}
case let .photoStrippedSize(_, data):
immediateThumbnailData = data.makeData()

View file

@ -13,13 +13,21 @@ func telegramMediaImageRepresentationsFromApiSizes(datacenterId: Int32, photoId:
switch location {
case let .fileLocationToBeDeprecated(volumeId, localId):
let resource = CloudPhotoSizeMediaResource(datacenterId: datacenterId, photoId: photoId, accessHash: accessHash, sizeSpec: type, volumeId: volumeId, localId: localId, size: nil, fileReference: fileReference)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: []))
}
case let .photoSize(type, location, w, h, size):
switch location {
case let .fileLocationToBeDeprecated(volumeId, localId):
let resource = CloudPhotoSizeMediaResource(datacenterId: datacenterId, photoId: photoId, accessHash: accessHash, sizeSpec: type, volumeId: volumeId, localId: localId, size: Int(size), fileReference: fileReference)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: []))
}
case let .photoSizeProgressive(type, location, w, h, sizes):
switch location {
case let .fileLocationToBeDeprecated(volumeId, localId):
if !sizes.isEmpty {
let resource = CloudPhotoSizeMediaResource(datacenterId: datacenterId, photoId: photoId, accessHash: accessHash, sizeSpec: type, volumeId: volumeId, localId: localId, size: Int(sizes[sizes.count - 1]), fileReference: fileReference)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: sizes))
}
}
case let .photoStrippedSize(_, data):
immediateThumbnailData = data.makeData()

Some files were not shown because too many files have changed in this diff Show more