Merge commit '9b342df78d' into mac-videocalls-release

This commit is contained in:
overtake 2020-08-21 14:06:24 +03:00
commit c4761bcb59
120 changed files with 6620 additions and 2435 deletions

3
.gitmodules vendored
View file

@ -26,3 +26,6 @@ url=https://github.com/ali-fareed/webrtc-ios.git
[submodule "submodules/TgVoipWebrtc/tgcalls"]
path = submodules/TgVoipWebrtc/tgcalls
url=../tgcalls.git
[submodule "third-party/libvpx/libvpx"]
path = third-party/libvpx/libvpx
url = https://github.com/webmproject/libvpx.git

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
{"v":"5.5.9","fr":60,"ip":0,"op":120,"w":240,"h":360,"nm":"Lock1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Path 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[120,282,0],"to":[0,-1.667,0],"ti":[0,0.833,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[120,272,0],"to":[0,-0.833,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":59,"s":[120,277,0],"to":[0,0,0],"ti":[0,-0.833,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":75,"s":[120,272,0],"to":[0,0.833,0],"ti":[0,-1.667,0]},{"t":120,"s":[120,282,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-8,3],[0,-3],[8,3]],"c":false},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.592000007629,0.592000007629,0.592000007629,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Path 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Rectangle","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.66,0],[0,0],[0,-1.66],[0,0],[1.66,0],[0,0],[0,1.66],[0,0]],"o":[[0,0],[1.66,0],[0,0],[0,1.66],[0,0],[-1.66,0],[0,0],[0,-1.66]],"v":[[-5,-7],[5,-7],[8,-4],[8,4],[5,7],[-5,7],[-8,4],[-8,-4]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.592000007629,0.592000007629,0.592000007629,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.96862745098,0.96862745098,0.96862745098,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Path","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":45,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":75,"s":[0]},{"t":120,"s":[10]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[150,118,0],"to":[0,1.667,0],"ti":[0,-0.833,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[150,128,0],"to":[0,0.833,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[150,123,0],"to":[0,0,0],"ti":[0,0.833,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":75,"s":[150,128,0],"to":[0,-0.833,0],"ti":[0,1.667,0]},{"t":120,"s":[150,118,0]}],"ix":2},"a":{"a":0,"k":[30,36,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.76,0],[0,-2.76],[0,0]],"o":[[0,0],[0,-2.76],[2.76,0],[0,0],[0,0]],"v":[[-5,2],[-5,-1],[0,-6],[5,-1],[5,6]],"c":false},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.592000007629,0.592000007629,0.592000007629,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0}],"markers":[]}

1
Random.txt Normal file
View file

@ -0,0 +1 @@
LS3VfNVetXNy6mHmek8hegOh0wsvmJs0hcrc7PLA9eI=

View file

@ -730,6 +730,7 @@ plist_fragment(
<string>onionhttp</string>
<string>ucbrowser</string>
<string>dolphin</string>
<string>instagram-stories</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>

View file

@ -3,7 +3,7 @@
@implementation Serialization
- (NSUInteger)currentLayer {
return 116;
return 118;
}
- (id _Nullable)parseMessage:(NSData * _Nullable)data {

View file

@ -264,6 +264,7 @@
<string>onionhttp</string>
<string>ucbrowser</string>
<string>dolphin</string>
<string>instagram-stories</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>

View file

@ -98,6 +98,7 @@
<string>onionhttp</string>
<string>ucbrowser</string>
<string>dolphin</string>
<string>instagram-stories</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -5735,3 +5735,5 @@ Any member of this group will be able to see messages in the channel.";
"Call.AudioRouteMute" = "Mute Yourself";
"AccessDenied.VideoCallCamera" = "Telegram needs access to your camera to make video calls.\n\nPlease go to Settings > Privacy > Camera and set Telegram to ON.";
"Call.AccountIsLoggedOnCurrentDevice" = "Sorry, you can't call %@ because that account is logged in to Telegram on the device you're using for the call.";

View file

@ -71,7 +71,7 @@ for NAME in $PATCH_OPTIONS; do
sed -i "" -e '1h;2,$H;$!d;g' -e 's/\("'"$NAME"'" : {\n[ ]*"p" : "$(inherited)\)/\1'" ${BAZEL_OPTIONS[*]}"'/' "$GEN_DIRECTORY/${APP_TARGET}.tulsiproj/Configs/${APP_TARGET}.tulsigen"
done
sed -i "" -e '1h;2,$H;$!d;g' -e 's/\("sourceFilters" : \[\n[ ]*\)"\.\/\.\.\."/\1"'"${APP_TARGET}"'\/...", "submodules\/..."/' "$GEN_DIRECTORY/${APP_TARGET}.tulsiproj/Configs/${APP_TARGET}.tulsigen"
sed -i "" -e '1h;2,$H;$!d;g' -e 's/\("sourceFilters" : \[\n[ ]*\)"\.\/\.\.\."/\1"'"${APP_TARGET}"'\/...", "submodules\/...", "third-party\/..."/' "$GEN_DIRECTORY/${APP_TARGET}.tulsiproj/Configs/${APP_TARGET}.tulsigen"
"$TULSI" -- \
--verbose \

View file

@ -40,7 +40,7 @@ public struct PresentationCallState: Equatable {
case connecting(Data?)
case active(Double, Int32?, Data)
case reconnecting(Double, Int32?, Data)
case terminating
case terminating(CallSessionTerminationReason?)
case terminated(CallId?, CallSessionTerminationReason?, Bool)
}

View file

@ -12,6 +12,7 @@ static_library(
"//submodules/YuvConversion:YuvConversion",
"//submodules/GZip:GZip",
"//submodules/rlottie:RLottieBinding",
"//submodules/MediaResources:MediaResources",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View file

@ -13,6 +13,7 @@ swift_library(
"//submodules/YuvConversion:YuvConversion",
"//submodules/GZip:GZip",
"//submodules/rlottie:RLottieBinding",
"//submodules/MediaResources:MediaResources",
],
visibility = [
"//visibility:public",

View file

@ -6,6 +6,7 @@ import AsyncDisplayKit
import RLottieBinding
import GZip
import YuvConversion
import MediaResources
private let sharedQueue = Queue()
private let sharedStoreQueue = Queue.concurrentDefaultQueue()
@ -438,7 +439,7 @@ private final class AnimatedStickerDirectFrameSourceCache {
private var scratchBuffer: Data
private var decodeBuffer: Data
init?(queue: Queue, pathPrefix: String, width: Int, height: Int, frameCount: Int) {
init?(queue: Queue, pathPrefix: String, width: Int, height: Int, frameCount: Int, fitzModifier: EmojiFitzModifier?) {
self.queue = queue
self.storeQueue = sharedStoreQueue
@ -446,7 +447,13 @@ private final class AnimatedStickerDirectFrameSourceCache {
self.width = width
self.height = height
let path = "\(pathPrefix)_\(width):\(height).stickerframecache"
let suffix : String
if let fitzModifier = fitzModifier {
suffix = "_fitz\(fitzModifier.rawValue)"
} else {
suffix = ""
}
let path = "\(pathPrefix)_\(width):\(height)\(suffix).stickerframecache"
var file = ManagedFileImpl(queue: queue, path: path, mode: .readwrite)
if let file = file {
self.file = file
@ -594,7 +601,7 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource
return self.currentFrame % self.frameCount
}
init?(queue: Queue, data: Data, width: Int, height: Int, cachePathPrefix: String?) {
init?(queue: Queue, data: Data, width: Int, height: Int, cachePathPrefix: String?, fitzModifier: EmojiFitzModifier?) {
self.queue = queue
self.data = data
self.width = width
@ -602,7 +609,9 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource
self.bytesPerRow = (4 * Int(width) + 15) & (~15)
self.currentFrame = 0
let rawData = TGGUnzipData(data, 8 * 1024 * 1024) ?? data
guard let animation = LottieInstance(data: rawData, cacheKey: "") else {
let decompressedData = transformedWithFitzModifier(data: rawData, fitzModifier: fitzModifier)
guard let animation = LottieInstance(data: decompressedData, cacheKey: "") else {
return nil
}
self.animation = animation
@ -611,7 +620,7 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource
self.frameRate = Int(animation.frameRate)
self.cache = cachePathPrefix.flatMap { cachePathPrefix in
AnimatedStickerDirectFrameSourceCache(queue: queue, pathPrefix: cachePathPrefix, width: width, height: height, frameCount: frameCount)
AnimatedStickerDirectFrameSourceCache(queue: queue, pathPrefix: cachePathPrefix, width: width, height: height, frameCount: frameCount, fitzModifier: fitzModifier)
}
}
@ -698,11 +707,15 @@ public struct AnimatedStickerStatus: Equatable {
}
public protocol AnimatedStickerNodeSource {
var fitzModifier: EmojiFitzModifier? { get }
func cachedDataPath(width: Int, height: Int) -> Signal<(String, Bool), NoError>
func directDataPath() -> Signal<String, NoError>
}
public final class AnimatedStickerNodeLocalFileSource: AnimatedStickerNodeSource {
public var fitzModifier: EmojiFitzModifier? = nil
public let path: String
public init(path: String) {
@ -733,8 +746,8 @@ 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, String?)?
private var cachedData: (Data, Bool)?
private var directData: (Data, String, Int, Int, String?, EmojiFitzModifier?)?
private var cachedData: (Data, Bool, EmojiFitzModifier?)?
private var renderer: (AnimationRenderer & ASDisplayNode)?
@ -809,7 +822,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
return
}
if let directData = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
strongSelf.directData = (directData, path, width, height, cachePathPrefix)
strongSelf.directData = (directData, path, width, height, cachePathPrefix, source.fitzModifier)
}
if case let .still(position) = playbackMode {
strongSelf.seekTo(position)
@ -830,9 +843,9 @@ public final class AnimatedStickerNode: ASDisplayNode {
return
}
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
if let (_, currentComplete) = strongSelf.cachedData {
if let (_, currentComplete, _) = strongSelf.cachedData {
if !currentComplete {
strongSelf.cachedData = (data, complete)
strongSelf.cachedData = (data, complete, source.fitzModifier)
strongSelf.frameSource.with { frameSource in
frameSource?.with { frameSource in
if let frameSource = frameSource.value as? AnimatedStickerCachedFrameSource {
@ -842,7 +855,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
}
}
} else {
strongSelf.cachedData = (data, complete)
strongSelf.cachedData = (data, complete, source.fitzModifier)
if strongSelf.isPlaying {
strongSelf.play()
} else if strongSelf.canDisplayFirstFrame {
@ -892,8 +905,8 @@ 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, cachePathPrefix: directData.4)
} else if let (cachedData, cachedDataComplete) = cachedData {
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3, cachePathPrefix: directData.4, fitzModifier: directData.5)
} else if let (cachedData, cachedDataComplete, _) = cachedData {
if #available(iOS 9.0, *) {
maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData, complete: cachedDataComplete, notifyUpdated: {
notifyUpdated?()
@ -964,8 +977,8 @@ 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, cachePathPrefix: directData.4)
} else if let (cachedData, cachedDataComplete) = cachedData {
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3, cachePathPrefix: directData.4, fitzModifier: directData.5)
} else if let (cachedData, cachedDataComplete, _) = cachedData {
if #available(iOS 9.0, *) {
maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData, complete: cachedDataComplete, notifyUpdated: {
notifyUpdated?()
@ -1054,11 +1067,11 @@ 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, cachePathPrefix: directData.4)
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3, cachePathPrefix: directData.4, fitzModifier: directData.5)
if case .end = position {
maybeFrameSource?.skipToEnd()
}
} else if let (cachedData, cachedDataComplete) = cachedData {
} else if let (cachedData, cachedDataComplete, _) = cachedData {
if #available(iOS 9.0, *) {
maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData, complete: cachedDataComplete, notifyUpdated: {})
}

View file

@ -0,0 +1,76 @@
import Foundation
import UIKit
import MediaResources
let colorKeyRegex = try? NSRegularExpression(pattern: "\"k\":\\[[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\]")
public func transformedWithFitzModifier(data: Data, fitzModifier: EmojiFitzModifier?) -> Data {
if let fitzModifier = fitzModifier, var string = String(data: data, encoding: .utf8) {
let colors: [UIColor] = [0xf77e41, 0xffb139, 0xffd140, 0xffdf79].map { UIColor(rgb: $0) }
let replacementColors: [UIColor]
switch fitzModifier {
case .type12:
replacementColors = [0xca907a, 0xedc5a5, 0xf7e3c3, 0xfbefd6].map { UIColor(rgb: $0) }
case .type3:
replacementColors = [0xaa7c60, 0xc8a987, 0xddc89f, 0xe6d6b2].map { UIColor(rgb: $0) }
case .type4:
replacementColors = [0x8c6148, 0xad8562, 0xc49e76, 0xd4b188].map { UIColor(rgb: $0) }
case .type5:
replacementColors = [0x6e3c2c, 0x925a34, 0xa16e46, 0xac7a52].map { UIColor(rgb: $0) }
case .type6:
replacementColors = [0x291c12, 0x472a22, 0x573b30, 0x68493c].map { UIColor(rgb: $0) }
}
func colorToString(_ color: UIColor) -> String {
var r: CGFloat = 0.0
var g: CGFloat = 0.0
var b: CGFloat = 0.0
if color.getRed(&r, green: &g, blue: &b, alpha: nil) {
return "\"k\":[\(r),\(g),\(b),1]"
}
return ""
}
func match(_ a: Double, _ b: Double, eps: Double) -> Bool {
return abs(a - b) < eps
}
var replacements: [(NSTextCheckingResult, String)] = []
if let colorKeyRegex = colorKeyRegex {
let results = colorKeyRegex.matches(in: string, range: NSRange(string.startIndex..., in: string))
for result in results.reversed() {
if let range = Range(result.range, in: string) {
let substring = String(string[range])
let color = substring[substring.index(string.startIndex, offsetBy: "\"k\":[".count) ..< substring.index(before: substring.endIndex)]
let components = color.split(separator: ",")
if components.count == 4, let r = Double(components[0]), let g = Double(components[1]), let b = Double(components[2]), let a = Double(components[3]) {
if match(a, 1.0, eps: 0.01) {
for i in 0 ..< colors.count {
let color = colors[i]
var cr: CGFloat = 0.0
var cg: CGFloat = 0.0
var cb: CGFloat = 0.0
if color.getRed(&cr, green: &cg, blue: &cb, alpha: nil) {
if match(r, Double(cr), eps: 0.01) && match(g, Double(cg), eps: 0.01) && match(b, Double(cb), eps: 0.01) {
replacements.append((result, colorToString(replacementColors[i])))
}
}
}
}
}
}
}
}
for (result, text) in replacements {
if let range = Range(result.range, in: string) {
string = string.replacingCharacters(in: range, with: text)
}
}
return string.data(using: .utf8) ?? data
} else {
return data
}
}

View file

@ -762,7 +762,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
)
)
let location: SearchMessagesLocation
location = .general
location = .general(tags: nil)
updateSearchContext { _ in
return (nil, true)

View file

@ -9,6 +9,7 @@ static_library(
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
"//submodules/Display:Display#shared",
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
"//submodules/SearchBarNode:SearchBarNode",

View file

@ -10,6 +10,7 @@ swift_library(
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/TelegramCore:TelegramCore",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
"//submodules/SearchBarNode:SearchBarNode",

View file

@ -2,12 +2,15 @@ import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import Postbox
import TelegramPresentationData
import TelegramStringFormatting
import SearchBarNode
import AppBundle
import TelegramCore
private func loadCountryCodes() -> [(String, Int)] {
private func loadCountryCodes() -> [Country] {
guard let filePath = getAppBundle().path(forResource: "PhoneCountries", ofType: "txt") else {
return []
}
@ -21,10 +24,12 @@ private func loadCountryCodes() -> [(String, Int)] {
let delimiter = ";"
let endOfLine = "\n"
var result: [(String, Int)] = []
var result: [Country] = []
var currentLocation = data.startIndex
let locale = Locale(identifier: "en-US")
while true {
guard let codeRange = data.range(of: delimiter, options: [], range: currentLocation ..< data.endIndex) else {
break
@ -40,8 +45,9 @@ private func loadCountryCodes() -> [(String, Int)] {
let maybeNameRange = data.range(of: endOfLine, options: [], range: idRange.upperBound ..< data.endIndex)
let countryName = locale.localizedString(forIdentifier: countryId) ?? ""
if let countryCodeInt = Int(countryCode) {
result.append((countryId, countryCodeInt))
result.append(Country(code: countryId, name: countryName, localizedName: nil, countryCodes: [Country.CountryCode(code: countryCode, prefixes: [], patterns: [])], hidden: false))
}
if let maybeNameRange = maybeNameRange {
@ -54,7 +60,14 @@ private func loadCountryCodes() -> [(String, Int)] {
return result
}
private let countryCodes: [(String, Int)] = loadCountryCodes()
private var countryCodes: [Country] = loadCountryCodes()
public func loadServerCountryCodes(accountManager: AccountManager, network: Network) {
let _ = (getCountriesList(accountManager: accountManager, network: network, langCode: nil)
|> deliverOnMainQueue).start(next: { countries in
countryCodes = countries
})
}
private final class AuthorizationSequenceCountrySelectionNavigationContentNode: NavigationBarContentNode {
private let theme: PresentationTheme
@ -117,8 +130,8 @@ private final class AuthorizationSequenceCountrySelectionNavigationContentNode:
public final class AuthorizationSequenceCountrySelectionController: ViewController {
public static func lookupCountryNameById(_ id: String, strings: PresentationStrings) -> String? {
for (itemId, _) in countryCodes {
if id == itemId {
for country in countryCodes {
if id == country.code {
let locale = localeWithStrings(strings)
if let countryName = locale.localizedString(forRegionCode: id) {
return countryName
@ -131,9 +144,22 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll
}
public static func lookupCountryIdByCode(_ code: Int) -> String? {
for (itemId, itemCode) in countryCodes {
if itemCode == code {
return itemId
for country in countryCodes {
for countryCode in country.countryCodes {
if countryCode.code == "\(code)" {
return country.code
}
}
}
return nil
}
public static func lookupPatternByCode(_ code: Int) -> String? {
for country in countryCodes {
for countryCode in country.countryCodes {
if countryCode.code == "\(code)" {
return countryCode.patterns.first
}
}
}
return nil

View file

@ -277,7 +277,12 @@ public final class DeviceAccess {
if status == PGCameraAuthorizationStatusRestricted {
text = presentationData.strings.AccessDenied_CameraRestricted
} else {
text = presentationData.strings.AccessDenied_Camera
switch cameraSubject {
case .video:
text = presentationData.strings.AccessDenied_Camera
case .videoCall:
text = presentationData.strings.AccessDenied_VideoCallCamera
}
}
completion(false)
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {

View file

@ -22,6 +22,7 @@ public struct Font {
public static let bold = Traits(rawValue: 1 << 0)
public static let italic = Traits(rawValue: 1 << 1)
public static let monospacedNumbers = Traits(rawValue: 1 << 2)
}
public enum Weight {
@ -43,6 +44,15 @@ public struct Font {
symbolicTraits.insert(.traitItalic)
}
var updatedDescriptor: UIFontDescriptor? = descriptor.withSymbolicTraits(symbolicTraits)
if traits.contains(.monospacedNumbers) {
updatedDescriptor = descriptor.addingAttributes([
UIFontDescriptor.AttributeName.featureSettings: [
[UIFontDescriptor.FeatureKey.featureIdentifier:
kNumberSpacingType,
UIFontDescriptor.FeatureKey.typeIdentifier:
kMonospacedNumbersSelector]
]])
}
switch design {
case .serif:
updatedDescriptor = updatedDescriptor?.withDesign(.serif)

View file

@ -5,7 +5,7 @@ import AVFoundation
public extension UnicodeScalar {
var isEmoji: Bool {
switch self.value {
case 0x1F600...0x1F64F, 0x1F300...0x1F5FF, 0x1F680...0x1F6FF, 0x1F1E6...0x1F1FF, 0xE0020...0xE007F, 0xFE00...0xFE0F, 0x1F900...0x1F9FF, 0x1F018...0x1F0F5, 0x1F200...0x1F270, 65024...65039, 9100...9300, 8400...8447, 0x1F004, 0x1F18E, 0x1F191...0x1F19A, 0x1F5E8:
case 0x1F600...0x1F64F, 0x1F300...0x1F5FF, 0x1F680...0x1F6FF, 0x1F1E6...0x1F1FF, 0xE0020...0xE007F, 0xFE00...0xFE0F, 0x1F900...0x1F9FF, 0x1F018...0x1F0F5, 0x1F200...0x1F270, 65024...65039, 9100...9300, 8400...8447, 0x1F004, 0x1F18E, 0x1F191...0x1F19A, 0x1F5E8, 0x1FA70...0x1FA73, 0x1FA78...0x1FA7A, 0x1FA80...0x1FA82, 0x1FA90...0x1FA95:
return true
case 0x2603, 0x265F, 0x267E, 0x2692, 0x26C4, 0x26C8, 0x26CE, 0x26CF, 0x26D1...0x26D3, 0x26E9, 0x26F0...0x26F9, 0x2705, 0x270A, 0x270B, 0x2728, 0x274E, 0x2753...0x2755, 0x274C, 0x2795...0x2797, 0x27B0, 0x27BF:
return true

View file

@ -41,7 +41,7 @@ public final class HashtagSearchController: TelegramBaseController {
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations)
let location: SearchMessagesLocation = .general
let location: SearchMessagesLocation = .general(tags: nil)
let search = searchMessages(account: context.account, location: location, query: query, state: nil)
let foundMessages: Signal<[ChatListSearchEntry], NoError> = search
|> map { result, _ in

View file

@ -11,7 +11,6 @@
@protocol TGModernConversationInputMicButtonDecoration <NSObject>
- (void)updateLevel:(CGFloat)level;
- (void)tick:(CGFloat)level;
- (void)setColor:(UIColor *)color;
- (void)stopAnimating;
- (void)startAnimating;

View file

@ -1461,13 +1461,13 @@
NSMutableArray *actions = [[NSMutableArray alloc] init];
NSString *text = [self itemIsLivePhoto] ? TGLocalized(@"MediaPicker.LivePhotoDescription") : TGLocalized(@"MediaPicker.VideoMuteDescription");
[actions addObject:@{@"title":text}];
_tooltipContainerView.menuView.forceArrowOnTop = true;
_tooltipContainerView.menuView.forceArrowOnTop = false;
_tooltipContainerView.menuView.multiline = true;
[_tooltipContainerView.menuView setButtonsAndActions:actions watcherHandle:nil];
_tooltipContainerView.menuView.buttonHighlightDisabled = true;
[_tooltipContainerView.menuView sizeToFit];
CGRect iconViewFrame = CGRectMake(12, 188 + _safeAreaInset.top, 40, 40);
CGRect iconViewFrame = CGRectMake(12, self.frame.size.height - 192.0 - _safeAreaInset.bottom, 40, 40);
[_tooltipContainerView showMenuFromRect:iconViewFrame animated:false];
}

View file

@ -514,8 +514,8 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
_innerCircleView.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
_outerCircleView.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
if (toSmallSize) {
_decoration.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(0.33f, 0.33f), CGAffineTransformMakeTranslation(-4, 0));
_innerIconWrapperView.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(0.492f, 0.492f), CGAffineTransformMakeTranslation(-TGScreenPixel, 0));
_decoration.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(0.33f, 0.33f), CGAffineTransformMakeTranslation(0, 2 - TGScreenPixel));
_innerIconWrapperView.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(0.492f, 0.492f), CGAffineTransformMakeTranslation(-TGScreenPixel, 1));
} else {
_decoration.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
_decoration.alpha = 0.0;
@ -846,7 +846,6 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
NSTimeInterval t = CACurrentMediaTime();
_currentLevel = _currentLevel * 0.9f + _inputLevel * 0.1f;
[_decoration tick:_currentLevel];
_currentTranslation = MIN(0.0, _currentTranslation * 0.7f + _targetTranslation * 0.3f);
_cancelTranslation = MIN(0.0, _cancelTranslation * 0.7f + _cancelTargetTranslation * 0.3f);

View file

@ -15,6 +15,7 @@ objc_library(
]),
includes = [
"Public",
"Public/opus",
],
deps = [
":opus_lib",

View file

@ -173,12 +173,12 @@ private final class ChannelOwnershipTransferPasswordFieldNode: ASDisplayNode, UI
}
}
private final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
public final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
private let strings: PresentationStrings
private let titleNode: ASTextNode
private let textNode: ASTextNode
let inputFieldNode: ChannelOwnershipTransferPasswordFieldNode
fileprivate let inputFieldNode: ChannelOwnershipTransferPasswordFieldNode
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [TextAlertContentActionNode]
@ -190,18 +190,25 @@ private final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
private let hapticFeedback = HapticFeedback()
var complete: (() -> Void)? {
public var complete: (() -> Void)? {
didSet {
self.inputFieldNode.complete = self.complete
}
}
override var dismissOnOutsideTap: Bool {
public var theme: PresentationTheme {
didSet {
self.inputFieldNode.updateTheme(self.theme)
}
}
public override var dismissOnOutsideTap: Bool {
return self.isUserInteractionEnabled
}
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction]) {
public init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction]) {
self.strings = strings
self.theme = ptheme
self.titleNode = ASTextNode()
self.titleNode.maximumNumberOfLines = 2
@ -258,19 +265,19 @@ private final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
self.disposable.dispose()
}
func dismissInput() {
public func dismissInput() {
self.inputFieldNode.deactivateInput()
}
var password: String {
public var password: String {
return self.inputFieldNode.password
}
func updateIsChecking(_ checking: Bool) {
public func updateIsChecking(_ checking: Bool) {
self.inputFieldNode.updateIsChecking(checking)
}
override func updateTheme(_ theme: AlertControllerTheme) {
public override func updateTheme(_ theme: AlertControllerTheme) {
self.titleNode.attributedText = NSAttributedString(string: self.strings.Channel_OwnershipTransfer_EnterPassword, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
self.textNode.attributedText = NSAttributedString(string: self.strings.Channel_OwnershipTransfer_EnterPasswordText, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
@ -281,13 +288,13 @@ private final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
for separatorNode in self.actionVerticalSeparators {
separatorNode.backgroundColor = theme.separatorColor
}
if let size = self.validLayout {
_ = self.updateLayout(size: size, transition: .immediate)
}
}
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
public override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
var size = size
size.width = min(size.width, 270.0)
let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)
@ -400,7 +407,7 @@ private final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
return resultSize
}
func animateError() {
public func animateError() {
self.inputFieldNode.updateIsInvalid()
self.inputFieldNode.layer.addShakeAnimation()
self.hapticFeedback.error()

View file

@ -76,9 +76,24 @@ private func cleanSuffix(_ text: String) -> String {
return result
}
extension String {
func applyPatternOnNumbers(pattern: String, replacementCharacter: Character) -> String {
var pureNumber = self.replacingOccurrences( of: "[^0-9]", with: "", options: .regularExpression)
for index in 0 ..< pattern.count {
guard index < pureNumber.count else { return pureNumber }
let stringIndex = String.Index(encodedOffset: index)
let patternCharacter = pattern[stringIndex]
guard patternCharacter != replacementCharacter else { continue }
pureNumber.insert(patternCharacter, at: stringIndex)
}
return pureNumber
}
}
public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
public let countryCodeField: TextFieldNode
public let numberField: TextFieldNode
public let placeholderNode: ImmediateTextNode
public var previousCountryCodeText = "+"
public var previousNumberText = ""
@ -151,13 +166,33 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
private let phoneFormatter = InteractivePhoneFormatter()
public var mask: NSAttributedString? {
didSet {
self.updatePlaceholder()
}
}
private func updatePlaceholder() {
if let mask = self.mask {
let mutableMask = NSMutableAttributedString(attributedString: mask)
mutableMask.replaceCharacters(in: NSRange(location: 0, length: mask.string.count), with: mask.string.replacingOccurrences(of: "X", with: "-"))
mutableMask.addAttribute(.foregroundColor, value: UIColor.clear, range: NSRange(location: 0, length: min(self.numberField.textField.text?.count ?? 0, mask.string.count)))
self.placeholderNode.attributedText = mutableMask
} else {
self.placeholderNode.attributedText = NSAttributedString(string: "")
}
let _ = self.placeholderNode.updateLayout(CGSize(width: self.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
}
private let fontSize: CGFloat
public init(fontSize: CGFloat = 20.0) {
self.fontSize = fontSize
let font = Font.with(size: fontSize, design: .regular, traits: [.monospacedNumbers])
self.countryCodeField = TextFieldNode()
self.countryCodeField.textField.font = Font.regular(fontSize)
self.countryCodeField.textField.font = font
self.countryCodeField.textField.textAlignment = .center
self.countryCodeField.textField.returnKeyType = .next
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
@ -167,18 +202,21 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
self.countryCodeField.textField.keyboardType = .numberPad
}
self.placeholderNode = ImmediateTextNode()
self.numberField = TextFieldNode()
self.numberField.textField.font = Font.regular(fontSize)
self.numberField.textField.font = font
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
self.numberField.textField.keyboardType = .asciiCapableNumberPad
self.numberField.textField.textContentType = .telephoneNumber
} else {
self.numberField.textField.keyboardType = .numberPad
}
self.numberField.textField.defaultTextAttributes = [NSAttributedString.Key.font: font, NSAttributedString.Key.kern: 2.0]
super.init()
self.addSubnode(self.countryCodeField)
self.addSubnode(self.placeholderNode)
self.addSubnode(self.numberField)
self.numberField.textField.didDeleteBackwardWhileEmpty = { [weak self] in
@ -223,8 +261,9 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
private func updateNumber(_ inputText: String, tryRestoringInputPosition: Bool = true, forceNotifyCountryCodeUpdated: Bool = false) {
let (regionPrefix, text) = self.phoneFormatter.updateText(inputText)
var realRegionPrefix: String
let numberText: String
var numberText: String
if let regionPrefix = regionPrefix, !regionPrefix.isEmpty, regionPrefix != "+" {
realRegionPrefix = cleanSuffix(regionPrefix)
if !realRegionPrefix.hasPrefix("+") {
@ -239,6 +278,10 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
numberText = ""
}
if let mask = self.mask {
numberText = numberText.applyPatternOnNumbers(pattern: mask.string, replacementCharacter: "X")
}
var focusOnNumber = false
if realRegionPrefix != self.countryCodeField.textField.text {
self.countryCodeField.textField.text = realRegionPrefix
@ -296,5 +339,7 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
if focusOnNumber && !self.numberField.textField.isFirstResponder {
self.numberField.textField.becomeFirstResponder()
}
self.updatePlaceholder()
}
}

View file

@ -72,7 +72,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case alternativeFolderTabs(Bool)
case playerEmbedding(Bool)
case playlistPlayback(Bool)
case enableHighBitrateVideoCalls(Bool)
case preferredVideoCodec(Int, String, String?, Bool)
case hostInfo(PresentationTheme, String)
case versionInfo(PresentationTheme)
@ -88,14 +88,14 @@ 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 .enableHighBitrateVideoCalls:
case .preferredVideoCodec:
return DebugControllerSection.videoExperiments.rawValue
case .hostInfo, .versionInfo:
return DebugControllerSection.info.rawValue
}
}
var stableId: Int32 {
var stableId: Int {
switch self {
case .sendLogs:
return 0
@ -147,12 +147,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 24
case .playlistPlayback:
return 25
case .enableHighBitrateVideoCalls:
return 26
case let .preferredVideoCodec(index, _, _, _):
return 26 + index
case .hostInfo:
return 29
return 100
case .versionInfo:
return 30
return 101
}
}
@ -570,12 +570,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .enableHighBitrateVideoCalls(value):
return ItemListSwitchItem(presentationData: presentationData, title: "HD Video Calls", value: value, sectionId: self.section, style: .blocks, updated: { value in
case let .preferredVideoCodec(_, title, value, isSelected):
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: {
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings
settings.enableHighBitrateVideoCalls = value
settings.preferredVideoCodec = value
return settings
})
}).start()
@ -625,7 +625,18 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
entries.append(.alternativeFolderTabs(experimentalSettings.foldersTabAtBottom))
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
entries.append(.enableHighBitrateVideoCalls(experimentalSettings.enableHighBitrateVideoCalls))
let codecs: [(String, String?)] = [
("No Preference", nil),
("H265", "H265"),
("H264", "H264"),
("VP8", "VP8"),
("VP9", "VP9")
]
for i in 0 ..< codecs.count {
entries.append(.preferredVideoCodec(i, codecs[i].0, codecs[i].1, experimentalSettings.preferredVideoCodec == codecs[i].1))
}
if let backupHostOverride = networkSettings?.backupHostOverride {
entries.append(.hostInfo(presentationData.theme, "Host: \(backupHostOverride)"))

View file

@ -593,6 +593,27 @@ public final class ShareController: ViewController {
}
self.controllerNode.shareExternal = { [weak self] in
if let strongSelf = self {
if case let .messages(messages) = strongSelf.subject, let message = messages.first, let peer = message.peers[message.id.peerId] {
let renderer = MessageStoryRenderer(context: strongSelf.currentContext, messages: messages)
let layout = ContainerViewLayout(size: CGSize(width: 414.0, height: 896.0), metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: .iPhoneX, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: 0.0, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false)
renderer.update(layout: layout) { image in
if let data = image?.pngData() {
let pasteboardItems: [[String: Any]] = [["com.instagram.sharedSticker.backgroundImage": data,
"com.instagram.sharedSticker.contentURL": "https://t.me/\(peer.addressName ?? "")/\(message.id.id)"]]
if #available(iOS 10.0, *) {
UIPasteboard.general.setItems(pasteboardItems, options: [.expirationDate: Date().addingTimeInterval(5 * 60)])
} else {
// UIPasteboard.general.setItems(pasteboardItems)
}
strongSelf.sharedContext.applicationBindings.openUrl("instagram-stories://share")
}
}
return .complete()
}
var collectableItems: [CollectableExternalShareItem] = []
switch strongSelf.subject {
case let .url(text):
@ -865,3 +886,137 @@ public final class ShareController: ViewController {
}))
}
}
class MessageStoryRenderer {
private let context: AccountContext
private let presentationData: PresentationData
private let messages: [Message]
let containerNode: ASDisplayNode
private let instantChatBackgroundNode: WallpaperBackgroundNode
private let messagesContainerNode: ASDisplayNode
private var dateHeaderNode: ListViewItemHeaderNode?
private var messageNodes: [ListViewItemNode]?
private let addressNode: ImmediateTextNode
init(context: AccountContext, messages: [Message]) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.messages = messages
self.containerNode = ASDisplayNode()
self.instantChatBackgroundNode = WallpaperBackgroundNode()
self.instantChatBackgroundNode.displaysAsynchronously = false
self.instantChatBackgroundNode.image = chatControllerBackgroundImage(theme: presentationData.theme, wallpaper: .builtin(WallpaperSettings()), mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper)
self.messagesContainerNode = ASDisplayNode()
self.messagesContainerNode.clipsToBounds = true
self.messagesContainerNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
let message = messages.first!
let addressName = message.peers[message.id.peerId]?.addressName ?? ""
self.addressNode = ImmediateTextNode()
self.addressNode.displaysAsynchronously = false
self.addressNode.attributedText = NSAttributedString(string: "t.me/\(addressName)/\(message.id.id)", font: Font.medium(14.0), textColor: UIColor(rgb: 0xa8b7c4))
// self.addressNode.textShadowColor = .black
self.containerNode.addSubnode(self.instantChatBackgroundNode)
self.containerNode.addSubnode(self.messagesContainerNode)
self.containerNode.addSubnode(self.addressNode)
}
func update(layout: ContainerViewLayout, completion: @escaping (UIImage?) -> Void) {
self.updateMessagesLayout(layout: layout)
Queue.mainQueue().after(0.01) {
UIGraphicsBeginImageContextWithOptions(layout.size, false, 3.0)
self.containerNode.view.drawHierarchy(in: CGRect(origin: CGPoint(), size: layout.size), afterScreenUpdates: true)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
completion(img)
}
}
private func updateMessagesLayout(layout: ContainerViewLayout) {
let size = layout.size
self.containerNode.frame = CGRect(origin: CGPoint(), size: layout.size)
self.instantChatBackgroundNode.frame = CGRect(origin: CGPoint(), size: layout.size)
self.instantChatBackgroundNode.updateLayout(size: size, transition: .immediate)
self.messagesContainerNode.frame = CGRect(origin: CGPoint(), size: layout.size)
let addressLayout = self.addressNode.updateLayout(size)
let theme = self.presentationData.theme.withUpdated(preview: true)
let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.messages.first?.timestamp ?? 0, theme: theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder)
var items: [ListViewItem] = []
let sampleMessages: [Message] = self.messages
items = sampleMessages.reversed().map { message in
self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message, theme: theme, strings: self.presentationData.strings, wallpaper: self.presentationData.theme.chat.defaultWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)
}
let inset: CGFloat = 16.0
let width = layout.size.width - inset * 2.0
let params = ListViewItemLayoutParams(width: width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height)
if let messageNodes = self.messageNodes {
for i in 0 ..< items.count {
let itemNode = messageNodes[i]
items[i].updateNode(async: { $0() }, node: {
return itemNode
}, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None, completion: { (layout, apply) in
let nodeFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((size.height - layout.size.height) / 2.0)), size: CGSize(width: width, height: layout.size.height))
itemNode.contentSize = layout.contentSize
itemNode.insets = layout.insets
itemNode.frame = nodeFrame
itemNode.isUserInteractionEnabled = false
apply(ListViewItemApply(isOnScreen: true))
})
}
} else {
var messageNodes: [ListViewItemNode] = []
for i in 0 ..< items.count {
var itemNode: ListViewItemNode?
items[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: true, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], completion: { node, apply in
itemNode = node
apply().1(ListViewItemApply(isOnScreen: true))
})
itemNode!.subnodeTransform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
itemNode!.isUserInteractionEnabled = false
messageNodes.append(itemNode!)
self.messagesContainerNode.addSubnode(itemNode!)
}
self.messageNodes = messageNodes
}
var bottomOffset: CGFloat = 0.0
if let messageNodes = self.messageNodes {
for itemNode in messageNodes {
itemNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((size.height - itemNode.frame.height) / 2.0)), size: itemNode.frame.size)
bottomOffset += itemNode.frame.maxY
itemNode.updateFrame(itemNode.frame, within: layout.size)
}
}
self.addressNode.frame = CGRect(origin: CGPoint(x: inset + 16.0, y: bottomOffset + 3.0), size: CGSize(width: addressLayout.width, height: addressLayout.height + 3.0))
let dateHeaderNode: ListViewItemHeaderNode
if let currentDateHeaderNode = self.dateHeaderNode {
dateHeaderNode = currentDateHeaderNode
headerItem.updateNode(dateHeaderNode, previous: nil, next: headerItem)
} else {
dateHeaderNode = headerItem.node()
dateHeaderNode.subnodeTransform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
self.messagesContainerNode.addSubnode(dateHeaderNode)
self.dateHeaderNode = dateHeaderNode
}
dateHeaderNode.frame = CGRect(origin: CGPoint(x: 0.0, y: bottomOffset), size: CGSize(width: layout.size.width, height: headerItem.height))
dateHeaderNode.updateLayout(size: self.containerNode.frame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right)
}
}

View file

@ -348,6 +348,7 @@ private enum SharedDataKeyValues: Int32 {
case proxySettings = 4
case autodownloadSettings = 5
case themeSettings = 6
case countriesList = 7
}
public struct SharedDataKeys {
@ -386,6 +387,12 @@ public struct SharedDataKeys {
key.setInt32(0, value: SharedDataKeyValues.themeSettings.rawValue)
return key
}()
public static let countriesList: ValueBoxKey = {
let key = ValueBoxKey(length: 4)
key.setInt32(0, value: SharedDataKeyValues.countriesList.rawValue)
return key
}()
}
public func applicationSpecificItemCacheCollectionId(_ value: Int8) -> Int8 {

View file

@ -3,7 +3,7 @@ import Postbox
public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
case text
case url(String)
case callback(MemoryBuffer)
case callback(requiresPassword: Bool, data: MemoryBuffer)
case requestPhone
case requestMap
case switchInline(samePeer: Bool, query: String)
@ -19,7 +19,7 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
case 1:
self = .url(decoder.decodeStringForKey("u", orElse: ""))
case 2:
self = .callback(decoder.decodeBytesForKey("d") ?? MemoryBuffer())
self = .callback(requiresPassword: decoder.decodeInt32ForKey("p", orElse: 0) != 0, data: decoder.decodeBytesForKey("d") ?? MemoryBuffer())
case 3:
self = .requestPhone
case 4:
@ -46,8 +46,9 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
case let .url(url):
encoder.encodeInt32(1, forKey: "v")
encoder.encodeString(url, forKey: "u")
case let .callback(data):
case let .callback(requiresPassword, data):
encoder.encodeInt32(2, forKey: "v")
encoder.encodeInt32(requiresPassword ? 1 : 0, forKey: "p")
encoder.encodeBytes(data, forKey: "d")
case .requestPhone:
encoder.encodeInt32(3, forKey: "v")

View file

@ -12,79 +12,7 @@ import MediaResources
import MobileCoreServices
import MediaResources
import YuvConversion
let colorKeyRegex = try? NSRegularExpression(pattern: "\"k\":\\[[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\]")
private func transformedWithFitzModifier(data: Data, fitzModifier: EmojiFitzModifier?) -> Data {
if let fitzModifier = fitzModifier, var string = String(data: data, encoding: .utf8) {
var colors: [UIColor] = [0xf77e41, 0xffb139, 0xffd140, 0xffdf79].map { UIColor(rgb: $0) }
let replacementColors: [UIColor]
switch fitzModifier {
case .type12:
replacementColors = [0xca907a, 0xedc5a5, 0xf7e3c3, 0xfbefd6].map { UIColor(rgb: $0) }
case .type3:
replacementColors = [0xaa7c60, 0xc8a987, 0xddc89f, 0xe6d6b2].map { UIColor(rgb: $0) }
case .type4:
replacementColors = [0x8c6148, 0xad8562, 0xc49e76, 0xd4b188].map { UIColor(rgb: $0) }
case .type5:
replacementColors = [0x6e3c2c, 0x925a34, 0xa16e46, 0xac7a52].map { UIColor(rgb: $0) }
case .type6:
replacementColors = [0x291c12, 0x472a22, 0x573b30, 0x68493c].map { UIColor(rgb: $0) }
}
func colorToString(_ color: UIColor) -> String {
var r: CGFloat = 0.0
var g: CGFloat = 0.0
var b: CGFloat = 0.0
if color.getRed(&r, green: &g, blue: &b, alpha: nil) {
return "\"k\":[\(r),\(g),\(b),1]"
}
return ""
}
func match(_ a: Double, _ b: Double, eps: Double) -> Bool {
return abs(a - b) < eps
}
var replacements: [(NSTextCheckingResult, String)] = []
if let colorKeyRegex = colorKeyRegex {
let results = colorKeyRegex.matches(in: string, range: NSRange(string.startIndex..., in: string))
for result in results.reversed() {
if let range = Range(result.range, in: string) {
let substring = String(string[range])
let color = substring[substring.index(string.startIndex, offsetBy: "\"k\":[".count) ..< substring.index(before: substring.endIndex)]
let components = color.split(separator: ",")
if components.count == 4, let r = Double(components[0]), let g = Double(components[1]), let b = Double(components[2]), let a = Double(components[3]) {
if match(a, 1.0, eps: 0.01) {
for i in 0 ..< colors.count {
let color = colors[i]
var cr: CGFloat = 0.0
var cg: CGFloat = 0.0
var cb: CGFloat = 0.0
if color.getRed(&cr, green: &cg, blue: &cb, alpha: nil) {
if match(r, Double(cr), eps: 0.01) && match(g, Double(cg), eps: 0.01) && match(b, Double(cb), eps: 0.01) {
replacements.append((result, colorToString(replacementColors[i])))
}
}
}
}
}
}
}
}
for (result, text) in replacements {
if let range = Range(result.range, in: string) {
string = string.replacingCharacters(in: range, with: text)
}
}
return string.data(using: .utf8) ?? data
} else {
return data
}
}
import AnimatedStickerNode
public func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal<TempBoxFile, NoError> {
return Signal({ subscriber in

View file

@ -255,6 +255,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
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[1854571743] = { return Api.Update.parse_updateChannelMessageForwards($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) }
@ -273,7 +274,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-994444869] = { return Api.Error.parse_error($0) }
dict[-1560655744] = { return Api.KeyboardButton.parse_keyboardButton($0) }
dict[629866245] = { return Api.KeyboardButton.parse_keyboardButtonUrl($0) }
dict[1748655686] = { return Api.KeyboardButton.parse_keyboardButtonCallback($0) }
dict[-1318425559] = { return Api.KeyboardButton.parse_keyboardButtonRequestPhone($0) }
dict[-59151553] = { return Api.KeyboardButton.parse_keyboardButtonRequestGeoLocation($0) }
dict[90744648] = { return Api.KeyboardButton.parse_keyboardButtonSwitchInline($0) }
@ -282,6 +282,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[280464681] = { return Api.KeyboardButton.parse_keyboardButtonUrlAuth($0) }
dict[-802258988] = { return Api.KeyboardButton.parse_inputKeyboardButtonUrlAuth($0) }
dict[-1144565411] = { return Api.KeyboardButton.parse_keyboardButtonRequestPoll($0) }
dict[901503851] = { return Api.KeyboardButton.parse_keyboardButtonCallback($0) }
dict[-748155807] = { return Api.ContactStatus.parse_contactStatus($0) }
dict[1679398724] = { return Api.SecureFile.parse_secureFileEmpty($0) }
dict[-534283678] = { return Api.SecureFile.parse_secureFile($0) }
@ -387,6 +388,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1694474197] = { return Api.messages.Chats.parse_chats($0) }
dict[-1663561404] = { return Api.messages.Chats.parse_chatsSlice($0) }
dict[482797855] = { return Api.InputSingleMedia.parse_inputSingleMedia($0) }
dict[1831138451] = { return Api.MessageViews.parse_messageViews($0) }
dict[218751099] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowContacts($0) }
dict[407582158] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowAll($0) }
dict[320652927] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowUsers($0) }
@ -447,6 +449,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-524237339] = { return Api.PageTableRow.parse_pageTableRow($0) }
dict[-40996577] = { return Api.DraftMessage.parse_draftMessage($0) }
dict[453805082] = { return Api.DraftMessage.parse_draftMessageEmpty($0) }
dict[-1014526429] = { return Api.help.Country.parse_country($0) }
dict[418631927] = { return Api.StatsGroupTopPoster.parse_statsGroupTopPoster($0) }
dict[-2128640689] = { return Api.account.SentEmailCode.parse_sentEmailCode($0) }
dict[-1038136962] = { return Api.EncryptedFile.parse_encryptedFileEmpty($0) }
@ -519,6 +522,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-914167110] = { return Api.CdnPublicKey.parse_cdnPublicKey($0) }
dict[53231223] = { return Api.InputGame.parse_inputGameID($0) }
dict[-1020139510] = { return Api.InputGame.parse_inputGameShortName($0) }
dict[1107543535] = { return Api.help.CountryCode.parse_countryCode($0) }
dict[-1502174430] = { return Api.InputMessage.parse_inputMessageID($0) }
dict[-1160215659] = { return Api.InputMessage.parse_inputMessageReplyTo($0) }
dict[-2037963464] = { return Api.InputMessage.parse_inputMessagePinned($0) }
@ -598,7 +602,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1820043071] = { return Api.User.parse_user($0) }
dict[-2082087340] = { return Api.Message.parse_messageEmpty($0) }
dict[-1642487306] = { return Api.Message.parse_messageService($0) }
dict[1160515173] = { return Api.Message.parse_message($0) }
dict[-181507201] = { return Api.Message.parse_message($0) }
dict[831924812] = { return Api.StatsGroupTopInviter.parse_statsGroupTopInviter($0) }
dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) }
dict[586395571] = { return Api.messages.RecentStickers.parse_recentStickers($0) }
@ -683,6 +687,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[364538944] = { return Api.messages.Dialogs.parse_dialogs($0) }
dict[1910543603] = { return Api.messages.Dialogs.parse_dialogsSlice($0) }
dict[-253500010] = { return Api.messages.Dialogs.parse_dialogsNotModified($0) }
dict[-1986399595] = { return Api.stats.MessageStats.parse_messageStats($0) }
dict[-709641735] = { return Api.EmojiKeyword.parse_emojiKeyword($0) }
dict[594408994] = { return Api.EmojiKeyword.parse_emojiKeywordDeleted($0) }
dict[-290921362] = { return Api.upload.CdnFile.parse_cdnFileReuploadNeeded($0) }
@ -789,6 +794,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1041346555] = { return Api.updates.ChannelDifference.parse_channelDifferenceEmpty($0) }
dict[543450958] = { return Api.updates.ChannelDifference.parse_channelDifference($0) }
dict[-1531132162] = { return Api.updates.ChannelDifference.parse_channelDifferenceTooLong($0) }
dict[-1815339214] = { return Api.help.CountriesList.parse_countriesListNotModified($0) }
dict[-2016381538] = { return Api.help.CountriesList.parse_countriesList($0) }
dict[-309659827] = { return Api.channels.AdminLogResults.parse_adminLogResults($0) }
dict[-264117680] = { return Api.ChatOnlines.parse_chatOnlines($0) }
dict[488313413] = { return Api.InputAppEvent.parse_inputAppEvent($0) }
@ -1106,6 +1113,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.InputSingleMedia:
_1.serialize(buffer, boxed)
case let _1 as Api.MessageViews:
_1.serialize(buffer, boxed)
case let _1 as Api.InputPrivacyRule:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.DhConfig:
@ -1148,6 +1157,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.DraftMessage:
_1.serialize(buffer, boxed)
case let _1 as Api.help.Country:
_1.serialize(buffer, boxed)
case let _1 as Api.StatsGroupTopPoster:
_1.serialize(buffer, boxed)
case let _1 as Api.account.SentEmailCode:
@ -1226,6 +1237,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.InputGame:
_1.serialize(buffer, boxed)
case let _1 as Api.help.CountryCode:
_1.serialize(buffer, boxed)
case let _1 as Api.InputMessage:
_1.serialize(buffer, boxed)
case let _1 as Api.PhoneCallProtocol:
@ -1366,6 +1379,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.messages.Dialogs:
_1.serialize(buffer, boxed)
case let _1 as Api.stats.MessageStats:
_1.serialize(buffer, boxed)
case let _1 as Api.EmojiKeyword:
_1.serialize(buffer, boxed)
case let _1 as Api.upload.CdnFile:
@ -1442,6 +1457,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.updates.ChannelDifference:
_1.serialize(buffer, boxed)
case let _1 as Api.help.CountriesList:
_1.serialize(buffer, boxed)
case let _1 as Api.channels.AdminLogResults:
_1.serialize(buffer, boxed)
case let _1 as Api.ChatOnlines:

View file

@ -6038,6 +6038,7 @@ public extension Api {
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)
case updateChannelMessageForwards(channelId: Int32, id: Int32, forwards: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -6730,6 +6731,14 @@ public extension Api {
if Int(flags) & Int(1 << 1) != 0 {newParticipant!.serialize(buffer, true)}
serializeInt32(qts, buffer: buffer, boxed: false)
break
case .updateChannelMessageForwards(let channelId, let id, let forwards):
if boxed {
buffer.appendInt32(1854571743)
}
serializeInt32(channelId, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
serializeInt32(forwards, buffer: buffer, boxed: false)
break
}
}
@ -6899,6 +6908,8 @@ public extension Api {
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)])
case .updateChannelMessageForwards(let channelId, let id, let forwards):
return ("updateChannelMessageForwards", [("channelId", channelId), ("id", id), ("forwards", forwards)])
}
}
@ -8277,6 +8288,23 @@ public extension Api {
return nil
}
}
public static func parse_updateChannelMessageForwards(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.Update.updateChannelMessageForwards(channelId: _1!, id: _2!, forwards: _3!)
}
else {
return nil
}
}
}
public enum PopularContact: TypeConstructorDescription {
@ -8732,7 +8760,6 @@ public extension Api {
public enum KeyboardButton: TypeConstructorDescription {
case keyboardButton(text: String)
case keyboardButtonUrl(text: String, url: String)
case keyboardButtonCallback(text: String, data: Buffer)
case keyboardButtonRequestPhone(text: String)
case keyboardButtonRequestGeoLocation(text: String)
case keyboardButtonSwitchInline(flags: Int32, text: String, query: String)
@ -8741,6 +8768,7 @@ public extension Api {
case keyboardButtonUrlAuth(flags: Int32, text: String, fwdText: String?, url: String, buttonId: Int32)
case inputKeyboardButtonUrlAuth(flags: Int32, text: String, fwdText: String?, url: String, bot: Api.InputUser)
case keyboardButtonRequestPoll(flags: Int32, quiz: Api.Bool?, text: String)
case keyboardButtonCallback(flags: Int32, text: String, data: Buffer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -8757,13 +8785,6 @@ public extension Api {
serializeString(text, buffer: buffer, boxed: false)
serializeString(url, buffer: buffer, boxed: false)
break
case .keyboardButtonCallback(let text, let data):
if boxed {
buffer.appendInt32(1748655686)
}
serializeString(text, buffer: buffer, boxed: false)
serializeBytes(data, buffer: buffer, boxed: false)
break
case .keyboardButtonRequestPhone(let text):
if boxed {
buffer.appendInt32(-1318425559)
@ -8824,6 +8845,14 @@ public extension Api {
if Int(flags) & Int(1 << 0) != 0 {quiz!.serialize(buffer, true)}
serializeString(text, buffer: buffer, boxed: false)
break
case .keyboardButtonCallback(let flags, let text, let data):
if boxed {
buffer.appendInt32(901503851)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(text, buffer: buffer, boxed: false)
serializeBytes(data, buffer: buffer, boxed: false)
break
}
}
@ -8833,8 +8862,6 @@ public extension Api {
return ("keyboardButton", [("text", text)])
case .keyboardButtonUrl(let text, let url):
return ("keyboardButtonUrl", [("text", text), ("url", url)])
case .keyboardButtonCallback(let text, let data):
return ("keyboardButtonCallback", [("text", text), ("data", data)])
case .keyboardButtonRequestPhone(let text):
return ("keyboardButtonRequestPhone", [("text", text)])
case .keyboardButtonRequestGeoLocation(let text):
@ -8851,6 +8878,8 @@ public extension Api {
return ("inputKeyboardButtonUrlAuth", [("flags", flags), ("text", text), ("fwdText", fwdText), ("url", url), ("bot", bot)])
case .keyboardButtonRequestPoll(let flags, let quiz, let text):
return ("keyboardButtonRequestPoll", [("flags", flags), ("quiz", quiz), ("text", text)])
case .keyboardButtonCallback(let flags, let text, let data):
return ("keyboardButtonCallback", [("flags", flags), ("text", text), ("data", data)])
}
}
@ -8879,20 +8908,6 @@ public extension Api {
return nil
}
}
public static func parse_keyboardButtonCallback(_ reader: BufferReader) -> KeyboardButton? {
var _1: String?
_1 = parseString(reader)
var _2: Buffer?
_2 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.KeyboardButton.keyboardButtonCallback(text: _1!, data: _2!)
}
else {
return nil
}
}
public static func parse_keyboardButtonRequestPhone(_ reader: BufferReader) -> KeyboardButton? {
var _1: String?
_1 = parseString(reader)
@ -9021,6 +9036,23 @@ public extension Api {
return nil
}
}
public static func parse_keyboardButtonCallback(_ reader: BufferReader) -> KeyboardButton? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: Buffer?
_3 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.KeyboardButton.keyboardButtonCallback(flags: _1!, text: _2!, data: _3!)
}
else {
return nil
}
}
}
public enum ContactStatus: TypeConstructorDescription {
@ -11718,6 +11750,44 @@ public extension Api {
}
}
}
public enum MessageViews: TypeConstructorDescription {
case messageViews(views: Int32, forwards: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .messageViews(let views, let forwards):
if boxed {
buffer.appendInt32(1831138451)
}
serializeInt32(views, buffer: buffer, boxed: false)
serializeInt32(forwards, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .messageViews(let views, let forwards):
return ("messageViews", [("views", views), ("forwards", forwards)])
}
}
public static func parse_messageViews(_ reader: BufferReader) -> MessageViews? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.MessageViews.messageViews(views: _1!, forwards: _2!)
}
else {
return nil
}
}
}
public enum InputPrivacyRule: TypeConstructorDescription {
case inputPrivacyValueAllowContacts
@ -17092,7 +17162,7 @@ public extension Api {
public enum Message: TypeConstructorDescription {
case messageEmpty(id: Int32)
case messageService(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, replyToMsgId: Int32?, date: Int32, action: Api.MessageAction)
case message(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?)
case message(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -17114,9 +17184,9 @@ public extension Api {
serializeInt32(date, buffer: buffer, boxed: false)
action.serialize(buffer, true)
break
case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let editDate, let postAuthor, let groupedId, let restrictionReason):
case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let editDate, let postAuthor, let groupedId, let restrictionReason):
if boxed {
buffer.appendInt32(1160515173)
buffer.appendInt32(-181507201)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
@ -17135,6 +17205,7 @@ public extension Api {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 10) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 10) != 0 {serializeInt32(forwards!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 15) != 0 {serializeInt32(editDate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 16) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 17) != 0 {serializeInt64(groupedId!, buffer: buffer, boxed: false)}
@ -17153,8 +17224,8 @@ public extension Api {
return ("messageEmpty", [("id", id)])
case .messageService(let flags, let id, let fromId, let toId, let replyToMsgId, let date, let action):
return ("messageService", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("replyToMsgId", replyToMsgId), ("date", date), ("action", action)])
case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let editDate, let postAuthor, let groupedId, let restrictionReason):
return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("restrictionReason", restrictionReason)])
case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let editDate, let postAuthor, let groupedId, let restrictionReason):
return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("forwards", forwards), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("restrictionReason", restrictionReason)])
}
}
@ -17240,14 +17311,16 @@ public extension Api {
var _13: Int32?
if Int(_1!) & Int(1 << 10) != 0 {_13 = reader.readInt32() }
var _14: Int32?
if Int(_1!) & Int(1 << 15) != 0 {_14 = reader.readInt32() }
var _15: String?
if Int(_1!) & Int(1 << 16) != 0 {_15 = parseString(reader) }
var _16: Int64?
if Int(_1!) & Int(1 << 17) != 0 {_16 = reader.readInt64() }
var _17: [Api.RestrictionReason]?
if Int(_1!) & Int(1 << 10) != 0 {_14 = reader.readInt32() }
var _15: Int32?
if Int(_1!) & Int(1 << 15) != 0 {_15 = reader.readInt32() }
var _16: String?
if Int(_1!) & Int(1 << 16) != 0 {_16 = parseString(reader) }
var _17: Int64?
if Int(_1!) & Int(1 << 17) != 0 {_17 = reader.readInt64() }
var _18: [Api.RestrictionReason]?
if Int(_1!) & Int(1 << 22) != 0 {if let _ = reader.readInt32() {
_17 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self)
_18 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self)
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
@ -17262,12 +17335,13 @@ public extension Api {
let _c11 = (Int(_1!) & Int(1 << 6) == 0) || _11 != nil
let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil
let _c13 = (Int(_1!) & Int(1 << 10) == 0) || _13 != nil
let _c14 = (Int(_1!) & Int(1 << 15) == 0) || _14 != nil
let _c15 = (Int(_1!) & Int(1 << 16) == 0) || _15 != nil
let _c16 = (Int(_1!) & Int(1 << 17) == 0) || _16 != nil
let _c17 = (Int(_1!) & Int(1 << 22) == 0) || _17 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 {
return Api.Message.message(flags: _1!, id: _2!, fromId: _3, toId: _4!, fwdFrom: _5, viaBotId: _6, replyToMsgId: _7, date: _8!, message: _9!, media: _10, replyMarkup: _11, entities: _12, views: _13, editDate: _14, postAuthor: _15, groupedId: _16, restrictionReason: _17)
let _c14 = (Int(_1!) & Int(1 << 10) == 0) || _14 != nil
let _c15 = (Int(_1!) & Int(1 << 15) == 0) || _15 != nil
let _c16 = (Int(_1!) & Int(1 << 16) == 0) || _16 != nil
let _c17 = (Int(_1!) & Int(1 << 17) == 0) || _17 != nil
let _c18 = (Int(_1!) & Int(1 << 22) == 0) || _18 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 {
return Api.Message.message(flags: _1!, id: _2!, fromId: _3, toId: _4!, fwdFrom: _5, viaBotId: _6, replyToMsgId: _7, date: _8!, message: _9!, media: _10, replyMarkup: _11, entities: _12, views: _13, forwards: _14, editDate: _15, postAuthor: _16, groupedId: _17, restrictionReason: _18)
}
else {
return nil

View file

@ -810,6 +810,42 @@ public struct stats {
}
}
public enum MessageStats: TypeConstructorDescription {
case messageStats(viewsGraph: Api.StatsGraph)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .messageStats(let viewsGraph):
if boxed {
buffer.appendInt32(-1986399595)
}
viewsGraph.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .messageStats(let viewsGraph):
return ("messageStats", [("viewsGraph", viewsGraph)])
}
}
public static func parse_messageStats(_ reader: BufferReader) -> MessageStats? {
var _1: Api.StatsGraph?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.StatsGraph
}
let _c1 = _1 != nil
if _c1 {
return Api.stats.MessageStats.messageStats(viewsGraph: _1!)
}
else {
return nil
}
}
}
}
}
public extension Api {
@ -1965,6 +2001,62 @@ public struct help {
}
}
}
public enum Country: TypeConstructorDescription {
case country(flags: Int32, iso2: String, defaultName: String, name: String?, countryCodes: [Api.help.CountryCode])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .country(let flags, let iso2, let defaultName, let name, let countryCodes):
if boxed {
buffer.appendInt32(-1014526429)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(iso2, buffer: buffer, boxed: false)
serializeString(defaultName, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeString(name!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(countryCodes.count))
for item in countryCodes {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .country(let flags, let iso2, let defaultName, let name, let countryCodes):
return ("country", [("flags", flags), ("iso2", iso2), ("defaultName", defaultName), ("name", name), ("countryCodes", countryCodes)])
}
}
public static func parse_country(_ reader: BufferReader) -> Country? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: String?
_3 = parseString(reader)
var _4: String?
if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) }
var _5: [Api.help.CountryCode]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.CountryCode.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.help.Country.country(flags: _1!, iso2: _2!, defaultName: _3!, name: _4, countryCodes: _5!)
}
else {
return nil
}
}
}
public enum PromoData: TypeConstructorDescription {
case promoDataEmpty(expires: Int32)
@ -2117,6 +2209,64 @@ public struct help {
}
}
}
public enum CountryCode: TypeConstructorDescription {
case countryCode(flags: Int32, countryCode: String, prefixes: [String]?, patterns: [String]?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .countryCode(let flags, let countryCode, let prefixes, let patterns):
if boxed {
buffer.appendInt32(1107543535)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(countryCode, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(prefixes!.count))
for item in prefixes! {
serializeString(item, buffer: buffer, boxed: false)
}}
if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(patterns!.count))
for item in patterns! {
serializeString(item, buffer: buffer, boxed: false)
}}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .countryCode(let flags, let countryCode, let prefixes, let patterns):
return ("countryCode", [("flags", flags), ("countryCode", countryCode), ("prefixes", prefixes), ("patterns", patterns)])
}
}
public static func parse_countryCode(_ reader: BufferReader) -> CountryCode? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: [String]?
if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self)
} }
var _4: [String]?
if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self)
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.help.CountryCode.countryCode(flags: _1!, countryCode: _2!, prefixes: _3, patterns: _4)
}
else {
return nil
}
}
}
public enum Support: TypeConstructorDescription {
case support(phoneNumber: String, user: Api.User)
@ -2316,5 +2466,61 @@ public struct help {
}
}
public enum CountriesList: TypeConstructorDescription {
case countriesListNotModified
case countriesList(countries: [Api.help.Country], hash: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .countriesListNotModified:
if boxed {
buffer.appendInt32(-1815339214)
}
break
case .countriesList(let countries, let hash):
if boxed {
buffer.appendInt32(-2016381538)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(countries.count))
for item in countries {
item.serialize(buffer, true)
}
serializeInt32(hash, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .countriesListNotModified:
return ("countriesListNotModified", [])
case .countriesList(let countries, let hash):
return ("countriesList", [("countries", countries), ("hash", hash)])
}
}
public static func parse_countriesListNotModified(_ reader: BufferReader) -> CountriesList? {
return Api.help.CountriesList.countriesListNotModified
}
public static func parse_countriesList(_ reader: BufferReader) -> CountriesList? {
var _1: [Api.help.Country]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.Country.self)
}
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.help.CountriesList.countriesList(countries: _1!, hash: _2!)
}
else {
return nil
}
}
}
}
}

View file

@ -2243,26 +2243,6 @@ public extension Api {
})
}
public static func getMessagesViews(peer: Api.InputPeer, id: [Int32], increment: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) {
let buffer = Buffer()
buffer.appendInt32(-993483427)
peer.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(id.count))
for item in id {
serializeInt32(item, buffer: buffer, boxed: false)
}
increment.serialize(buffer, true)
return (FunctionDescription(name: "messages.getMessagesViews", parameters: [("peer", peer), ("id", id), ("increment", increment)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in
let reader = BufferReader(buffer)
var result: [Int32]?
if let _ = reader.readInt32() {
result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
}
return result
})
}
public static func editChatAdmin(chatId: Int32, userId: Api.InputUser, isAdmin: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-1444503762)
@ -2429,23 +2409,6 @@ public extension Api {
})
}
public static func getBotCallbackAnswer(flags: Int32, peer: Api.InputPeer, msgId: Int32, data: Buffer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.BotCallbackAnswer>) {
let buffer = Buffer()
buffer.appendInt32(-2130010132)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeBytes(data!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "messages.getBotCallbackAnswer", parameters: [("flags", flags), ("peer", peer), ("msgId", msgId), ("data", data)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotCallbackAnswer? in
let reader = BufferReader(buffer)
var result: Api.messages.BotCallbackAnswer?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.BotCallbackAnswer
}
return result
})
}
public static func setBotCallbackAnswer(flags: Int32, queryId: Int64, message: String?, url: String?, cacheTime: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-712043766)
@ -3385,26 +3348,6 @@ public extension Api {
})
}
public static func searchGlobal(flags: Int32, folderId: Int32?, q: String, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
let buffer = Buffer()
buffer.appendInt32(-1083038300)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)}
serializeString(q, buffer: buffer, boxed: false)
serializeInt32(offsetRate, buffer: buffer, boxed: false)
offsetPeer.serialize(buffer, true)
serializeInt32(offsetId, buffer: buffer, boxed: false)
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.searchGlobal", parameters: [("flags", flags), ("folderId", folderId), ("q", q), ("offsetRate", offsetRate), ("offsetPeer", offsetPeer), ("offsetId", offsetId), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in
let reader = BufferReader(buffer)
var result: Api.messages.Messages?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.Messages
}
return result
})
}
public static func sendMessage(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(1376532592)
@ -3718,6 +3661,65 @@ public extension Api {
return result
})
}
public static func getBotCallbackAnswer(flags: Int32, peer: Api.InputPeer, msgId: Int32, data: Buffer?, password: Api.InputCheckPasswordSRP?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.BotCallbackAnswer>) {
let buffer = Buffer()
buffer.appendInt32(-1824339449)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeBytes(data!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {password!.serialize(buffer, true)}
return (FunctionDescription(name: "messages.getBotCallbackAnswer", parameters: [("flags", flags), ("peer", peer), ("msgId", msgId), ("data", data), ("password", password)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotCallbackAnswer? in
let reader = BufferReader(buffer)
var result: Api.messages.BotCallbackAnswer?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.BotCallbackAnswer
}
return result
})
}
public static func getMessagesViews(peer: Api.InputPeer, id: [Int32], increment: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.MessageViews]>) {
let buffer = Buffer()
buffer.appendInt32(-39035462)
peer.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(id.count))
for item in id {
serializeInt32(item, buffer: buffer, boxed: false)
}
increment.serialize(buffer, true)
return (FunctionDescription(name: "messages.getMessagesViews", parameters: [("peer", peer), ("id", id), ("increment", increment)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.MessageViews]? in
let reader = BufferReader(buffer)
var result: [Api.MessageViews]?
if let _ = reader.readInt32() {
result = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageViews.self)
}
return result
})
}
public static func searchGlobal(flags: Int32, folderId: Int32?, q: String, filter: Api.MessagesFilter, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
let buffer = Buffer()
buffer.appendInt32(1934479725)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)}
serializeString(q, buffer: buffer, boxed: false)
filter.serialize(buffer, true)
serializeInt32(offsetRate, buffer: buffer, boxed: false)
offsetPeer.serialize(buffer, true)
serializeInt32(offsetId, buffer: buffer, boxed: false)
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.searchGlobal", parameters: [("flags", flags), ("folderId", folderId), ("q", q), ("filter", filter), ("offsetRate", offsetRate), ("offsetPeer", offsetPeer), ("offsetId", offsetId), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in
let reader = BufferReader(buffer)
var result: Api.messages.Messages?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.Messages
}
return result
})
}
}
public struct channels {
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
@ -4450,6 +4452,41 @@ public extension Api {
return result
})
}
public static func getMessagePublicForwards(channel: Api.InputChannel, msgId: Int32, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
let buffer = Buffer()
buffer.appendInt32(1445996571)
channel.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
serializeInt32(offsetRate, buffer: buffer, boxed: false)
offsetPeer.serialize(buffer, true)
serializeInt32(offsetId, buffer: buffer, boxed: false)
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "stats.getMessagePublicForwards", parameters: [("channel", channel), ("msgId", msgId), ("offsetRate", offsetRate), ("offsetPeer", offsetPeer), ("offsetId", offsetId), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in
let reader = BufferReader(buffer)
var result: Api.messages.Messages?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.Messages
}
return result
})
}
public static func getMessageStats(flags: Int32, channel: Api.InputChannel, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stats.MessageStats>) {
let buffer = Buffer()
buffer.appendInt32(-1226791947)
serializeInt32(flags, buffer: buffer, boxed: false)
channel.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
return (FunctionDescription(name: "stats.getMessageStats", parameters: [("flags", flags), ("channel", channel), ("msgId", msgId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.MessageStats? in
let reader = BufferReader(buffer)
var result: Api.stats.MessageStats?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.stats.MessageStats
}
return result
})
}
}
public struct auth {
public static func checkPhone(phoneNumber: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.CheckedPhone>) {
@ -5369,34 +5406,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 editUserInfo(userId: Api.InputUser, message: String, entities: [Api.MessageEntity]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.help.UserInfo>) {
let buffer = Buffer()
buffer.appendInt32(1723407216)
@ -5431,6 +5440,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 dismissSuggestion(suggestion: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(125807007)
@ -5444,6 +5481,21 @@ public extension Api {
return result
})
}
public static func getCountriesList(langCode: String, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.help.CountriesList>) {
let buffer = Buffer()
buffer.appendInt32(1935116200)
serializeString(langCode, buffer: buffer, boxed: false)
serializeInt32(hash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "help.getCountriesList", parameters: [("langCode", langCode), ("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.CountriesList? in
let reader = BufferReader(buffer)
var result: Api.help.CountriesList?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.help.CountriesList
}
return result
})
}
}
public struct updates {
public static func getState() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.updates.State>) {
@ -6802,11 +6854,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() {
@ -6816,14 +6871,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() {

View file

@ -61,9 +61,11 @@ private final class CallVideoNode: ASDisplayNode {
super.init()
self.backgroundColor = .black
self.clipsToBounds = true
if #available(iOS 13.0, *) {
self.layer.cornerCurve = .continuous
self.videoTransformContainer.layer.cornerCurve = .continuous
}
self.videoTransformContainer.view.addSubview(self.videoView.view)
@ -162,25 +164,41 @@ private final class CallVideoNode: ASDisplayNode {
})
}
func updateLayout(size: CGSize, cornerRadius: CGFloat, transition: ContainedViewLayoutTransition) {
func updateLayout(size: CGSize, cornerRadius: CGFloat, deviceOrientation: UIDeviceOrientation, transition: ContainedViewLayoutTransition) {
self.currentCornerRadius = cornerRadius
var rotationAngle: CGFloat
var rotateFrame: Bool
switch self.currentOrientation {
case .rotation0:
rotationAngle = 0.0
rotateFrame = false
case .rotation90:
rotationAngle = -CGFloat.pi / 2.0
rotateFrame = true
case .rotation180:
rotationAngle = -CGFloat.pi
rotateFrame = false
case .rotation270:
rotationAngle = -CGFloat.pi * 3.0 / 2.0
rotateFrame = true
rotationAngle = CGFloat.pi / 2.0
}
var additionalAngle: CGFloat = 0.0
switch deviceOrientation {
case .portrait:
additionalAngle = 0.0
case .landscapeLeft:
additionalAngle = CGFloat.pi / 2.0
case .landscapeRight:
additionalAngle = -CGFloat.pi / 2.0
case .portraitUpsideDown:
rotationAngle = -CGFloat.pi
default:
additionalAngle = 0.0
}
rotationAngle += additionalAngle
if abs(rotationAngle - (-CGFloat.pi)) < 1.0 {
rotationAngle = -CGFloat.pi + 0.001
}
var rotateFrame = abs(rotationAngle.remainder(dividingBy: CGFloat.pi)) > 1.0
var originalRotateFrame = rotateFrame
if size.width > size.height {
rotateFrame = !rotateFrame
@ -233,11 +251,6 @@ private final class CallVideoNode: ASDisplayNode {
transition.updateFrame(view: effectView, frame: videoFrame)
}
transition.updateCornerRadius(layer: self.videoTransformContainer.layer, cornerRadius: self.currentCornerRadius)
if let effectView = self.effectView {
transition.updateCornerRadius(layer: effectView.layer, cornerRadius: self.currentCornerRadius)
}
transition.updateCornerRadius(layer: self.layer, cornerRadius: self.currentCornerRadius)
}
@ -250,8 +263,6 @@ private final class CallVideoNode: ASDisplayNode {
if isBlurred {
if self.effectView == nil {
let effectView = UIVisualEffectView()
effectView.clipsToBounds = true
effectView.layer.cornerRadius = self.currentCornerRadius
self.effectView = effectView
effectView.frame = self.videoTransformContainer.bounds
self.videoTransformContainer.view.addSubview(effectView)
@ -358,6 +369,8 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
private var validLayout: (ContainerViewLayout, CGFloat)?
private var disableActionsUntilTimestamp: Double = 0.0
private var displayedVersionOutdatedAlert: Bool = false
var isMuted: Bool = false {
didSet {
self.buttonsNode.isMuted = self.isMuted
@ -403,6 +416,9 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
private var pictureInPictureCorner: VideoNodeCorner = .topRight
private var pictureInPictureTransitionFraction: CGFloat = 0.0
private var deviceOrientation: UIDeviceOrientation = .portrait
private var orientationDidChangeObserver: NSObjectProtocol?
init(sharedContext: SharedAccountContext, account: Account, presentationData: PresentationData, statusBar: StatusBar, debugInfo: Signal<(String, String), NoError>, shouldStayHiddenUntilConnection: Bool = false, easyDebugAccess: Bool, call: PresentationCall) {
self.sharedContext = sharedContext
self.account = account
@ -417,9 +433,6 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
self.containerTransformationNode.clipsToBounds = true
self.containerNode = ASDisplayNode()
if self.shouldStayHiddenUntilConnection {
self.containerNode.alpha = 0.0
}
self.imageNode = TransformImageNode()
self.imageNode.contentAnimations = [.subsequentUpdates]
@ -564,13 +577,38 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
self.backButtonNode.addTarget(self, action: #selector(self.backPressed), forControlEvents: .touchUpInside)
if !shouldStayHiddenUntilConnection && call.isVideo && call.isOutgoing {
if shouldStayHiddenUntilConnection {
self.containerNode.alpha = 0.0
Queue.mainQueue().after(3.0, { [weak self] in
self?.containerNode.alpha = 1.0
self?.animateIn()
})
} else if call.isVideo && call.isOutgoing {
self.containerNode.alpha = 0.0
Queue.mainQueue().after(1.0, { [weak self] in
self?.containerNode.alpha = 1.0
self?.animateIn()
})
}
self.orientationDidChangeObserver = NotificationCenter.default.addObserver(forName: UIDevice.orientationDidChangeNotification, object: nil, queue: nil, using: { [weak self] _ in
guard let strongSelf = self else {
return
}
let deviceOrientation = UIDevice.current.orientation
if strongSelf.deviceOrientation != deviceOrientation {
strongSelf.deviceOrientation = deviceOrientation
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
})
}
deinit {
if let orientationDidChangeObserver = self.orientationDidChangeObserver {
NotificationCenter.default.removeObserver(orientationDidChangeObserver)
}
}
func displayCameraTooltip() {
@ -881,8 +919,20 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
case .hungUp, .missed:
statusValue = .text(string: self.presentationData.strings.Call_StatusEnded, displayLogo: false)
}
case .error:
statusValue = .text(string: self.presentationData.strings.Call_StatusFailed, displayLogo: false)
case let .error(error):
let text = self.presentationData.strings.Call_StatusFailed
switch error {
case .notSupportedByPeer:
if !self.displayedVersionOutdatedAlert, let peer = self.peer {
self.displayedVersionOutdatedAlert = true
self.present?(textAlertController(sharedContext: self.sharedContext, title: nil, text: self.presentationData.strings.Call_ParticipantVersionOutdatedError(peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)).0, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {
})]))
}
default:
break
}
statusValue = .text(string: text, displayLogo: false)
}
} else {
statusValue = .text(string: self.presentationData.strings.Call_StatusEnded, displayLogo: false)
@ -1202,6 +1252,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
let previewVideoSide = interpolate(from: 350.0, to: 200.0, value: 1.0 - self.pictureInPictureTransitionFraction)
var previewVideoSize = layout.size.aspectFitted(CGSize(width: previewVideoSide, height: previewVideoSide))
previewVideoSize = CGSize(width: 30.0, height: 45.0).aspectFitted(previewVideoSize)
if let minimizedVideoNode = minimizedVideoNode {
switch minimizedVideoNode.currentOrientation {
case .rotation90, .rotation270:
@ -1254,6 +1305,10 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationBarHeight)
if !self.hasVideoNodes {
self.isUIHidden = false
}
var isUIHidden = self.isUIHidden
switch self.callState?.state {
case .terminated, .terminating:
@ -1377,6 +1432,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
}
if let expandedVideoNode = self.expandedVideoNode {
transition.updateAlpha(node: expandedVideoNode, alpha: 1.0)
var expandedVideoTransition = transition
if expandedVideoNode.frame.isEmpty || self.disableAnimationForExpandedVideoOnce {
expandedVideoTransition = .immediate
@ -1393,7 +1449,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
expandedVideoTransition.updateFrame(node: expandedVideoNode, frame: fullscreenVideoFrame)
}
expandedVideoNode.updateLayout(size: expandedVideoNode.frame.size, cornerRadius: 0.0, transition: expandedVideoTransition)
expandedVideoNode.updateLayout(size: expandedVideoNode.frame.size, cornerRadius: 0.0, deviceOrientation: self.deviceOrientation, transition: expandedVideoTransition)
if self.animateRequestedVideoOnce {
self.animateRequestedVideoOnce = false
@ -1424,6 +1480,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
if let minimizedVideoNode = self.minimizedVideoNode {
transition.updateAlpha(node: minimizedVideoNode, alpha: pipTransitionAlpha)
var minimizedVideoTransition = transition
var didAppear = false
if minimizedVideoNode.frame.isEmpty {
@ -1442,7 +1499,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
self.animationForExpandedVideoSnapshotView = nil
}
minimizedVideoTransition.updateFrame(node: minimizedVideoNode, frame: previewVideoFrame)
minimizedVideoNode.updateLayout(size: previewVideoFrame.size, cornerRadius: interpolate(from: 14.0, to: 24.0, value: self.pictureInPictureTransitionFraction), transition: minimizedVideoTransition)
minimizedVideoNode.updateLayout(size: previewVideoFrame.size, cornerRadius: interpolate(from: 14.0, to: 24.0, value: self.pictureInPictureTransitionFraction), deviceOrientation: .portrait, 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)
}
@ -1523,9 +1580,9 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
} else if let _ = self.keyPreviewNode {
self.backPressed()
} else {
if let expandedVideoNode = self.expandedVideoNode, let minimizedVideoNode = self.minimizedVideoNode {
if self.hasVideoNodes {
let point = recognizer.location(in: recognizer.view)
if minimizedVideoNode.frame.contains(point) {
if let expandedVideoNode = self.expandedVideoNode, let minimizedVideoNode = self.minimizedVideoNode, minimizedVideoNode.frame.contains(point) {
if !self.areUserActionsDisabledNow() {
let copyView = minimizedVideoNode.view.snapshotView(afterScreenUpdates: false)
copyView?.frame = minimizedVideoNode.frame

View file

@ -45,7 +45,7 @@ private func generateEmptyButtonImage(icon: UIImage?, strokeColor: UIColor?, fil
context.fill(imageRect)
} else {
context.setBlendMode(.normal)
context.draw(icon.cgImage!, in: imageRect)
context.draw(generateTintedImage(image: icon, color: .white)!.cgImage!, in: imageRect)
}
}
})

View file

@ -169,7 +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 preferredVideoCodec: String?
public let peer: Peer?
private let serializedData: String?
@ -265,7 +265,7 @@ public final class PresentationCallImpl: PresentationCall {
updatedNetworkType: Signal<NetworkType, NoError>,
startWithVideo: Bool,
isVideoPossible: Bool,
enableHighBitrateVideoCalls: Bool
preferredVideoCodec: String?
) {
self.account = account
self.audioSession = audioSession
@ -292,9 +292,9 @@ public final class PresentationCallImpl: PresentationCall {
self.isOutgoing = isOutgoing
self.isVideo = initialState?.type == .video
self.isVideoPossible = isVideoPossible
self.preferredVideoCodec = preferredVideoCodec
self.peer = peer
self.isVideo = startWithVideo
self.enableHighBitrateVideoCalls = enableHighBitrateVideoCalls
if self.isVideo {
self.videoCapturer = OngoingCallVideoCapturer()
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .active, remoteVideoState: .inactive, remoteAudioState: .active, remoteBatteryLevel: .normal))
@ -508,7 +508,7 @@ public final class PresentationCallImpl: PresentationCall {
} else {
if self.isVideo {
mappedVideoState = .active
} else if self.isVideoPossible {
} else if self.isVideoPossible && sessionState.isVideoPossible {
mappedVideoState = .inactive
} else {
mappedVideoState = .notAvailable
@ -557,8 +557,8 @@ public final class PresentationCallImpl: PresentationCall {
case .accepting:
self.callWasActive = true
presentationState = PresentationCallState(state: .connecting(nil), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
case .dropping:
presentationState = PresentationCallState(state: .terminating, videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
case let .dropping(reason):
presentationState = PresentationCallState(state: .terminating(reason), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
case let .terminated(id, reason, options):
presentationState = PresentationCallState(state: .terminated(id, reason, self.callWasActive && (options.contains(.reportRating) || self.shouldPresentCallRating)), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
case let .requesting(ringing):
@ -570,7 +570,7 @@ public final class PresentationCallImpl: PresentationCall {
case .initializing:
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
case .failed:
presentationState = nil
presentationState = PresentationCallState(state: .terminating(.error(.disconnected)), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil))
case .connected:
let timestamp: Double
@ -606,7 +606,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, 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)
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, audioSessionActive: self.audioSessionActive.get(), logName: logName, preferredVideoCodec: self.preferredVideoCodec)
self.ongoingContext = ongoingContext
ongoingContext.setIsMuted(self.isMutedValue)

View file

@ -314,7 +314,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
updatedNetworkType: firstState.0.networkType,
startWithVideo: firstState.2.isVideo,
isVideoPossible: firstState.2.isVideoPossible,
enableHighBitrateVideoCalls: experimentalSettings.enableHighBitrateVideoCalls
preferredVideoCodec: experimentalSettings.preferredVideoCodec
)
strongSelf.updateCurrentCall(call)
strongSelf.currentCallPromise.set(.single(call))
@ -333,7 +333,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
} else {
for (account, _, state, _, _) in ringingStates {
if state.id != self.currentCall?.internalId {
account.callSessionManager.drop(internalId: state.id, reason: .missed, debugLog: .single(nil))
account.callSessionManager.drop(internalId: state.id, reason: .busy, debugLog: .single(nil))
}
}
}
@ -556,7 +556,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
updatedNetworkType: account.networkType,
startWithVideo: isVideo,
isVideoPossible: isVideoPossible,
enableHighBitrateVideoCalls: experimentalSettings.enableHighBitrateVideoCalls
preferredVideoCodec: experimentalSettings.preferredVideoCodec
)
strongSelf.updateCurrentCall(call)
strongSelf.currentCallPromise.set(.single(call))

View file

@ -163,6 +163,8 @@ private var declaredEncodables: Void = {
declareEncodable(CachedChatContextResult.self, f: { CachedChatContextResult(decoder: $0) })
declareEncodable(PeerAccessRestrictionInfo.self, f: { PeerAccessRestrictionInfo(decoder: $0) })
declareEncodable(TelegramMediaImage.VideoRepresentation.self, f: { TelegramMediaImage.VideoRepresentation(decoder: $0) })
declareEncodable(Country.self, f: { Country(decoder: $0) })
declareEncodable(Country.CountryCode.self, f: { Country.CountryCode(decoder: $0) })
return
}()

View file

@ -591,7 +591,7 @@ public final class AccountViewTracker {
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.messages.getMessagesViews(peer: inputPeer, id: messageIds.map { $0.id }, increment: .boolTrue))
|> map(Optional.init)
|> `catch` { _ -> Signal<[Int32]?, NoError> in
|> `catch` { _ -> Signal<[Api.MessageViews]?, NoError> in
return .single(nil)
}
|> mapToSignal { viewCounts -> Signal<Void, NoError> in
@ -599,20 +599,7 @@ public final class AccountViewTracker {
return account.postbox.transaction { transaction -> Void in
for i in 0 ..< messageIds.count {
if i < viewCounts.count {
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)))
}
}
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] {
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
@ -626,7 +613,7 @@ public final class AccountViewTracker {
}
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

@ -97,7 +97,7 @@ enum CallSessionInternalState {
case requested(id: Int64, accessHash: Int64, a: Data, gA: Data, config: SecretChatEncryptionConfig, remoteConfirmationTimestamp: Int32?)
case confirming(id: Int64, accessHash: Int64, key: Data, keyId: Int64, keyVisualHash: Data, disposable: Disposable)
case active(id: Int64, accessHash: Int64, beginTimestamp: Int32, key: Data, keyId: Int64, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, allowsP2P: Bool)
case dropping(Disposable)
case dropping(reason: CallSessionTerminationReason, disposable: Disposable)
case terminated(id: Int64?, accessHash: Int64?, reason: CallSessionTerminationReason, reportRating: Bool, sendDebugLogs: Bool)
}
@ -138,7 +138,7 @@ public enum CallSessionState {
case accepting
case requesting(ringing: Bool)
case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, allowsP2P: Bool)
case dropping
case dropping(reason: CallSessionTerminationReason)
case terminated(id: CallId?, reason: CallSessionTerminationReason, options: CallTerminationOptions)
fileprivate init(_ context: CallSessionContext) {
@ -155,8 +155,8 @@ public enum CallSessionState {
self = .requesting(ringing: remoteConfirmationTimestamp != nil)
case let .active(id, accessHash, _, key, _, keyVisualHash, connections, maxLayer, version, allowsP2P):
self = .active(id: CallId(id: id, accessHash: accessHash), key: key, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, allowsP2P: allowsP2P)
case .dropping:
self = .dropping
case let .dropping(reason, _):
self = .dropping(reason: reason)
case let .terminated(id, accessHash, reason, reportRating, sendDebugLogs):
var options = CallTerminationOptions()
if reportRating {
@ -186,6 +186,7 @@ public struct CallSession {
public let isOutgoing: Bool
public let type: CallType
public let state: CallSessionState
public let isVideoPossible: Bool
}
public enum CallSessionConnection: Equatable {
@ -277,7 +278,7 @@ private final class CallSessionContext {
let peerId: PeerId
let isOutgoing: Bool
var type: CallSession.CallType
let isVideoPossible: Bool
var isVideoPossible: Bool
var state: CallSessionInternalState
let subscribers = Bag<(CallSession) -> Void>()
let signalingSubscribers = Bag<(Data) -> Void>()
@ -412,7 +413,7 @@ private final class CallSessionManagerContext {
let index = context.subscribers.add { next in
subscriber.putNext(next)
}
subscriber.putNext(CallSession(id: internalId, isOutgoing: context.isOutgoing, type: context.type, state: CallSessionState(context)))
subscriber.putNext(CallSession(id: internalId, isOutgoing: context.isOutgoing, type: context.type, state: CallSessionState(context), isVideoPossible: context.isVideoPossible))
disposable.set(ActionDisposable {
queue.async {
if let strongSelf = self, let context = strongSelf.contexts[internalId] {
@ -473,7 +474,7 @@ private final class CallSessionManagerContext {
private func contextUpdated(internalId: CallSessionInternalId) {
if let context = self.contexts[internalId] {
let session = CallSession(id: internalId, isOutgoing: context.isOutgoing, type: context.type, state: CallSessionState(context))
let session = CallSession(id: internalId, isOutgoing: context.isOutgoing, type: context.type, state: CallSessionState(context), isVideoPossible: context.isVideoPossible)
for subscriber in context.subscribers.copyItems() {
subscriber(session)
}
@ -526,7 +527,9 @@ private final class CallSessionManagerContext {
wasRinging = true
let internalReason: DropCallSessionReason
switch reason {
case .busy, .hangUp:
case .busy:
internalReason = .busy
case .hangUp:
internalReason = .hangUp(0)
case .disconnect:
internalReason = .disconnect
@ -578,7 +581,8 @@ private final class CallSessionManagerContext {
if let (id, accessHash, reason) = dropData {
self.contextIdByStableId.removeValue(forKey: id)
context.state = .dropping((dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: reason)
let mappedReason: CallSessionTerminationReason = .ended(.hungUp)
context.state = .dropping(reason: mappedReason, disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: reason)
|> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in
if let strongSelf = self {
if let context = strongSelf.contexts[internalId] {
@ -803,6 +807,9 @@ private final class CallSessionManagerContext {
switch callProtocol {
case let .phoneCallProtocol(_, _, maxLayer, versions):
if !versions.isEmpty {
let isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) })
context.isVideoPossible = isVideoPossible
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: calculatedKeyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], allowsP2P: allowsP2P)
self.contextUpdated(internalId: internalId)
} else {
@ -819,6 +826,9 @@ private final class CallSessionManagerContext {
switch callProtocol {
case let .phoneCallProtocol(_, _, maxLayer, versions):
if !versions.isEmpty {
let isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) })
context.isVideoPossible = isVideoPossible
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], allowsP2P: allowsP2P)
self.contextUpdated(internalId: internalId)
} else {
@ -848,7 +858,7 @@ private final class CallSessionManagerContext {
}
}
if let context = self.contexts[internalId] {
let callSession = CallSession(id: internalId, isOutgoing: context.isOutgoing, type: context.type, state: CallSessionState(context))
let callSession = CallSession(id: internalId, isOutgoing: context.isOutgoing, type: context.type, state: CallSessionState(context), isVideoPossible: context.isVideoPossible)
if let resultRingingStateValue = resultRingingStateValue {
resultRingingState = (resultRingingStateValue, callSession)
}

View file

@ -67,7 +67,7 @@ public func checkOwnershipTranfserAvailability(postbox: Postbox, network: Networ
}
|> mapToSignal { updates -> Signal<Never, ChannelOwnershipTransferError> in
accountStateManager.addUpdates(updates)
return.complete()
return .complete()
}
}
}

View file

@ -0,0 +1,164 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import SyncCore
public struct Country: PostboxCoding, Equatable {
public static func == (lhs: Country, rhs: Country) -> Bool {
return lhs.code == rhs.code && lhs.name == rhs.name && lhs.localizedName == rhs.localizedName && lhs.countryCodes == rhs.countryCodes && lhs.hidden == rhs.hidden
}
public struct CountryCode: PostboxCoding, Equatable {
public let code: String
public let prefixes: [String]
public let patterns: [String]
public init(code: String, prefixes: [String], patterns: [String]) {
self.code = code
self.prefixes = prefixes
self.patterns = patterns
}
public init(decoder: PostboxDecoder) {
self.code = decoder.decodeStringForKey("c", orElse: "")
self.prefixes = decoder.decodeStringArrayForKey("pfx")
self.patterns = decoder.decodeStringArrayForKey("ptrn")
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeString(self.code, forKey: "c")
encoder.encodeStringArray(self.prefixes, forKey: "pfx")
encoder.encodeStringArray(self.patterns, forKey: "ptrn")
}
}
public let code: String
public let name: String
public let localizedName: String?
public let countryCodes: [CountryCode]
public let hidden: Bool
public init(code: String, name: String, localizedName: String?, countryCodes: [CountryCode], hidden: Bool) {
self.code = code
self.name = name
self.localizedName = localizedName
self.countryCodes = countryCodes
self.hidden = hidden
}
public init(decoder: PostboxDecoder) {
self.code = decoder.decodeStringForKey("c", orElse: "")
self.name = decoder.decodeStringForKey("n", orElse: "")
self.localizedName = decoder.decodeOptionalStringForKey("ln")
self.countryCodes = decoder.decodeObjectArrayForKey("cc").map { $0 as! CountryCode }
self.hidden = decoder.decodeBoolForKey("h", orElse: false)
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeString(self.code, forKey: "c")
encoder.encodeString(self.name, forKey: "n")
if let localizedName = self.localizedName {
encoder.encodeString(localizedName, forKey: "ln")
} else {
encoder.encodeNil(forKey: "ln")
}
encoder.encodeObjectArray(self.countryCodes, forKey: "cc")
encoder.encodeBool(self.hidden, forKey: "h")
}
}
public final class CountriesList: PreferencesEntry, Equatable {
public let countries: [Country]
public let hash: Int32
public init(countries: [Country], hash: Int32) {
self.countries = countries
self.hash = hash
}
public init(decoder: PostboxDecoder) {
self.countries = decoder.decodeObjectArrayForKey("c").map { $0 as! Country }
self.hash = decoder.decodeInt32ForKey("h", orElse: 0)
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObjectArray(self.countries, forKey: "c")
encoder.encodeInt32(self.hash, forKey: "h")
}
public func isEqual(to: PreferencesEntry) -> Bool {
if let to = to as? CountriesList {
return self == to
} else {
return false
}
}
public static func ==(lhs: CountriesList, rhs: CountriesList) -> Bool {
return lhs.countries == rhs.countries && lhs.hash == rhs.hash
}
}
public func getCountriesList(accountManager: AccountManager, network: Network, langCode: String?, forceUpdate: Bool = false) -> Signal<[Country], NoError> {
let fetch: ([Country]?, Int32?) -> Signal<[Country], NoError> = { current, hash in
return network.request(Api.functions.help.getCountriesList(langCode: langCode ?? "", hash: hash ?? 0))
|> retryRequest
|> mapToSignal { result -> Signal<[Country], NoError> in
switch result {
case let .countriesList(apiCountries, hash):
let result = apiCountries.compactMap { Country(apiCountry: $0) }
if result == current {
return .complete()
} else {
let _ = accountManager.transaction { transaction in
transaction.updateSharedData(SharedDataKeys.countriesList, { _ in
return CountriesList(countries: result, hash: hash)
})
}.start()
return .single(result)
}
case .countriesListNotModified:
return .complete()
}
}
}
if forceUpdate {
return fetch(nil, nil)
} else {
return accountManager.sharedData(keys: [SharedDataKeys.countriesList])
|> map { sharedData -> ([Country], Int32) in
if let countriesList = sharedData.entries[SharedDataKeys.countriesList] as? CountriesList {
return (countriesList.countries, countriesList.hash)
} else {
return ([], 0)
}
} |> mapToSignal { current, hash -> Signal<[Country], NoError> in
return .single(current)
|> then(fetch(current, hash))
}
}
}
extension Country.CountryCode {
init(apiCountryCode: Api.help.CountryCode) {
switch apiCountryCode {
case let .countryCode(_, countryCode, apiPrefixes, apiPatterns):
let prefixes: [String] = apiPrefixes.flatMap { $0 } ?? []
let patterns: [String] = apiPatterns.flatMap { $0 } ?? []
self.init(code: countryCode, prefixes: prefixes, patterns: patterns)
}
}
}
extension Country {
init(apiCountry: Api.help.Country) {
switch apiCountry {
case let .country(flags, iso2, defaultName, name, countryCodes):
self.init(code: iso2, name: defaultName, localizedName: name, countryCodes: countryCodes.map { Country.CountryCode(apiCountryCode: $0) }, hidden: (flags & 1 << 0) != 0)
}
}
}

View file

@ -9,11 +9,11 @@ extension ReplyMarkupButton {
switch apiButton {
case let .keyboardButton(text):
self.init(title: text, titleWhenForwarded: nil, action: .text)
case let .keyboardButtonCallback(text, data):
case let .keyboardButtonCallback(flags, text, data):
let memory = malloc(data.size)!
memcpy(memory, data.data, data.size)
let dataBuffer = MemoryBuffer(memory: memory, capacity: data.size, length: data.size, freeWhenDone: true)
self.init(title: text, titleWhenForwarded: nil, action: .callback(dataBuffer))
self.init(title: text, titleWhenForwarded: nil, action: .callback(requiresPassword: (flags & (1 << 0)) != 0, data: dataBuffer))
case let .keyboardButtonRequestGeoLocation(text):
self.init(title: text, titleWhenForwarded: nil, action: .requestMap)
case let .keyboardButtonRequestPhone(text):

View file

@ -3,8 +3,6 @@ import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
import SyncCore
public enum MessageActionCallbackResult {
@ -14,8 +12,69 @@ public enum MessageActionCallbackResult {
case url(String)
}
public func requestMessageActionCallback(account: Account, messageId: MessageId, isGame:Bool, data: MemoryBuffer?) -> Signal<MessageActionCallbackResult, NoError> {
public enum MessageActionCallbackError {
case generic
case twoStepAuthMissing
case twoStepAuthTooFresh(Int32)
case authSessionTooFresh(Int32)
case limitExceeded
case requestPassword
case invalidPassword
case restricted
case userBlocked
}
public func requestMessageActionCallbackPasswordCheck(account: Account, messageId: MessageId, isGame: Bool, data: MemoryBuffer?) -> Signal<Never, MessageActionCallbackError> {
return account.postbox.loadedPeerWithId(messageId.peerId)
|> castError(MessageActionCallbackError.self)
|> take(1)
|> mapToSignal { peer in
if let inputPeer = apiInputPeer(peer) {
var flags: Int32 = 0
var dataBuffer: Buffer?
if let data = data {
flags |= Int32(1 << 0)
dataBuffer = Buffer(data: data.makeData())
}
if isGame {
flags |= Int32(1 << 1)
}
return account.network.request(Api.functions.messages.getBotCallbackAnswer(flags: flags, peer: inputPeer, msgId: messageId.id, data: dataBuffer, password: .inputCheckPasswordEmpty))
|> mapError { error -> MessageActionCallbackError in
if error.errorDescription == "PASSWORD_HASH_INVALID" {
return .requestPassword
} else if error.errorDescription == "PASSWORD_MISSING" {
return .twoStepAuthMissing
} else if error.errorDescription.hasPrefix("PASSWORD_TOO_FRESH_") {
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "PASSWORD_TOO_FRESH_".count)...])
if let value = Int32(timeout) {
return .twoStepAuthTooFresh(value)
}
} else if error.errorDescription.hasPrefix("SESSION_TOO_FRESH_") {
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "SESSION_TOO_FRESH_".count)...])
if let value = Int32(timeout) {
return .authSessionTooFresh(value)
}
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
return .restricted
} else if error.errorDescription == "USER_BLOCKED" {
return .userBlocked
}
return .generic
}
|> mapToSignal { _ -> Signal<Never, MessageActionCallbackError> in
return .complete()
}
} else {
return .fail(.generic)
}
}
}
public func requestMessageActionCallback(account: Account, messageId: MessageId, isGame :Bool, password: String?, data: MemoryBuffer?) -> Signal<MessageActionCallbackResult, MessageActionCallbackError> {
return account.postbox.loadedPeerWithId(messageId.peerId)
|> castError(MessageActionCallbackError.self)
|> take(1)
|> mapToSignal { peer in
if let inputPeer = apiInputPeer(peer) {
@ -28,28 +87,79 @@ public func requestMessageActionCallback(account: Account, messageId: MessageId,
if isGame {
flags |= Int32(1 << 1)
}
return account.network.request(Api.functions.messages.getBotCallbackAnswer(flags: flags, peer: inputPeer, msgId: messageId.id, data: dataBuffer))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.BotCallbackAnswer?, NoError> in
return .single(nil)
}
|> map { result -> MessageActionCallbackResult in
guard let result = result else {
return .none
let checkPassword: Signal<Api.InputCheckPasswordSRP?, MessageActionCallbackError>
if let password = password, !password.isEmpty {
flags |= Int32(1 << 2)
checkPassword = twoStepAuthData(account.network)
|> mapError { error -> MessageActionCallbackError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else {
return .generic
}
}
switch result {
case let .botCallbackAnswer(flags, message, url, cacheTime):
if let message = message {
if (flags & (1 << 1)) != 0 {
return .alert(message)
} else {
return .toast(message)
}
} else if let url = url {
return .url(url)
} else {
return .none
|> mapToSignal { authData -> Signal<Api.InputCheckPasswordSRP?, MessageActionCallbackError> in
if let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData {
guard let kdfResult = passwordKDF(encryptionProvider: account.network.encryptionProvider, password: password, derivation: currentPasswordDerivation, srpSessionData: srpSessionData) else {
return .fail(.generic)
}
return .single(.inputCheckPasswordSRP(srpId: kdfResult.id, A: Buffer(data: kdfResult.A), M1: Buffer(data: kdfResult.M1)))
} else {
return .fail(.twoStepAuthMissing)
}
}
} else {
checkPassword = .single(nil)
}
return checkPassword
|> mapToSignal { password -> Signal<MessageActionCallbackResult, MessageActionCallbackError> in
return account.network.request(Api.functions.messages.getBotCallbackAnswer(flags: flags, peer: inputPeer, msgId: messageId.id, data: dataBuffer, password: password))
|> map(Optional.init)
|> mapError { error -> MessageActionCallbackError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription == "PASSWORD_HASH_INVALID" {
return .invalidPassword
} else if error.errorDescription == "PASSWORD_MISSING" {
return .twoStepAuthMissing
} else if error.errorDescription.hasPrefix("PASSWORD_TOO_FRESH_") {
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "PASSWORD_TOO_FRESH_".count)...])
if let value = Int32(timeout) {
return .twoStepAuthTooFresh(value)
}
} else if error.errorDescription.hasPrefix("SESSION_TOO_FRESH_") {
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "SESSION_TOO_FRESH_".count)...])
if let value = Int32(timeout) {
return .authSessionTooFresh(value)
}
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
return .restricted
} else if error.errorDescription == "USER_BLOCKED" {
return .userBlocked
}
return .generic
}
|> map { result -> MessageActionCallbackResult in
guard let result = result else {
return .none
}
switch result {
case let .botCallbackAnswer(flags, message, url, cacheTime):
if let message = message {
if (flags & (1 << 1)) != 0 {
return .alert(message)
} else {
return .toast(message)
}
} else if let url = url {
return .url(url)
} else {
return .none
}
}
}
}
} else {

View file

@ -7,7 +7,7 @@ import MtProtoKit
import SyncCore
public enum SearchMessagesLocation: Equatable {
case general
case general(tags: MessageTags?)
case group(PeerGroupId)
case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?)
case publicForwards(messageId: MessageId, datacenterId: Int?)
@ -193,7 +193,6 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
}
let filter: Api.MessagesFilter
if let tags = tags {
if tags.contains(.file) {
filter = .inputMessagesFilterDocument
@ -270,7 +269,8 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
}
case .group:
remoteSearchResult = .single((nil, nil))
case .general:
case let .general(tags):
let filter: Api.MessagesFilter = tags.flatMap { messageFilterForTagMask($0) } ?? .inputMessagesFilterEmpty
remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in
var lowerBound: MessageIndex?
if let state = state, let message = state.main.messages.last {
@ -283,7 +283,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
}
}
|> mapToSignal { (nextRate, lowerBound, inputPeer) in
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)
return account.network.request(Api.functions.messages.searchGlobal(flags: 0, folderId: nil, q: query, filter: filter, 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)
}
@ -292,8 +292,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
}
}
case let .publicForwards(messageId, datacenterId):
remoteSearchResult = .single((nil, nil))
/*remoteSearchResult = account.postbox.transaction { transaction -> (Api.InputChannel?, Int32, MessageIndex?, Api.InputPeer) in
remoteSearchResult = account.postbox.transaction { transaction -> (Api.InputChannel?, Int32, MessageIndex?, Api.InputPeer) in
let sourcePeer = transaction.getPeer(messageId.peerId)
let inputChannel = sourcePeer.flatMap { apiInputChannel($0) }
@ -330,7 +329,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
|> `catch` { _ -> Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> in
return .single((nil, nil))
}
}*/
}
}
return remoteSearchResult

View file

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

View file

@ -136,7 +136,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
switch message {
case let .message(flags, _, fromId, toId, fwdHeader, viaBotId, _, _, _, media, _, entities, _, _, _, _, _):
case let .message(flags, _, fromId, toId, fwdHeader, viaBotId, _, _, _, media, _, entities, _, _, _, _, _, _):
let peerId: PeerId
switch toId {
case let .peerUser(userId):
@ -240,7 +240,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
func apiMessageAssociatedMessageIds(_ message: Api.Message) -> [MessageId]? {
switch message {
case let .message(flags, _, fromId, toId, _, _, replyToMsgId, _, _, _, _, _, _, _, _, _, _):
case let .message(flags, _, fromId, toId, _, _, replyToMsgId, _, _, _, _, _, _, _, _, _, _, _):
if let replyToMsgId = replyToMsgId {
let peerId: PeerId
switch toId {
@ -398,7 +398,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes
extension StoreMessage {
convenience init?(apiMessage: Api.Message, namespace: MessageId.Namespace = Namespaces.Message.Cloud) {
switch apiMessage {
case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, date, message, media, replyMarkup, entities, views, editDate, postAuthor, groupingId, restrictionReason):
case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, date, message, media, replyMarkup, entities, views, forwards, editDate, postAuthor, groupingId, restrictionReason):
let peerId: PeerId
var authorId: PeerId?
switch toId {
@ -517,14 +517,16 @@ extension StoreMessage {
attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId)))
}
if let views = views, namespace != Namespaces.Message.ScheduledCloud {
attributes.append(ViewCountMessageAttribute(count: Int(views)))
if namespace != Namespaces.Message.ScheduledCloud {
if let views = views {
attributes.append(ViewCountMessageAttribute(count: Int(views)))
}
if let forwards = forwards {
attributes.append(ForwardCountMessageAttribute(count: Int(forwards)))
}
}
/*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

@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService {
self.putNext(groups)
}
case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyToMsgId, entities):
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: fromId, toId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil)
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: fromId, toId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil)
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
if groups.count != 0 {
@ -75,7 +75,7 @@ class UpdateMessageService: NSObject, MTMessageService {
generatedToId = Api.Peer.peerUser(userId: self.peerId.id)
}
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, toId: generatedToId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil)
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, toId: generatedToId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil)
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
if groups.count != 0 {

View file

@ -58,6 +58,8 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
self.openUrl = openUrl
self.back = back
loadServerCountryCodes(accountManager: sharedContext.accountManager, network: account.network)
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(presentationData.theme), strings: NavigationBarStrings(presentationStrings: presentationData.strings)))
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)

View file

@ -121,26 +121,32 @@ private final class PhoneAndCountryNode: ASDisplayNode {
self.phoneInputNode.countryCodeField.textField.disableAutomaticKeyboardHandling = [.forward]
self.phoneInputNode.numberField.textField.disableAutomaticKeyboardHandling = [.forward]
self.countryButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 15.0, bottom: 10.0, right: 0.0)
self.countryButton.contentHorizontalAlignment = .left
self.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor)
// self.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor)
self.countryButton.addTarget(self, action: #selector(self.countryPressed), forControlEvents: .touchUpInside)
self.phoneInputNode.countryCodeUpdated = { [weak self] code, name in
let font = Font.with(size: 20.0, design: .monospace, traits: [])
if let strongSelf = self {
if let code = Int(code), let name = name, let countryName = countryCodeAndIdToName[CountryCodeAndId(code: code, id: name)] {
let flagString = emojiFlagForISOCountryCode(name as NSString)
let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(name, strings: strongSelf.strings) ?? countryName
strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemPrimaryTextColor, for: [])
strongSelf.phoneInputNode.mask = AuthorizationSequenceCountrySelectionController.lookupPatternByCode(code).flatMap { NSAttributedString(string: $0, font: font, textColor: theme.list.itemPlaceholderTextColor) }
} else if let code = Int(code), let (countryId, countryName) = countryCodeToIdAndName[code] {
let flagString = emojiFlagForISOCountryCode(countryId as NSString)
let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(countryId, strings: strongSelf.strings) ?? countryName
strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemPrimaryTextColor, for: [])
strongSelf.phoneInputNode.mask = AuthorizationSequenceCountrySelectionController.lookupPatternByCode(code).flatMap { NSAttributedString(string: $0, font: font, textColor: theme.list.itemPlaceholderTextColor) }
} else {
strongSelf.countryButton.setTitle(strings.Login_SelectCountry_Title, with: Font.regular(20.0), with: theme.list.itemPlaceholderTextColor, for: [])
strongSelf.phoneInputNode.mask = nil
}
}
}
@ -165,12 +171,14 @@ private final class PhoneAndCountryNode: ASDisplayNode {
let countryCodeFrame = CGRect(origin: CGPoint(x: 18.0, y: size.height - 57.0), size: CGSize(width: 60.0, height: 57.0))
let numberFrame = CGRect(origin: CGPoint(x: 96.0, y: size.height - 57.0), size: CGSize(width: size.width - 96.0 - 8.0, height: 57.0))
let placeholderFrame = numberFrame.offsetBy(dx: -1.0, dy: 16.0)
let phoneInputFrame = countryCodeFrame.union(numberFrame)
self.phoneInputNode.frame = phoneInputFrame
self.phoneInputNode.countryCodeField.frame = countryCodeFrame.offsetBy(dx: -phoneInputFrame.minX, dy: -phoneInputFrame.minY)
self.phoneInputNode.numberField.frame = numberFrame.offsetBy(dx: -phoneInputFrame.minX, dy: -phoneInputFrame.minY)
self.phoneInputNode.placeholderNode.frame = placeholderFrame.offsetBy(dx: -phoneInputFrame.minX, dy: -phoneInputFrame.minY)
}
}
@ -204,6 +212,8 @@ private final class ContactSyncNode: ASDisplayNode {
}
}
final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
private let sharedContext: SharedAccountContext
private var account: UnauthorizedAccount

View file

@ -3,53 +3,81 @@ import UIKit
import Display
import LegacyComponents
private enum Constants {
static let maxLevel: CGFloat = 4
}
final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration {
private let smallBlob = BlobView(
pointsCount: 8,
minRandomness: 0.1,
maxRandomness: 0.5,
minSpeed: 0.2,
maxSpeed: 0.6,
minScale: 0.45,
maxScale: 0.55,
scaleSpeed: 0.2,
isCircle: true
)
private let mediumBlob = BlobView(
pointsCount: 8,
minRandomness: 1,
maxRandomness: 1,
minSpeed: 1.5,
maxSpeed: 7,
minScale: 0.52,
maxScale: 0.87,
scaleSpeed: 0.2,
isCircle: false
)
private let bigBlob = BlobView(
pointsCount: 8,
minRandomness: 1,
maxRandomness: 1,
minSpeed: 1.5,
maxSpeed: 7,
minScale: 0.57,
maxScale: 1,
scaleSpeed: 0.2,
isCircle: false
)
private let smallBlob: BlobView
private let mediumBlob: BlobView
private let bigBlob: BlobView
override init(frame: CGRect) {
private let maxLevel: CGFloat
private var displayLinkAnimator: ConstantDisplayLinkAnimator?
private var audioLevel: CGFloat = 0
private var presentationAudioLevel: CGFloat = 0
private(set) var isAnimating = false
typealias BlobRange = (min: CGFloat, max: CGFloat)
init(
frame: CGRect,
maxLevel: CGFloat,
smallBlobRange: BlobRange,
mediumBlobRange: BlobRange,
bigBlobRange: BlobRange
) {
self.maxLevel = maxLevel
self.smallBlob = BlobView(
pointsCount: 8,
minRandomness: 0.1,
maxRandomness: 0.5,
minSpeed: 0.2,
maxSpeed: 0.6,
minScale: smallBlobRange.min,
maxScale: smallBlobRange.max,
scaleSpeed: 0.2,
isCircle: true
)
self.mediumBlob = BlobView(
pointsCount: 8,
minRandomness: 1,
maxRandomness: 1,
minSpeed: 1.5,
maxSpeed: 7,
minScale: mediumBlobRange.min,
maxScale: mediumBlobRange.max,
scaleSpeed: 0.2,
isCircle: false
)
self.bigBlob = BlobView(
pointsCount: 8,
minRandomness: 1,
maxRandomness: 1,
minSpeed: 1.5,
maxSpeed: 7,
minScale: bigBlobRange.min,
maxScale: bigBlobRange.max,
scaleSpeed: 0.2,
isCircle: false
)
super.init(frame: frame)
addSubview(bigBlob)
addSubview(mediumBlob)
addSubview(smallBlob)
displayLinkAnimator = ConstantDisplayLinkAnimator() { [weak self] in
guard let strongSelf = self else { return }
strongSelf.presentationAudioLevel = strongSelf.presentationAudioLevel * 0.9 + strongSelf.audioLevel * 0.1
strongSelf.smallBlob.level = strongSelf.presentationAudioLevel
strongSelf.mediumBlob.level = strongSelf.presentationAudioLevel
strongSelf.bigBlob.level = strongSelf.presentationAudioLevel
}
}
required init?(coder: NSCoder) {
@ -63,45 +91,61 @@ final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration
}
func updateLevel(_ level: CGFloat) {
let normalizedLevel = min(1, max(level / Constants.maxLevel, 0))
let normalizedLevel = min(1, max(level / maxLevel, 0))
smallBlob.updateSpeedLevel(to: normalizedLevel)
mediumBlob.updateSpeedLevel(to: normalizedLevel)
bigBlob.updateSpeedLevel(to: normalizedLevel)
}
func tick(_ level: CGFloat) {
let normalizedLevel = min(1, max(level / Constants.maxLevel, 0))
smallBlob.level = normalizedLevel
mediumBlob.level = normalizedLevel
bigBlob.level = normalizedLevel
audioLevel = normalizedLevel
}
func startAnimating() {
mediumBlob.layer.animateScale(from: 0.5, to: 1, duration: 0.1, removeOnCompletion: false)
bigBlob.layer.animateScale(from: 0.5, to: 1, duration: 0.1, removeOnCompletion: false)
guard !isAnimating else { return }
isAnimating = true
mediumBlob.layer.animateScale(from: 0.5, to: 1, duration: 0.15, removeOnCompletion: false)
bigBlob.layer.animateScale(from: 0.5, to: 1, duration: 0.15, removeOnCompletion: false)
updateBlobsState()
displayLinkAnimator?.isPaused = false
}
func stopAnimating() {
mediumBlob.layer.animateScale(from: 1.0, to: 0.5, duration: 0.1, removeOnCompletion: false)
bigBlob.layer.animateScale(from: 1.0, to: 0.5, duration: 0.1, removeOnCompletion: false)
guard isAnimating else { return }
isAnimating = false
mediumBlob.layer.animateScale(from: 1.0, to: 0.5, duration: 0.15, removeOnCompletion: false)
bigBlob.layer.animateScale(from: 1.0, to: 0.5, duration: 0.15, removeOnCompletion: false)
updateBlobsState()
displayLinkAnimator?.isPaused = true
}
private func updateBlobsState() {
if isAnimating {
if smallBlob.frame.size != .zero {
smallBlob.startAnimating()
mediumBlob.startAnimating()
bigBlob.startAnimating()
}
} else {
smallBlob.stopAnimating()
mediumBlob.stopAnimating()
bigBlob.stopAnimating()
}
}
override func layoutSubviews() {
super.layoutSubviews()
let isInitial = smallBlob.frame == .zero
smallBlob.frame = bounds
mediumBlob.frame = bounds
bigBlob.frame = bounds
if isInitial {
smallBlob.startAnimating()
mediumBlob.startAnimating()
bigBlob.startAnimating()
}
updateBlobsState()
}
}
@ -221,40 +265,19 @@ final class BlobView: UIView {
animateToNewShape()
}
func animateToNewScale() {
let scaleLevelForAnimation: CGFloat = {
if scaleLevelsToBalance.isEmpty {
return 0
}
return scaleLevelsToBalance.reduce(0, +) / CGFloat(scaleLevelsToBalance.count)
}()
let isDownscale = lastScaleLevel > scaleLevelForAnimation
lastScaleLevel = scaleLevelForAnimation
shapeLayer.pop_removeAnimation(forKey: "scale")
let currentScale = minScale + (maxScale - minScale) * scaleLevelForAnimation
let scaleAnimation = POPBasicAnimation(propertyNamed: kPOPLayerScaleXY)!
scaleAnimation.toValue = CGPoint(x: currentScale, y: currentScale)
scaleAnimation.duration = isDownscale ? 0.45 : CFTimeInterval(scaleSpeed)
scaleAnimation.completionBlock = { [weak self] animation, finished in
if finished {
self?.animateToNewScale()
}
}
shapeLayer.pop_add(scaleAnimation, forKey: "scale")
scaleLevel = 0
scaleLevelsToBalance.removeAll()
func stopAnimating() {
fromPoints = currentPoints
toPoints = nil
pop_removeAnimation(forKey: "blob")
}
func animateToNewShape() {
private func animateToNewShape() {
guard !isCircle else { return }
if pop_animation(forKey: "blob") != nil {
fromPoints = currentPoints
toPoints = nil
shapeLayer.pop_removeAnimation(forKey: "blob")
pop_removeAnimation(forKey: "blob")
}
if fromPoints == nil {

View file

@ -176,11 +176,11 @@ final class ChatButtonKeyboardInputNode: ChatInputNode {
self.controllerInteraction.shareAccountContact()
case .openWebApp:
if let message = self.message {
self.controllerInteraction.requestMessageActionCallback(message.id, nil, true)
self.controllerInteraction.requestMessageActionCallback(message.id, nil, true, false)
}
case let .callback(data):
case let .callback(requiresPassword, data):
if let message = self.message {
self.controllerInteraction.requestMessageActionCallback(message.id, data, false)
self.controllerInteraction.requestMessageActionCallback(message.id, data, false, requiresPassword)
}
case let .switchInline(samePeer, query):
if let message = message {

View file

@ -793,7 +793,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.enqueueChatContextResult(collection, result, hideVia: true, closeMediaInput: true)
return true
}, requestMessageActionCallback: { [weak self] messageId, data, isGame in
}, requestMessageActionCallback: { [weak self] messageId, data, isGame, requiresPassword in
if let strongSelf = self {
guard !strongSelf.presentationInterfaceState.isScheduledMessages else {
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
@ -819,52 +819,73 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
})
strongSelf.messageActionCallbackDisposable.set(((requestMessageActionCallback(account: strongSelf.context.account, messageId: messageId, isGame: isGame, data: data)
|> afterDisposed {
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if let index = $0.firstIndex(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
}
return $0
}
let proceedWithResult: (MessageActionCallbackResult) -> Void = { [weak self] result in
guard let strongSelf = self else {
return
}
switch result {
case .none:
break
case let .alert(text):
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
case let .toast(text):
let message: Signal<String?, NoError> = .single(text)
let noMessage: Signal<String?, NoError> = .single(nil)
let delayedNoMessage: Signal<String?, NoError> = noMessage |> delay(1.0, queue: Queue.mainQueue())
strongSelf.botCallbackAlertMessage.set(message |> then(delayedNoMessage))
case let .url(url):
if isGame {
strongSelf.chatDisplayNode.dismissInput()
strongSelf.effectiveNavigationController?.pushViewController(GameController(context: strongSelf.context, url: url, message: message))
} else {
strongSelf.openUrl(url, concealed: false)
}
}
}
let account = strongSelf.context.account
if requiresPassword {
strongSelf.messageActionCallbackDisposable.set((requestMessageActionCallbackPasswordCheck(account: account, messageId: messageId, isGame: isGame, data: data)
|> deliverOnMainQueue).start(error: { error in
let controller = ownershipTransferController(context: context, initialError: error, present: { c, a in
strongSelf.present(c, in: .window(.root), with: a)
}, commit: { password in
return requestMessageActionCallback(account: account, messageId: messageId, isGame: isGame, password: password, data: data)
}, completion: { result in
proceedWithResult(result)
})
strongSelf.present(controller, in: .window(.root))
}))
} else {
strongSelf.messageActionCallbackDisposable.set(((requestMessageActionCallback(account: account, messageId: messageId, isGame: isGame, password: nil, data: data)
|> afterDisposed {
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if let index = $0.firstIndex(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
}
return $0
}
})
}
}
}
})
|> deliverOnMainQueue).start(next: { result in
if let strongSelf = self {
switch result {
case .none:
break
case let .alert(text):
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
case let .toast(text):
let message: Signal<String?, NoError> = .single(text)
let noMessage: Signal<String?, NoError> = .single(nil)
let delayedNoMessage: Signal<String?, NoError> = noMessage |> delay(1.0, queue: Queue.mainQueue())
strongSelf.botCallbackAlertMessage.set(message |> then(delayedNoMessage))
case let .url(url):
if isGame {
strongSelf.chatDisplayNode.dismissInput()
strongSelf.effectiveNavigationController?.pushViewController(GameController(context: strongSelf.context, url: url, message: message))
} else {
strongSelf.openUrl(url, concealed: false)
}
}
}
}))
})
|> deliverOnMainQueue).start(next: { result in
proceedWithResult(result)
}))
}
}
}
}, requestMessageActionUrlAuth: { [weak self] defaultUrl, messageId, buttonId in
@ -9215,6 +9236,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
public override var keyShortcuts: [KeyShortcut] {
if !self.traceVisibility() || !isTopmostChatController(self) {
return []
}
let strings = self.presentationData.strings
var inputShortcuts: [KeyShortcut]
@ -9325,16 +9350,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
let otherShortcuts: [KeyShortcut] = [
KeyShortcut(title: strings.KeyCommand_ScrollUp, input: UIKeyCommand.inputUpArrow, modifiers: [.shift], action: { [weak self] in
if let strongSelf = self {
_ = strongSelf.chatDisplayNode.historyNode.scrollWithDirection(.down, distance: 75.0)
}
}),
KeyShortcut(title: strings.KeyCommand_ScrollDown, input: UIKeyCommand.inputDownArrow, modifiers: [.shift], action: { [weak self] in
if let strongSelf = self {
_ = strongSelf.chatDisplayNode.historyNode.scrollWithDirection(.up, distance: 75.0)
}
}),
// KeyShortcut(title: strings.KeyCommand_ScrollUp, input: UIKeyCommand.inputUpArrow, modifiers: [.shift], action: { [weak self] in
// if let strongSelf = self {
// _ = strongSelf.chatDisplayNode.historyNode.scrollWithDirection(.down, distance: 75.0)
// }
// }),
// KeyShortcut(title: strings.KeyCommand_ScrollDown, input: UIKeyCommand.inputDownArrow, modifiers: [.shift], action: { [weak self] in
// if let strongSelf = self {
// _ = strongSelf.chatDisplayNode.historyNode.scrollWithDirection(.up, distance: 75.0)
// }
// }),
KeyShortcut(title: strings.KeyCommand_ChatInfo, input: "I", modifiers: [.command, .control], action: { [weak self] in
if let strongSelf = self {
strongSelf.interfaceInteraction?.openPeerInfo()

View file

@ -71,7 +71,7 @@ public final class ChatControllerInteraction {
let sendSticker: (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool
let sendGif: (FileMediaReference, ASDisplayNode, CGRect) -> Bool
let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool
let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool) -> Void
let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void
let requestMessageActionUrlAuth: (String, MessageId, Int32) -> Void
let activateSwitchInline: (PeerId?, String) -> Void
let openUrl: (String, Bool, Bool?, Message?) -> Void
@ -138,7 +138,7 @@ public final class ChatControllerInteraction {
var searchTextHighightState: (String, [MessageIndex])?
var seenOneTimeAnimatedMedia = Set<MessageId>()
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId, Bool) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, NSAttributedString, TextSelectionAction) -> Void, updateMessageLike: @escaping (MessageId, Bool) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, dismissReplyMarkupMessage: @escaping (Message) -> Void, openMessagePollResults: @escaping (MessageId, Data) -> Void, openPollCreation: @escaping (Bool?) -> Void, displayPollSolution: @escaping (TelegramMediaPollResults.Solution, ASDisplayNode) -> Void, displayPsa: @escaping (String, ASDisplayNode) -> Void, displayDiceTooltip: @escaping (TelegramMediaDice) -> Void, animateDiceSuccess: @escaping () -> Void, greetingStickerNode: @escaping () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)?, openPeerContextMenu: @escaping (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId, Bool) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, NSAttributedString, TextSelectionAction) -> Void, updateMessageLike: @escaping (MessageId, Bool) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, dismissReplyMarkupMessage: @escaping (Message) -> Void, openMessagePollResults: @escaping (MessageId, Data) -> Void, openPollCreation: @escaping (Bool?) -> Void, displayPollSolution: @escaping (TelegramMediaPollResults.Solution, ASDisplayNode) -> Void, displayPsa: @escaping (String, ASDisplayNode) -> Void, displayDiceTooltip: @escaping (TelegramMediaDice) -> Void, animateDiceSuccess: @escaping () -> Void, greetingStickerNode: @escaping () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)?, openPeerContextMenu: @escaping (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
self.openMessage = openMessage
self.openPeer = openPeer
self.openPeerMention = openPeerMention
@ -215,7 +215,7 @@ public final class ChatControllerInteraction {
static var `default`: ChatControllerInteraction {
return ChatControllerInteraction(openMessage: { _, _ in
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
}, presentController: { _, _ in }, navigationController: {
return nil
}, chatControllerNode: {

View file

@ -68,7 +68,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private var highlightedState: Bool = false
private var heartbeatHaptic: HeartbeatHaptic?
private var haptic: EmojiHaptic?
private var currentSwipeToReplyTranslation: CGFloat = 0.0
@ -217,7 +217,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
didSet {
if self.visibilityStatus != oldValue {
self.updateVisibility()
self.heartbeatHaptic?.enabled = self.visibilityStatus
self.haptic?.enabled = self.visibilityStatus
}
}
}
@ -228,15 +228,20 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
if let telegramDice = self.telegramDice {
let animationNode = ManagedDiceAnimationNode(context: item.context, emoji: telegramDice.emoji.strippedEmoji)
if !item.message.effectivelyIncoming(item.context.account.peerId) {
animationNode.success = { [weak self] in
if let strongSelf = self, let item = strongSelf.item {
item.controllerInteraction.animateDiceSuccess()
// if telegramDice.emoji == "🎲" {
// let animationNode = SlotMachineAnimationNode(context: item.context)
// self.animationNode = animationNode
// } else {
let animationNode = ManagedDiceAnimationNode(context: item.context, emoji: telegramDice.emoji.strippedEmoji)
if !item.message.effectivelyIncoming(item.context.account.peerId) {
animationNode.success = { [weak self] in
if let strongSelf = self, let item = strongSelf.item {
item.controllerInteraction.animateDiceSuccess()
}
}
}
}
self.animationNode = animationNode
self.animationNode = animationNode
// }
} else {
let animationNode: AnimatedStickerNode
if let (node, parentNode, listNode, greetingCompletion) = item.controllerInteraction.greetingStickerNode(), let greetingStickerNode = node as? AnimatedStickerNode {
@ -290,7 +295,13 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.setupNode(item: item)
if let telegramDice = self.telegramDice, let diceNode = self.animationNode as? ManagedDiceAnimationNode {
if let telegramDice = self.telegramDice, let diceNode = self.animationNode as? SlotMachineAnimationNode {
if let value = telegramDice.value {
diceNode.setState(value == 0 ? .rolling : .value(value, true))
} else {
diceNode.setState(.rolling)
}
} else if let telegramDice = self.telegramDice, let diceNode = self.animationNode as? ManagedDiceAnimationNode {
if let value = telegramDice.value {
diceNode.setState(value == 0 ? .rolling : .value(value, true))
} else {
@ -1099,24 +1110,29 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
let beatingHearts: [UInt32] = [0x2764, 0x1F90E, 0x1F9E1, 0x1F49A, 0x1F49C, 0x1F49B, 0x1F5A4, 0x1F90D]
if let text = self.item?.message.text, let firstScalar = text.unicodeScalars.first, beatingHearts.contains(firstScalar.value) {
let peach = 0x1F351
if let text = self.item?.message.text, let firstScalar = text.unicodeScalars.first, beatingHearts.contains(firstScalar.value) || firstScalar.value == peach {
return .optionalAction({
let _ = startTime.start(next: { [weak self] time in
guard let strongSelf = self else {
return
}
let heartbeatHaptic: HeartbeatHaptic
if let current = strongSelf.heartbeatHaptic {
heartbeatHaptic = current
var haptic: EmojiHaptic
if let current = strongSelf.haptic {
haptic = current
} else {
heartbeatHaptic = HeartbeatHaptic()
heartbeatHaptic.enabled = true
strongSelf.heartbeatHaptic = heartbeatHaptic
if beatingHearts.contains(firstScalar.value) {
haptic = HeartbeatHaptic()
} else {
haptic = PeachHaptic()
}
haptic.enabled = true
strongSelf.haptic = haptic
}
if !heartbeatHaptic.active {
heartbeatHaptic.start(time: time)
if !haptic.active {
haptic.start(time: time)
}
})
})

View file

@ -907,6 +907,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
}
if item.presentationData.isPreview {
needShareButton = false
}
var tmpWidth: CGFloat
if allowFullWidth {
tmpWidth = baseWidth
@ -1843,6 +1847,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
strongSelf.contextSourceNode.contentNode.addSubnode(nameNode)
}
nameNode.frame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + nameNodeOriginY), size: nameNodeSizeApply.0)
nameNode.displaysAsynchronously = !item.presentationData.isPreview
if let credibilityIconImage = currentCredibilityIconImage {
let credibilityIconNode: ASImageNode

View file

@ -508,6 +508,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
impressionIcon.removeFromSupernode()
strongSelf.impressionIcon = nil
}
strongSelf.impressionIcon?.displaysAsynchronously = !presentationData.isPreview
strongSelf.dateNode.frame = CGRect(origin: CGPoint(x: leftInset + backgroundInsets.left + impressionWidth, y: backgroundInsets.top + 1.0 + offset), size: date.size)

View file

@ -26,7 +26,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode {
self.addSubnode(self.contentNode)
self.contentNode.openMedia = { [weak self] _ in
if let strongSelf = self, let item = strongSelf.item {
item.controllerInteraction.requestMessageActionCallback(item.message.id, nil, true)
item.controllerInteraction.requestMessageActionCallback(item.message.id, nil, true, false)
}
}
}

View file

@ -34,7 +34,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
private var iconNode: TransformImageNode?
private var statusNode: SemanticStatusNode?
private var playbackAudioLevelView: VoiceBlobView?
private var displayLinkAnimator: ConstantDisplayLinkAnimator?
private var streamingStatusNode: RadialStatusNode?
private var tapRecognizer: UITapGestureRecognizer?
@ -63,21 +62,10 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
var visibility: Bool = false {
didSet {
if self.visibility != oldValue {
if self.visibility {
if self.displayLinkAnimator == nil {
self.displayLinkAnimator = ConstantDisplayLinkAnimator(update: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.currentAudioLevel = strongSelf.currentAudioLevel * 0.9 + strongSelf.inputAudioLevel * 0.1
strongSelf.playbackAudioLevelView?.tick(strongSelf.currentAudioLevel)
})
}
self.displayLinkAnimator?.isPaused = false
} else {
self.displayLinkAnimator?.isPaused = true
}
guard self.visibility != oldValue else { return }
if !self.visibility {
self.playbackAudioLevelView?.stopAnimating()
}
}
}
@ -449,8 +437,13 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
if hasThumbnail {
minLayoutWidth = max(titleLayout.size.width, descriptionMaxWidth) + 86.0
} else if isVoice {
var descriptionAndStatusWidth = descriptionLayout.size.width
if let statusSize = statusSize {
descriptionAndStatusWidth += 6 + statusSize.width
}
let calcDuration = max(minVoiceLength, min(maxVoiceLength, CGFloat(audioDuration)))
minLayoutWidth = minVoiceWidth + (maxVoiceWidth - minVoiceWidth) * (calcDuration - minVoiceLength) / (maxVoiceLength - minVoiceLength)
minLayoutWidth = max(descriptionAndStatusWidth + 56, minLayoutWidth)
} else {
minLayoutWidth = max(titleLayout.size.width, descriptionMaxWidth) + 44.0 + 8.0
}
@ -477,7 +470,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
return (minLayoutWidth, { boundingWidth in
let progressDiameter: CGFloat = (isVoice && !hasThumbnail) ? 37.0 : 44.0
let progressDiameter: CGFloat = 44.0
var iconFrame: CGRect?
let progressFrame: CGRect
@ -487,10 +480,19 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
if hasThumbnail {
let currentIconFrame = CGRect(origin: CGPoint(x: -1.0, y: -7.0), size: CGSize(width: 74.0, height: 74.0))
iconFrame = currentIconFrame
progressFrame = CGRect(origin: CGPoint(x: currentIconFrame.minX + floor((currentIconFrame.size.width - progressDiameter) / 2.0), y: currentIconFrame.minY + floor((currentIconFrame.size.height - progressDiameter) / 2.0)), size: CGSize(width: progressDiameter, height: progressDiameter))
progressFrame = CGRect(
origin: CGPoint(
x: currentIconFrame.minX + floor((currentIconFrame.size.width - progressDiameter) / 2.0),
y: currentIconFrame.minY + floor((currentIconFrame.size.height - progressDiameter) / 2.0)
),
size: CGSize(width: progressDiameter, height: progressDiameter)
)
controlAreaWidth = 86.0
} else {
progressFrame = CGRect(origin: CGPoint(x: 0.0, y: isVoice ? -5.0 : 0.0), size: CGSize(width: progressDiameter, height: progressDiameter))
progressFrame = CGRect(
origin: CGPoint(x: 3.0, y: -3.0),
size: CGSize(width: progressDiameter, height: progressDiameter)
)
controlAreaWidth = progressFrame.maxX + 8.0
}
@ -506,7 +508,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
let descriptionFrame: CGRect
if isVoice {
descriptionFrame = CGRect(origin: CGPoint(x: 43.0, y: 19.0), size: descriptionLayout.size)
descriptionFrame = CGRect(origin: CGPoint(x: 56.0, y: 22.0), size: descriptionLayout.size)
} else {
descriptionFrame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY - 1.0), size: descriptionLayout.size)
}
@ -516,7 +518,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
let textSizes = titleFrame.union(descriptionFrame).size
fittedLayoutSize = CGSize(width: textSizes.width + controlAreaWidth, height: 59.0)
} else if isVoice {
fittedLayoutSize = CGSize(width: minLayoutWidth, height: 27.0)
fittedLayoutSize = CGSize(width: minLayoutWidth, height: 38.0)
} else {
let unionSize = titleFrame.union(descriptionFrame).union(progressFrame).size
fittedLayoutSize = CGSize(width: unionSize.width, height: unionSize.height + 6.0)
@ -529,8 +531,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
if let statusFrameValue = statusFrame, descriptionFrame.intersects(statusFrameValue) {
fittedLayoutSize.height += statusFrameValue.height
statusFrame = statusFrameValue.offsetBy(dx: 0.0, dy: statusFrameValue.height)
let intersection = descriptionFrame.intersection(statusFrameValue)
let addedWidth = intersection.width + 20
fittedLayoutSize.width += addedWidth
}
if let statusFrameValue = statusFrame, let iconFrame = iconFrame, iconFrame.intersects(statusFrameValue) {
fittedLayoutSize.height += 15.0
@ -598,7 +601,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
strongSelf.waveformScrubbingNode = waveformScrubbingNode
strongSelf.addSubnode(waveformScrubbingNode)
}
strongSelf.waveformScrubbingNode?.frame = CGRect(origin: CGPoint(x: 43.0, y: -1.0), size: CGSize(width: boundingWidth - 41.0, height: 12.0))
strongSelf.waveformScrubbingNode?.frame = CGRect(origin: CGPoint(x: 57.0, y: 1.0), size: CGSize(width: boundingWidth - 60.0, height: 15.0))
let waveformColor: UIColor
if incoming {
if consumableContentIcon != nil {
@ -679,7 +682,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
strongSelf.waveformNode.displaysAsynchronously = !presentationData.isPreview
strongSelf.statusNode?.displaysAsynchronously = !presentationData.isPreview
strongSelf.statusNode?.frame = progressFrame
strongSelf.playbackAudioLevelView?.frame = progressFrame.insetBy(dx: -20.0, dy: -20.0)
strongSelf.playbackAudioLevelView?.frame = progressFrame.insetBy(dx: -12.0, dy: -12.0)
strongSelf.progressFrame = progressFrame
strongSelf.streamingCacheStatusFrame = streamingCacheStatusFrame
strongSelf.fileIconImage = fileIconImage
@ -860,19 +863,35 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
let statusNode = SemanticStatusNode(backgroundNodeColor: backgroundNodeColor, foregroundNodeColor: foregroundNodeColor)
self.statusNode = statusNode
statusNode.frame = progressFrame
if self.playbackAudioLevelView == nil, false {
let playbackAudioLevelView = VoiceBlobView(frame: progressFrame.insetBy(dx: -20.0, dy: -20.0))
playbackAudioLevelView.setColor(presentationData.theme.theme.chat.inputPanel.actionControlFillColor)
self.playbackAudioLevelView = playbackAudioLevelView
self.view.addSubview(playbackAudioLevelView)
}
self.addSubnode(statusNode)
} else if let statusNode = self.statusNode {
statusNode.backgroundNodeColor = backgroundNodeColor
}
if state != .none && isVoice && self.playbackAudioLevelView == nil {
let blobFrame = progressFrame.insetBy(dx: -12.0, dy: -12.0)
let playbackAudioLevelView = VoiceBlobView(
frame: blobFrame,
maxLevel: 0.3,
smallBlobRange: (0, 0),
mediumBlobRange: (0.7, 0.8),
bigBlobRange: (0.8, 0.9)
)
self.playbackAudioLevelView = playbackAudioLevelView
self.view.addSubview(playbackAudioLevelView)
let maskRect = CGRect(origin: .zero, size: blobFrame.size)
let playbackMaskLayer = CAShapeLayer()
playbackMaskLayer.frame = maskRect
playbackMaskLayer.fillRule = .evenOdd
let maskPath = UIBezierPath()
maskPath.append(UIBezierPath(roundedRect: maskRect.insetBy(dx: 12, dy: 12), cornerRadius: 22))
maskPath.append(UIBezierPath(rect: maskRect))
playbackMaskLayer.path = maskPath.cgPath
playbackAudioLevelView.layer.mask = playbackMaskLayer
}
self.playbackAudioLevelView?.setColor(presentationData.theme.theme.chat.inputPanel.actionControlFillColor)
if streamingState != .none && self.streamingStatusNode == nil {
let streamingStatusNode = RadialStatusNode(backgroundNodeColor: .clear)
self.streamingStatusNode = streamingStatusNode
@ -893,6 +912,13 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
statusNode?.removeFromSupernode()
}
})
switch state {
case .pause:
self.playbackAudioLevelView?.startAnimating()
default:
self.playbackAudioLevelView?.stopAnimating()
}
}
if let streamingStatusNode = self.streamingStatusNode {

View file

@ -782,9 +782,9 @@ public class ChatMessageItemView: ListViewItemNode {
case .requestPhone:
item.controllerInteraction.shareAccountContact()
case .openWebApp:
item.controllerInteraction.requestMessageActionCallback(item.message.id, nil, true)
case let .callback(data):
item.controllerInteraction.requestMessageActionCallback(item.message.id, data, false)
item.controllerInteraction.requestMessageActionCallback(item.message.id, nil, true, false)
case let .callback(requiresPassword, data):
item.controllerInteraction.requestMessageActionCallback(item.message.id, data, false, requiresPassword)
case let .switchInline(samePeer, query):
var botPeer: Peer?

View file

@ -216,7 +216,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, _ in
self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame)
}, openMessageContextActions: { _, _, _, _ in
}, navigateToMessage: { _, _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in
}, navigateToMessage: { _, _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in
self?.openUrl(url)
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {

View file

@ -101,7 +101,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
super.init()
self.addSubnode(self.deleteButton)
self.deleteButton.addSubnode(binNode)
self.deleteButton.addSubnode(self.binNode)
self.addSubnode(self.waveformBackgroundNode)
self.addSubnode(self.sendButton)
self.addSubnode(self.waveformScubberNode)
@ -190,8 +190,8 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
let panelHeight = defaultHeight(metrics: metrics)
transition.updateFrame(node: self.deleteButton, frame: CGRect(origin: CGPoint(x: leftInset + 2.0 - UIScreenPixel, y: panelHeight - 44 + 1), size: CGSize(width: 40.0, height: 40)))
transition.updateFrame(node: self.sendButton, frame: CGRect(origin: CGPoint(x: width - rightInset - 43.0 - UIScreenPixel, y: -UIScreenPixel), size: CGSize(width: 44.0, height: panelHeight)))
transition.updateFrame(node: self.deleteButton, frame: CGRect(origin: CGPoint(x: leftInset + 2.0 - UIScreenPixel, y: 1), size: CGSize(width: 40.0, height: 40)))
transition.updateFrame(node: self.sendButton, frame: CGRect(origin: CGPoint(x: width - rightInset - 43.0 - UIScreenPixel, y: 2 - UIScreenPixel), size: CGSize(width: 44.0, height: 44)))
self.binNode.frame = self.deleteButton.bounds
if let slowmodeState = interfaceState.slowmodeState, !interfaceState.isScheduledMessages {
@ -230,9 +230,10 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
self.prevInputPanelNode = nil
if let audioRecordingDotNode = prevTextInputPanelNode.audioRecordingDotNode {
audioRecordingDotNode.layer.animateScale(from: 1.0, to: 0.3, duration: 0.15, removeOnCompletion: false)
let startAlpha = CGFloat(audioRecordingDotNode.layer.presentation()?.opacity ?? 1.0)
audioRecordingDotNode.layer.removeAllAnimations()
audioRecordingDotNode.layer.animateAlpha(from: CGFloat(audioRecordingDotNode.layer.presentation()?.opacity ?? 1.0), to: 0.0, duration: 0.15, removeOnCompletion: false)
audioRecordingDotNode.layer.animateScale(from: 1.0, to: 0.3, duration: 0.15, removeOnCompletion: false)
audioRecordingDotNode.layer.animateAlpha(from: startAlpha, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
if let audioRecordingTimeNode = prevTextInputPanelNode.audioRecordingTimeNode {

View file

@ -238,7 +238,13 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto
}
private lazy var micDecoration: (UIView & TGModernConversationInputMicButtonDecoration) = {
let blobView = VoiceBlobView(frame: CGRect(origin: CGPoint(), size: CGSize(width: 220.0, height: 220.0)))
let blobView = VoiceBlobView(
frame: CGRect(origin: CGPoint(), size: CGSize(width: 220.0, height: 220.0)),
maxLevel: 4,
smallBlobRange: (0.45, 0.55),
mediumBlobRange: (0.52, 0.87),
bigBlobRange: (0.57, 1.00)
)
blobView.setColor(self.theme.chat.inputPanel.actionControlFillColor)
return blobView
}()

View file

@ -1060,13 +1060,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
}
animateDotAppearing = transition.isAnimated && !hideInfo
if let mediaRecordingState = mediaRecordingState, case .waitingForPreview = mediaRecordingState {
animateDotAppearing = false
}
audioRecordingDotNode.frame = CGRect(origin: CGPoint(x: leftInset + 2.0 - UIScreenPixel, y: panelHeight - 44 + 1), size: CGSize(width: 40.0, height: 40))
audioRecordingDotNode.frame = CGRect(origin: CGPoint(x: leftInset + 2.0 - UIScreenPixel, y: audioRecordingTimeNode.frame.midY - 20), size: CGSize(width: 40.0, height: 40))
if animateDotAppearing {
let dotStartScale: CGFloat = (audioRecordingDotNode.layer.presentation()?.value(forKeyPath: "transform.scale.x") as? CGFloat) ?? 1
audioRecordingDotNode.layer.animateScale(from: dotStartScale, to: 1, duration: 0.15, delay: 0, removeOnCompletion: false)
audioRecordingDotNode.layer.animateScale(from: 0.3, to: 1, duration: 0.15, delay: 0, removeOnCompletion: false)
if audioRecordingDotNode.layer.animation(forKey: "recording") == nil {
audioRecordingDotNode.layer.animateAlpha(from: CGFloat(audioRecordingDotNode.layer.presentation()?.opacity ?? 1), to: 1, duration: 0.15, delay: 0, completion: { [weak audioRecordingDotNode] finished in
audioRecordingDotNode.layer.animateAlpha(from: CGFloat(audioRecordingDotNode.layer.presentation()?.opacity ?? 0), to: 1, duration: 0.15, delay: 0, completion: { [weak audioRecordingDotNode] finished in
if finished {
let animation = CAKeyframeAnimation(keyPath: "opacity")
animation.values = [1.0 as NSNumber, 1.0 as NSNumber, 0.0 as NSNumber]

View file

@ -108,7 +108,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
var selectStickerImpl: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
self.controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, node, rect in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, node, rect in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
}, presentController: { _, _ in }, navigationController: {
return nil
}, chatControllerNode: {

View file

@ -2,11 +2,18 @@ import Foundation
import Display
import SwiftSignalKit
final class HeartbeatHaptic {
protocol EmojiHaptic {
var enabled: Bool { get set }
var active: Bool { get }
func start(time: Double)
}
final class HeartbeatHaptic: EmojiHaptic {
private var hapticFeedback = HapticFeedback()
private var timer: SwiftSignalKit.Timer?
private var time: Double = 0.0
var enabled = false {
var enabled: Bool = false {
didSet {
if !self.enabled {
self.reset()

View file

@ -60,7 +60,11 @@ final class InstantVideoController: LegacyController, StandalonePresentableContr
self?.durationValue.set(duration)
}
captureController.onDismiss = { [weak self] _, isCancelled in
self?.onDismiss?(isCancelled)
guard let strongSelf = self else { return }
if !strongSelf.dismissedVideo {
self?.dismissedVideo = true
self?.onDismiss?(isCancelled)
}
}
captureController.onStop = { [weak self] in
self?.onStop?()

View file

@ -76,7 +76,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
return false
}, sendBotContextResultAsGif: { _, _, _, _ in
return false
}, requestMessageActionCallback: { _, _, _ in
}, requestMessageActionCallback: { _, _, _, _ in
}, requestMessageActionUrlAuth: { _, _, _ in
}, activateSwitchInline: { _, _ in
}, openUrl: { _, _, _, _ in

View file

@ -0,0 +1,128 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import SyncCore
import TelegramPresentationData
import ActivityIndicator
import TextFormat
import AccountContext
import AlertUI
import PresentationDataUtils
import PasswordSetupUI
import Markdown
import PeerInfoUI
private func commitOwnershipTransferController(context: AccountContext, present: @escaping (ViewController, Any?) -> Void, commit: @escaping (String) -> Signal<MessageActionCallbackResult, MessageActionCallbackError>, completion: @escaping (MessageActionCallbackResult) -> Void) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var dismissImpl: (() -> Void)?
var proceedImpl: (() -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
let disposable = MetaDisposable()
let contentNode = ChannelOwnershipTransferAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?()
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.OwnershipTransfer_Transfer, action: {
proceedImpl?()
})])
contentNode.complete = {
proceedImpl?()
}
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
controller?.theme = AlertControllerTheme(presentationData: presentationData)
contentNode?.theme = presentationData.theme
})
controller.dismissed = {
presentationDataDisposable.dispose()
disposable.dispose()
}
dismissImpl = { [weak controller, weak contentNode] in
contentNode?.dismissInput()
controller?.dismissAnimated()
}
proceedImpl = { [weak contentNode] in
guard let contentNode = contentNode else {
return
}
contentNode.updateIsChecking(true)
disposable.set((commit(contentNode.password) |> deliverOnMainQueue).start(next: { result in
dismissImpl?()
}, error: { [weak contentNode] error in
var errorTextAndActions: (String, [TextAlertAction])?
switch error {
case .invalidPassword:
contentNode?.animateError()
case .limitExceeded:
errorTextAndActions = (presentationData.strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
case .userBlocked, .restricted:
errorTextAndActions = (presentationData.strings.Group_OwnershipTransfer_ErrorPrivacyRestricted, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
default:
errorTextAndActions = (presentationData.strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
}
contentNode?.updateIsChecking(false)
if let (text, actions) = errorTextAndActions {
dismissImpl?()
present(textAlertController(context: context, title: nil, text: text, actions: actions), nil)
}
}))
}
pushControllerImpl = { [weak controller] c in
controller?.push(c)
}
return controller
}
func ownershipTransferController(context: AccountContext, initialError: MessageActionCallbackError, present: @escaping (ViewController, Any?) -> Void, commit: @escaping (String) -> Signal<MessageActionCallbackResult, MessageActionCallbackError>, completion: @escaping (MessageActionCallbackResult) -> Void) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let theme = AlertControllerTheme(presentationData: presentationData)
var title: NSAttributedString? = NSAttributedString(string: presentationData.strings.OwnershipTransfer_SecurityCheck, font: Font.medium(presentationData.listsFontSize.itemListBaseFontSize), textColor: theme.primaryColor, paragraphAlignment: .center)
var text = presentationData.strings.OwnershipTransfer_SecurityRequirements
var actions: [TextAlertAction] = []
switch initialError {
case .requestPassword:
return commitOwnershipTransferController(context: context, present: present, commit: commit, completion: completion)
case .twoStepAuthTooFresh, .authSessionTooFresh:
text = text + presentationData.strings.OwnershipTransfer_ComeBackLater
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]
case .twoStepAuthMissing:
actions = [TextAlertAction(type: .genericAction, title: presentationData.strings.OwnershipTransfer_SetupTwoStepAuth, action: {
let controller = SetupTwoStepVerificationController(context: context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in
if shouldDismiss {
controller.dismiss()
}
})
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {})]
case .userBlocked, .restricted:
title = nil
text = presentationData.strings.Group_OwnershipTransfer_ErrorPrivacyRestricted
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]
default:
title = nil
text = presentationData.strings.Login_UnknownError
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]
}
let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor)
let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
return richTextAlertController(context: context, title: title, text: attributedText, actions: actions)
}

View file

@ -0,0 +1,73 @@
import Foundation
import Display
import SwiftSignalKit
private let impactTime: Double = 0.4
final class PeachHaptic: EmojiHaptic {
private var hapticFeedback = HapticFeedback()
private var timer: SwiftSignalKit.Timer?
private var time: Double = 0.0
var enabled: Bool = false {
didSet {
if !self.enabled {
self.reset()
}
}
}
var active: Bool {
return self.timer != nil
}
private func reset() {
if let timer = self.timer {
self.time = 0.0
timer.invalidate()
self.timer = nil
}
}
private func beat(time: Double) {
let epsilon = 0.1
if fabs(impactTime - time) < epsilon {
self.hapticFeedback.impact(.heavy)
}
}
func start(time: Double) {
self.hapticFeedback.prepareImpact()
if time > impactTime {
return
}
let startTime: Double = 0.0
let block = { [weak self] in
guard let strongSelf = self, strongSelf.enabled else {
return
}
strongSelf.time = startTime
strongSelf.beat(time: startTime)
strongSelf.timer = SwiftSignalKit.Timer(timeout: 0.2, repeat: true, completion: { [weak self] in
guard let strongSelf = self, strongSelf.enabled else {
return
}
strongSelf.time += 0.2
strongSelf.beat(time: strongSelf.time)
if strongSelf.time > impactTime {
strongSelf.reset()
strongSelf.time = 0.0
strongSelf.timer?.invalidate()
strongSelf.timer = nil
}
}, queue: Queue.mainQueue())
strongSelf.timer?.start()
}
block()
}
}

View file

@ -1825,7 +1825,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
return false
}, sendBotContextResultAsGif: { _, _, _, _ in
return false
}, requestMessageActionCallback: { _, _, _ in
}, requestMessageActionCallback: { _, _, _, _ in
}, requestMessageActionUrlAuth: { _, _, _ in
}, activateSwitchInline: { _, _ in
}, openUrl: { [weak self] url, concealed, external, _ in

View file

@ -327,7 +327,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
return false
}, sendBotContextResultAsGif: { _, _, _, _ in
return false
}, requestMessageActionCallback: { _, _, _ in
}, requestMessageActionCallback: { _, _, _, _ in
}, requestMessageActionUrlAuth: { _, _, _ in
}, activateSwitchInline: { _, _ in
}, openUrl: { [weak self] url, _, external, _ in

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