mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge commit '9b342df78d' into mac-videocalls-release
This commit is contained in:
commit
c4761bcb59
120 changed files with 6620 additions and 2435 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -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
|
|
@ -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
1
Random.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
LS3VfNVetXNy6mHmek8hegOh0wsvmJs0hcrc7PLA9eI=
|
||||
|
|
@ -730,6 +730,7 @@ plist_fragment(
|
|||
<string>onionhttp</string>
|
||||
<string>ucbrowser</string>
|
||||
<string>dolphin</string>
|
||||
<string>instagram-stories</string>
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
@implementation Serialization
|
||||
|
||||
- (NSUInteger)currentLayer {
|
||||
return 116;
|
||||
return 118;
|
||||
}
|
||||
|
||||
- (id _Nullable)parseMessage:(NSData * _Nullable)data {
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@
|
|||
<string>onionhttp</string>
|
||||
<string>ucbrowser</string>
|
||||
<string>dolphin</string>
|
||||
<string>instagram-stories</string>
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@
|
|||
<string>onionhttp</string>
|
||||
<string>ucbrowser</string>
|
||||
<string>dolphin</string>
|
||||
<string>instagram-stories</string>
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
|
|
|
|||
BIN
Telegram/Telegram-iOS/Resources/Slot_Back_Idle.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_Back_Idle.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_Back_Win.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_Back_Win.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_Front_Pull.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_Front_Pull.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_L_7.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_L_7.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_L_7_Win.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_L_7_Win.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_L_Bar.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_L_Bar.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_L_Berries.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_L_Berries.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_L_Lemon.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_L_Lemon.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_L_Spinning.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_L_Spinning.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_M_7.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_M_7.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_M_7_Win.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_M_7_Win.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_M_Bar.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_M_Bar.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_M_Berries.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_M_Berries.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_M_Lemon.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_M_Lemon.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_M_Spinning.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_M_Spinning.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_R_7.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_R_7.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_R_7_Win.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_R_7_Win.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_R_Bar.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_R_Bar.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_R_Berries.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_R_Berries.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_R_Lemon.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_R_Lemon.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Slot_R_Spinning.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Slot_R_Spinning.tgs
Normal file
Binary file not shown.
|
|
@ -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.";
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ swift_library(
|
|||
"//submodules/YuvConversion:YuvConversion",
|
||||
"//submodules/GZip:GZip",
|
||||
"//submodules/rlottie:RLottieBinding",
|
||||
"//submodules/MediaResources:MediaResources",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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: {})
|
||||
}
|
||||
|
|
|
|||
76
submodules/AnimatedStickerNode/Sources/FitzModifier.swift
Normal file
76
submodules/AnimatedStickerNode/Sources/FitzModifier.swift
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -762,7 +762,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||
)
|
||||
)
|
||||
let location: SearchMessagesLocation
|
||||
location = .general
|
||||
location = .general(tags: nil)
|
||||
|
||||
updateSearchContext { _ in
|
||||
return (nil, true)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
@protocol TGModernConversationInputMicButtonDecoration <NSObject>
|
||||
|
||||
- (void)updateLevel:(CGFloat)level;
|
||||
- (void)tick:(CGFloat)level;
|
||||
- (void)setColor:(UIColor *)color;
|
||||
- (void)stopAnimating;
|
||||
- (void)startAnimating;
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
1
submodules/Opus/BUILD
vendored
1
submodules/Opus/BUILD
vendored
|
|
@ -15,6 +15,7 @@ objc_library(
|
|||
]),
|
||||
includes = [
|
||||
"Public",
|
||||
"Public/opus",
|
||||
],
|
||||
deps = [
|
||||
":opus_lib",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)"))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
})
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ public func checkOwnershipTranfserAvailability(postbox: Postbox, network: Networ
|
|||
}
|
||||
|> mapToSignal { updates -> Signal<Never, ChannelOwnershipTransferError> in
|
||||
accountStateManager.addUpdates(updates)
|
||||
return.complete()
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
164
submodules/TelegramCore/Sources/Countries.swift
Normal file
164
submodules/TelegramCore/Sources/Countries.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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! {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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?()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
128
submodules/TelegramUI/Sources/OwnershipTransferController.swift
Normal file
128
submodules/TelegramUI/Sources/OwnershipTransferController.swift
Normal 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)
|
||||
}
|
||||
73
submodules/TelegramUI/Sources/PeachHaptic.swift
Normal file
73
submodules/TelegramUI/Sources/PeachHaptic.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue