Merge branch 'master' into beta
1
.bazelrc
@ -6,6 +6,7 @@ build --cxxopt='-std=c++14'
|
|||||||
build --per_file_copt="third-party/webrtc/.*\.cpp$","@-std=c++14"
|
build --per_file_copt="third-party/webrtc/.*\.cpp$","@-std=c++14"
|
||||||
build --per_file_copt="third-party/webrtc/.*\.cc$","@-std=c++14"
|
build --per_file_copt="third-party/webrtc/.*\.cc$","@-std=c++14"
|
||||||
build --per_file_copt="third-party/webrtc/.*\.mm$","@-std=c++14"
|
build --per_file_copt="third-party/webrtc/.*\.mm$","@-std=c++14"
|
||||||
|
build --per_file_copt="submodules/LottieMeshSwift/LottieMeshBinding/Sources/.*\.mm$","@-std=c++14"
|
||||||
|
|
||||||
build --swiftcopt=-disallow-use-new-driver
|
build --swiftcopt=-disallow-use-new-driver
|
||||||
|
|
||||||
|
@ -268,6 +268,27 @@ filegroup(
|
|||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
alternate_icon_folders = [
|
||||||
|
"BlackIcon",
|
||||||
|
"BlackClassicIcon",
|
||||||
|
"BlackFilledIcon",
|
||||||
|
"BlueIcon",
|
||||||
|
"BlueClassicIcon",
|
||||||
|
"BlueFilledIcon",
|
||||||
|
"WhiteFilledIcon",
|
||||||
|
"New1",
|
||||||
|
"New2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[
|
||||||
|
filegroup(
|
||||||
|
name = "{}".format(name),
|
||||||
|
srcs = glob([
|
||||||
|
"Telegram-iOS/{}.alticon/*.png".format(name),
|
||||||
|
]),
|
||||||
|
) for name in alternate_icon_folders
|
||||||
|
]
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "LaunchScreen",
|
name = "LaunchScreen",
|
||||||
srcs = glob([
|
srcs = glob([
|
||||||
@ -351,7 +372,6 @@ plist_fragment(
|
|||||||
|
|
||||||
official_apple_pay_merchants = [
|
official_apple_pay_merchants = [
|
||||||
"merchant.ph.telegra.Telegraph",
|
"merchant.ph.telegra.Telegraph",
|
||||||
"merchant.yandex.ph.telegra.Telegraph",
|
|
||||||
"merchant.sberbank.ph.telegra.Telegraph",
|
"merchant.sberbank.ph.telegra.Telegraph",
|
||||||
"merchant.sberbank.test.ph.telegra.Telegraph",
|
"merchant.sberbank.test.ph.telegra.Telegraph",
|
||||||
"merchant.privatbank.test.telergramios",
|
"merchant.privatbank.test.telergramios",
|
||||||
@ -359,6 +379,7 @@ official_apple_pay_merchants = [
|
|||||||
"merchant.paymaster.test.telegramios",
|
"merchant.paymaster.test.telegramios",
|
||||||
"merchant.smartglocal.prod.telegramios",
|
"merchant.smartglocal.prod.telegramios",
|
||||||
"merchant.smartglocal.test.telegramios",
|
"merchant.smartglocal.test.telegramios",
|
||||||
|
"merchant.yoomoney.test.telegramios",
|
||||||
]
|
]
|
||||||
|
|
||||||
official_bundle_ids = [
|
official_bundle_ids = [
|
||||||
@ -1855,11 +1876,14 @@ ios_application(
|
|||||||
":VersionInfoPlist",
|
":VersionInfoPlist",
|
||||||
":UrlTypesInfoPlist",
|
":UrlTypesInfoPlist",
|
||||||
],
|
],
|
||||||
ipa_post_processor = ":AddAlternateIcons",
|
alternate_icons = [
|
||||||
|
":{}".format(name) for name in alternate_icon_folders
|
||||||
|
],
|
||||||
|
#ipa_post_processor = ":AddAlternateIcons",
|
||||||
resources = [
|
resources = [
|
||||||
":LaunchScreen",
|
":LaunchScreen",
|
||||||
":DefaultAppIcon",
|
":DefaultAppIcon",
|
||||||
":AdditionalIcons",
|
#":AdditionalIcons",
|
||||||
],
|
],
|
||||||
frameworks = [
|
frameworks = [
|
||||||
":MtProtoKitFramework",
|
":MtProtoKitFramework",
|
||||||
|
@ -326,7 +326,7 @@ private let gradientColors: [NSArray] = [
|
|||||||
[UIColor(rgb: 0xd669ed).cgColor, UIColor(rgb: 0xe0a2f3).cgColor],
|
[UIColor(rgb: 0xd669ed).cgColor, UIColor(rgb: 0xe0a2f3).cgColor],
|
||||||
]
|
]
|
||||||
|
|
||||||
private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId: Int64, letters: [String]) -> UIImage? {
|
private func avatarViewLettersImage(size: CGSize, peerId: PeerId, letters: [String]) -> UIImage? {
|
||||||
UIGraphicsBeginImageContextWithOptions(size, false, 2.0)
|
UIGraphicsBeginImageContextWithOptions(size, false, 2.0)
|
||||||
let context = UIGraphicsGetCurrentContext()
|
let context = UIGraphicsGetCurrentContext()
|
||||||
|
|
||||||
@ -334,7 +334,12 @@ private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId:
|
|||||||
context?.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
|
context?.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
|
||||||
context?.clip()
|
context?.clip()
|
||||||
|
|
||||||
let colorIndex = abs(Int(accountPeerId + peerId))
|
let colorIndex: Int
|
||||||
|
if peerId.namespace == .max {
|
||||||
|
colorIndex = 0
|
||||||
|
} else {
|
||||||
|
colorIndex = abs(Int(clamping: peerId.id._internalGetInt64Value()))
|
||||||
|
}
|
||||||
|
|
||||||
let colorsArray = gradientColors[colorIndex % gradientColors.count]
|
let colorsArray = gradientColors[colorIndex % gradientColors.count]
|
||||||
var locations: [CGFloat] = [1.0, 0.0]
|
var locations: [CGFloat] = [1.0, 0.0]
|
||||||
@ -368,11 +373,11 @@ private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId:
|
|||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
|
|
||||||
private func avatarImage(path: String?, peerId: Int64, accountPeerId: Int64, letters: [String], size: CGSize) -> UIImage {
|
private func avatarImage(path: String?, peerId: PeerId, letters: [String], size: CGSize) -> UIImage {
|
||||||
if let path = path, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) {
|
if let path = path, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) {
|
||||||
return roundImage
|
return roundImage
|
||||||
} else {
|
} else {
|
||||||
return avatarViewLettersImage(size: size, peerId: peerId, accountPeerId: accountPeerId, letters: letters)!
|
return avatarViewLettersImage(size: size, peerId: peerId, letters: letters)!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,48 +399,27 @@ private func storeTemporaryImage(path: String) -> String {
|
|||||||
private func peerAvatar(mediaBox: MediaBox, accountPeerId: PeerId, peer: Peer) -> INImage? {
|
private func peerAvatar(mediaBox: MediaBox, accountPeerId: PeerId, peer: Peer) -> INImage? {
|
||||||
if let resource = smallestImageRepresentation(peer.profileImageRepresentations)?.resource, let path = mediaBox.completedResourcePath(resource) {
|
if let resource = smallestImageRepresentation(peer.profileImageRepresentations)?.resource, let path = mediaBox.completedResourcePath(resource) {
|
||||||
let cachedPath = mediaBox.cachedRepresentationPathForId(resource.id.stringRepresentation, representationId: "intents.png", keepDuration: .shortLived)
|
let cachedPath = mediaBox.cachedRepresentationPathForId(resource.id.stringRepresentation, representationId: "intents.png", keepDuration: .shortLived)
|
||||||
if let _ = fileSize(cachedPath), let data = try? Data(contentsOf: URL(fileURLWithPath: cachedPath), options: .alwaysMapped) {
|
if let _ = fileSize(cachedPath) {
|
||||||
do {
|
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
|
||||||
} catch {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let image = avatarImage(path: path, peerId: peer.id.toInt64(), accountPeerId: accountPeerId.toInt64(), letters: peer.displayLetters, size: CGSize(width: 50.0, height: 50.0))
|
let image = avatarImage(path: path, peerId: peer.id, letters: peer.displayLetters, size: CGSize(width: 50.0, height: 50.0))
|
||||||
if let data = image.pngData() {
|
if let data = image.pngData() {
|
||||||
let _ = try? data.write(to: URL(fileURLWithPath: cachedPath), options: .atomic)
|
let _ = try? data.write(to: URL(fileURLWithPath: cachedPath), options: .atomic)
|
||||||
}
|
}
|
||||||
do {
|
|
||||||
//let data = try Data(contentsOf: URL(fileURLWithPath: cachedPath), options: .alwaysMapped)
|
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||||
//return INImage(imageData: data)
|
|
||||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
|
||||||
} catch {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedPath = mediaBox.cachedRepresentationPathForId("lettersAvatar-\(peer.displayLetters.joined(separator: ","))", representationId: "intents.png", keepDuration: .shortLived)
|
let cachedPath = mediaBox.cachedRepresentationPathForId("lettersAvatar-\(peer.displayLetters.joined(separator: ","))", representationId: "intents.png", keepDuration: .shortLived)
|
||||||
if let _ = fileSize(cachedPath) {
|
if let _ = fileSize(cachedPath) {
|
||||||
do {
|
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||||
//let data = try Data(contentsOf: URL(fileURLWithPath: cachedPath), options: [])
|
|
||||||
//return INImage(imageData: data)
|
|
||||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
|
||||||
} catch {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let image = avatarImage(path: nil, peerId: peer.id.toInt64(), accountPeerId: accountPeerId.toInt64(), letters: peer.displayLetters, size: CGSize(width: 50.0, height: 50.0))
|
let image = avatarImage(path: nil, peerId: peer.id, letters: peer.displayLetters, size: CGSize(width: 50.0, height: 50.0))
|
||||||
if let data = image.pngData() {
|
if let data = image.pngData() {
|
||||||
let _ = try? data.write(to: URL(fileURLWithPath: cachedPath), options: .atomic)
|
let _ = try? data.write(to: URL(fileURLWithPath: cachedPath), options: .atomic)
|
||||||
}
|
}
|
||||||
do {
|
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||||
//let data = try Data(contentsOf: URL(fileURLWithPath: cachedPath), options: .alwaysMapped)
|
|
||||||
//return INImage(imageData: data)
|
|
||||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
|
||||||
} catch {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,17 +598,40 @@ private final class NotificationServiceHandler {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = (self.accountManager.currentAccountRecord(allocateIfNotExists: false)
|
let _ = (self.accountManager.accountRecords()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOn(self.queue)).start(next: { [weak self] records in
|
|> deliverOn(self.queue)).start(next: { [weak self] records in
|
||||||
guard let strongSelf = self, let record = records else {
|
var recordId: AccountRecordId?
|
||||||
|
var isCurrentAccount: Bool = false
|
||||||
|
|
||||||
|
if let keyId = notificationPayloadKeyId(data: payloadData) {
|
||||||
|
outer: for listRecord in records.records {
|
||||||
|
for attribute in listRecord.attributes {
|
||||||
|
if case let .backupData(backupData) = attribute {
|
||||||
|
if let notificationEncryptionKeyId = backupData.data?.notificationEncryptionKeyId {
|
||||||
|
if keyId == notificationEncryptionKeyId {
|
||||||
|
recordId = listRecord.id
|
||||||
|
isCurrentAccount = records.currentRecord?.id == listRecord.id
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let strongSelf = self, let recordId = recordId else {
|
||||||
|
let content = NotificationContent()
|
||||||
|
updateCurrentContent(content)
|
||||||
|
completed()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = (standaloneStateManager(
|
let _ = (standaloneStateManager(
|
||||||
accountManager: strongSelf.accountManager,
|
accountManager: strongSelf.accountManager,
|
||||||
networkArguments: networkArguments,
|
networkArguments: networkArguments,
|
||||||
id: record.0,
|
id: recordId,
|
||||||
encryptionParameters: strongSelf.encryptionParameters,
|
encryptionParameters: strongSelf.encryptionParameters,
|
||||||
rootPath: rootPath,
|
rootPath: rootPath,
|
||||||
auxiliaryMethods: accountAuxiliaryMethods
|
auxiliaryMethods: accountAuxiliaryMethods
|
||||||
@ -634,6 +641,8 @@ private final class NotificationServiceHandler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let stateManager = stateManager else {
|
guard let stateManager = stateManager else {
|
||||||
|
let content = NotificationContent()
|
||||||
|
updateCurrentContent(content)
|
||||||
completed()
|
completed()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -642,18 +651,31 @@ private final class NotificationServiceHandler {
|
|||||||
strongSelf.notificationKeyDisposable.set((existingMasterNotificationsKey(postbox: stateManager.postbox)
|
strongSelf.notificationKeyDisposable.set((existingMasterNotificationsKey(postbox: stateManager.postbox)
|
||||||
|> deliverOn(strongSelf.queue)).start(next: { notificationsKey in
|
|> deliverOn(strongSelf.queue)).start(next: { notificationsKey in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
|
let content = NotificationContent()
|
||||||
|
updateCurrentContent(content)
|
||||||
|
completed()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let notificationsKey = notificationsKey else {
|
guard let notificationsKey = notificationsKey else {
|
||||||
|
let content = NotificationContent()
|
||||||
|
updateCurrentContent(content)
|
||||||
completed()
|
completed()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let decryptedPayload = decryptedNotificationPayload(key: notificationsKey, data: payloadData) else {
|
guard let decryptedPayload = decryptedNotificationPayload(key: notificationsKey, data: payloadData) else {
|
||||||
|
let content = NotificationContent()
|
||||||
|
updateCurrentContent(content)
|
||||||
completed()
|
completed()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let payloadJson = try? JSONSerialization.jsonObject(with: decryptedPayload, options: []) as? [String: Any] else {
|
guard let payloadJson = try? JSONSerialization.jsonObject(with: decryptedPayload, options: []) as? [String: Any] else {
|
||||||
|
let content = NotificationContent()
|
||||||
|
updateCurrentContent(content)
|
||||||
completed()
|
completed()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -679,6 +701,10 @@ private final class NotificationServiceHandler {
|
|||||||
if let channelIdValue = Int64(channelIdString) {
|
if let channelIdValue = Int64(channelIdString) {
|
||||||
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelIdValue))
|
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelIdValue))
|
||||||
}
|
}
|
||||||
|
} else if let encryptionIdString = payloadJson["encryption_id"] as? String {
|
||||||
|
if let encryptionIdValue = Int64(encryptionIdString) {
|
||||||
|
peerId = PeerId(namespace: Namespaces.Peer.SecretChat, id: PeerId.Id._internalFromInt64Value(encryptionIdValue))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Action {
|
enum Action {
|
||||||
@ -751,7 +777,7 @@ private final class NotificationServiceHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
content.userInfo["peerId"] = "\(peerId.toInt64())"
|
content.userInfo["peerId"] = "\(peerId.toInt64())"
|
||||||
content.userInfo["accountId"] = "\(record.0.int64)"
|
content.userInfo["accountId"] = "\(recordId.int64)"
|
||||||
|
|
||||||
if let silentString = payloadJson["silent"] as? String {
|
if let silentString = payloadJson["silent"] as? String {
|
||||||
if let silentValue = Int(silentString), silentValue != 0 {
|
if let silentValue = Int(silentString), silentValue != 0 {
|
||||||
@ -904,7 +930,9 @@ private final class NotificationServiceHandler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
content.badge = Int(value.0)
|
if isCurrentAccount {
|
||||||
|
content.badge = Int(value.0)
|
||||||
|
}
|
||||||
|
|
||||||
if let image = mediaAttachment as? TelegramMediaImage, let resource = largestImageRepresentation(image.representations)?.resource {
|
if let image = mediaAttachment as? TelegramMediaImage, let resource = largestImageRepresentation(image.representations)?.resource {
|
||||||
if let mediaData = mediaData {
|
if let mediaData = mediaData {
|
||||||
@ -1053,7 +1081,9 @@ private final class NotificationServiceHandler {
|
|||||||
)
|
)
|
||||||
|> deliverOn(strongSelf.queue)).start(next: { value in
|
|> deliverOn(strongSelf.queue)).start(next: { value in
|
||||||
var content = NotificationContent()
|
var content = NotificationContent()
|
||||||
content.badge = Int(value.0)
|
if isCurrentAccount {
|
||||||
|
content.badge = Int(value.0)
|
||||||
|
}
|
||||||
|
|
||||||
updateCurrentContent(content)
|
updateCurrentContent(content)
|
||||||
|
|
||||||
@ -1096,7 +1126,9 @@ private final class NotificationServiceHandler {
|
|||||||
)
|
)
|
||||||
|> deliverOn(strongSelf.queue)).start(next: { value in
|
|> deliverOn(strongSelf.queue)).start(next: { value in
|
||||||
var content = NotificationContent()
|
var content = NotificationContent()
|
||||||
content.badge = Int(value.0)
|
if isCurrentAccount {
|
||||||
|
content.badge = Int(value.0)
|
||||||
|
}
|
||||||
|
|
||||||
updateCurrentContent(content)
|
updateCurrentContent(content)
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 737 B After Width: | Height: | Size: 737 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 749 B After Width: | Height: | Size: 749 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 715 B After Width: | Height: | Size: 715 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 665 B After Width: | Height: | Size: 665 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 889 B After Width: | Height: | Size: 889 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 917 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
BIN
Telegram/Telegram-iOS/Resources/Requests.tgs
Normal file
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
@ -4097,6 +4097,7 @@ Unused sets are archived when you add more.";
|
|||||||
|
|
||||||
"ChatSettings.AutoDownloadSettings.TypePhoto" = "Photos";
|
"ChatSettings.AutoDownloadSettings.TypePhoto" = "Photos";
|
||||||
"ChatSettings.AutoDownloadSettings.TypeVideo" = "Videos (%@)";
|
"ChatSettings.AutoDownloadSettings.TypeVideo" = "Videos (%@)";
|
||||||
|
"ChatSettings.AutoDownloadSettings.TypeMedia" = "Media (%@)";
|
||||||
"ChatSettings.AutoDownloadSettings.TypeFile" = "Files (%@)";
|
"ChatSettings.AutoDownloadSettings.TypeFile" = "Files (%@)";
|
||||||
"ChatSettings.AutoDownloadSettings.OffForAll" = "Disabled";
|
"ChatSettings.AutoDownloadSettings.OffForAll" = "Disabled";
|
||||||
"ChatSettings.AutoDownloadSettings.Delimeter" = ", ";
|
"ChatSettings.AutoDownloadSettings.Delimeter" = ", ";
|
||||||
@ -6706,6 +6707,8 @@ Sorry for the inconvenience.";
|
|||||||
"Activity.ChoosingSticker" = "choosing sticker";
|
"Activity.ChoosingSticker" = "choosing sticker";
|
||||||
"DialogList.SingleChoosingStickerSuffix" = "%@ is choosing sticker";
|
"DialogList.SingleChoosingStickerSuffix" = "%@ is choosing sticker";
|
||||||
|
|
||||||
|
"Activity.TappingInteractiveEmoji" = "tapping on %@";
|
||||||
|
|
||||||
"WallpaperPreview.Animate" = "Animate";
|
"WallpaperPreview.Animate" = "Animate";
|
||||||
"WallpaperPreview.AnimateDescription" = "Colors will move when you send messages";
|
"WallpaperPreview.AnimateDescription" = "Colors will move when you send messages";
|
||||||
|
|
||||||
@ -6781,13 +6784,7 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"SponsoredMessageMenu.Info" = "What are sponsored\nmessages?";
|
"SponsoredMessageMenu.Info" = "What are sponsored\nmessages?";
|
||||||
"SponsoredMessageInfoScreen.Title" = "What are sponsored messages?";
|
"SponsoredMessageInfoScreen.Title" = "What are sponsored messages?";
|
||||||
"SponsoredMessageInfoScreen.Text" = "Unlike other apps, Telegram never uses your private data to target ads. You are seeing this message only because someone chose this public one-to many channel as a space to promote their messages. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored message.
|
"SponsoredMessageInfoScreen.Text" = "Unlike other apps, Telegram never uses your private data to target ads. You are seeing this message only because someone chose this public one-to many channel as a space to promote their messages. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored message.\n\nUnline other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can't spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible adverticers at:\n[url]\nAds should no longer be synonymous with abuse of user privacy. Let us redefine how a tech compony should operate — together.";
|
||||||
|
|
||||||
Unline other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can't spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.
|
|
||||||
|
|
||||||
Telegram offers free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible adverticers at:
|
|
||||||
[url]
|
|
||||||
Ads should no longer be synonymous with abuse of user privacy. Let us redefine how a tech compony should operate — together.";
|
|
||||||
"SponsoredMessageInfo.Action" = "Learn More";
|
"SponsoredMessageInfo.Action" = "Learn More";
|
||||||
"SponsoredMessageInfo.Url" = "https://telegram.org/ads";
|
"SponsoredMessageInfo.Url" = "https://telegram.org/ads";
|
||||||
|
|
||||||
@ -6866,3 +6863,97 @@ Ads should no longer be synonymous with abuse of user privacy. Let us redefine h
|
|||||||
"MediaPicker.JpegConversionText" = "Do you want to convert photos to JPEG?";
|
"MediaPicker.JpegConversionText" = "Do you want to convert photos to JPEG?";
|
||||||
"MediaPicker.KeepHeic" = "Keep HEIC";
|
"MediaPicker.KeepHeic" = "Keep HEIC";
|
||||||
"MediaPicker.ConvertToJpeg" = "Convert to JPEG";
|
"MediaPicker.ConvertToJpeg" = "Convert to JPEG";
|
||||||
|
|
||||||
|
"GroupInfo.MemberRequests" = "Member Requests";
|
||||||
|
|
||||||
|
"InviteLink.Create.RequestApproval" = "Request Admin Approval";
|
||||||
|
"InviteLink.Create.RequestApprovalOffInfoGroup" = "New users will be able to join the group without being approved by the admins.";
|
||||||
|
"InviteLink.Create.RequestApprovalOffInfoChannel" = "New users will be able to join the channel without being approved by the admins.";
|
||||||
|
"InviteLink.Create.RequestApprovalOnInfoGroup" = "New users will be able to join the group only after having been approved by the admins.";
|
||||||
|
"InviteLink.Create.RequestApprovalOnInfoChannel" = "New users will be able to join the channel only after having been approved by the admins.";
|
||||||
|
|
||||||
|
"MemberRequests.Title" = "Member Requests";
|
||||||
|
"MemberRequests.DescriptionGroup" = "Some [additional links]() are set up to accept requests to join the group.";
|
||||||
|
"MemberRequests.DescriptionChannel" = "Some [additional links]() are set up to accept requests to join the channel.";
|
||||||
|
|
||||||
|
"MemberRequests.PeopleRequested_1" = "%@ requested to join";
|
||||||
|
"MemberRequests.PeopleRequested_2" = "%@ requested to join";
|
||||||
|
"MemberRequests.PeopleRequested_3_10" = "%@ requested to join";
|
||||||
|
"MemberRequests.PeopleRequested_many" = "%@ requested to join";
|
||||||
|
"MemberRequests.PeopleRequested_any" = "%@ requested to join";
|
||||||
|
|
||||||
|
"MemberRequests.PeopleRequestedShort_1" = "%@ requested";
|
||||||
|
"MemberRequests.PeopleRequestedShort_2" = "%@ requested";
|
||||||
|
"MemberRequests.PeopleRequestedShort_3_10" = "%@ requested";
|
||||||
|
"MemberRequests.PeopleRequestedShort_many" = "%@ requested";
|
||||||
|
"MemberRequests.PeopleRequestedShort_any" = "%@ requested";
|
||||||
|
|
||||||
|
"MemberRequests.AddToGroup" = "Add to Group";
|
||||||
|
"MemberRequests.AddToChannel" = "Add to Channel";
|
||||||
|
"MemberRequests.Dismiss" = "Dismiss";
|
||||||
|
|
||||||
|
"MemberRequests.UserAddedToGroup" = "%@ has been added to the group.";
|
||||||
|
"MemberRequests.UserAddedToChannel" = "%@ has been added to the channel.";
|
||||||
|
|
||||||
|
"MemberRequests.NoRequests" = "No Member Requests";
|
||||||
|
"MemberRequests.NoRequestsDescriptionGroup" = "You have no pending requests to join the group.";
|
||||||
|
"MemberRequests.NoRequestsDescriptionChannel" = "You have no pending requests to join the channel.";
|
||||||
|
|
||||||
|
"Conversation.RequestsToJoin_1" = "%@ Request to Join";
|
||||||
|
"Conversation.RequestsToJoin_2" = "%@ Requests to Join";
|
||||||
|
"Conversation.RequestsToJoin_3_10" = "%@ Requests to Join";
|
||||||
|
"Conversation.RequestsToJoin_many" = "%@ Requests to Join";
|
||||||
|
"Conversation.RequestsToJoin_any" = "%@ Requests to Join";
|
||||||
|
|
||||||
|
"MemberRequests.RequestToJoinGroup" = "Request to Join Group";
|
||||||
|
"MemberRequests.RequestToJoinChannel" = "Request to Join Channel";
|
||||||
|
|
||||||
|
"MemberRequests.RequestToJoinDescriptionGroup" = "This group accepts new members only after they are approved by its admins.";
|
||||||
|
"MemberRequests.RequestToJoinDescriptionChannel" = "This channel accepts new subscribers only after they are approved by its admins.";
|
||||||
|
|
||||||
|
"MemberRequests.RequestToJoinSent" = "Request to join Sent";
|
||||||
|
"MemberRequests.RequestToJoinSentDescriptionGroup" = "You will be added to the group once it admins approve your request.";
|
||||||
|
"MemberRequests.RequestToJoinSentDescriptionChannel" = "You will be added to the channel once it admins approve your request.";
|
||||||
|
|
||||||
|
"Notification.JoinedChatByRequestYou" = "Your request to join the channel was approved";
|
||||||
|
"Notification.JoinedGroupByRequestYou" = "Your request to join the group was approved";
|
||||||
|
"Notification.JoinedGroupByRequest" = "%@ was accepted to the group chat";
|
||||||
|
|
||||||
|
"Notification.JoinedGroupByLinkYou" = "You joined the group via invite link";
|
||||||
|
|
||||||
|
"InviteLink.InviteLinkForwardTooltip.Chat.One" = "Invite link forwarded to **%@**";
|
||||||
|
"InviteLink.InviteLinkForwardTooltip.TwoChats.One" = "Invite link forwarded to **%@** and **%@**";
|
||||||
|
"InviteLink.InviteLinkForwardTooltip.ManyChats.One" = "Invite link forwarded to **%@** and %@ others";
|
||||||
|
"InviteLink.InviteLinkForwardTooltip.SavedMessages.One" = "Invite link forwarded to **Saved Messages**";
|
||||||
|
|
||||||
|
"Conversation.RequestToJoinChannel" = "REQUEST TO JOIN";
|
||||||
|
"Conversation.RequestToJoinGroup" = "REQUEST TO JOIN";
|
||||||
|
|
||||||
|
"Channel.AdminLog.JoinedViaRequest" = "%1$@ joined via invite link %2$@, approved by %3$@";
|
||||||
|
|
||||||
|
"Appearance.NightTheme" = "Night Mode";
|
||||||
|
|
||||||
|
"Map.ETADays_0" = "%@ days";
|
||||||
|
"Map.ETADays_1" = "%@ day";
|
||||||
|
"Map.ETADays_2" = "%@ days";
|
||||||
|
"Map.ETADays_3_10" = "%@ days";
|
||||||
|
"Map.ETADays_many" = "%@ days";
|
||||||
|
"Map.ETADays_any" = "%@ days";
|
||||||
|
|
||||||
|
"ChatSettings.UseLessDataForCalls" = "Use Less Data for Calls";
|
||||||
|
|
||||||
|
"Time.JustNow" = "just now";
|
||||||
|
"Time.MinutesAgo_0" = "%@ minutes ago"; //three to ten
|
||||||
|
"Time.MinutesAgo_1" = "%@ minute ago"; //one
|
||||||
|
"Time.MinutesAgo_2" = "%@ minutes ago"; //two
|
||||||
|
"Time.MinutesAgo_3_10" = "%@ minutes ago"; //three to ten
|
||||||
|
"Time.MinutesAgo_many" = "%@ minutes ago"; // more than ten
|
||||||
|
"Time.MinutesAgo_any" = "%@ minutes ago"; // more than ten
|
||||||
|
"Time.HoursAgo_0" = "%@ hours ago";
|
||||||
|
"Time.HoursAgo_1" = "%@ hour ago";
|
||||||
|
"Time.HoursAgo_2" = "%@ hours ago";
|
||||||
|
"Time.HoursAgo_3_10" = "%@ hours ago";
|
||||||
|
"Time.HoursAgo_any" = "%@ hours ago";
|
||||||
|
"Time.HoursAgo_many" = "%@ hours ago";
|
||||||
|
"Time.HoursAgo_0" = "%@ hours ago";
|
||||||
|
"Time.AtDate" = "last seen %@";
|
||||||
|
@ -129,20 +129,4 @@ def generate(build_environment: BuildEnvironment, disable_extensions, disable_pr
|
|||||||
|
|
||||||
xcodeproj_path = '{project}/{target}.xcodeproj'.format(project=project_path, target=app_target)
|
xcodeproj_path = '{project}/{target}.xcodeproj'.format(project=project_path, target=app_target)
|
||||||
|
|
||||||
bazel_build_settings_path = '{}/.tulsi/Scripts/bazel_build_settings.py'.format(xcodeproj_path)
|
|
||||||
|
|
||||||
with open(bazel_build_settings_path, 'rb') as bazel_build_settings:
|
|
||||||
bazel_build_settings_contents = bazel_build_settings.read().decode('utf-8')
|
|
||||||
bazel_build_settings_contents = bazel_build_settings_contents.replace(
|
|
||||||
'BUILD_SETTINGS = BazelBuildSettings(',
|
|
||||||
'import os\nBUILD_SETTINGS = BazelBuildSettings('
|
|
||||||
)
|
|
||||||
bazel_build_settings_contents = bazel_build_settings_contents.replace(
|
|
||||||
'\'--cpu=ios_arm64\'',
|
|
||||||
'\'--cpu=ios_arm64\'.replace(\'ios_arm64\', \'ios_sim_arm64\' if os.environ.get(\'EFFECTIVE_PLATFORM_NAME\') '
|
|
||||||
'== \'-iphonesimulator\' else \'ios_arm64\')'
|
|
||||||
)
|
|
||||||
with open(bazel_build_settings_path, 'wb') as bazel_build_settings:
|
|
||||||
bazel_build_settings.write(bazel_build_settings_contents.encode('utf-8'))
|
|
||||||
|
|
||||||
call_executable(['open', xcodeproj_path])
|
call_executable(['open', xcodeproj_path])
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 8c8f4661dba2bbe8578ae42b8ab7001d27357575
|
Subproject commit 03c89782e9a15d467c7e036ee36f9adb6bdda910
|
@ -1 +1 @@
|
|||||||
Subproject commit 01d37ab862350cb33cbae25cf6622bf534df264f
|
Subproject commit ec7dd9ddf4b73dedb02df827b7ab3b2cbb1f2ac0
|
@ -20,6 +20,7 @@ swift_library(
|
|||||||
"//submodules/Postbox:Postbox",
|
"//submodules/Postbox:Postbox",
|
||||||
"//submodules/TelegramCore:TelegramCore",
|
"//submodules/TelegramCore:TelegramCore",
|
||||||
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
|
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
|
||||||
|
"//submodules/MeshAnimationCache:MeshAnimationCache"
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -10,6 +10,7 @@ import AsyncDisplayKit
|
|||||||
import Display
|
import Display
|
||||||
import DeviceLocationManager
|
import DeviceLocationManager
|
||||||
import TemporaryCachedPeerDataManager
|
import TemporaryCachedPeerDataManager
|
||||||
|
import MeshAnimationCache
|
||||||
|
|
||||||
public final class TelegramApplicationOpenUrlCompletion {
|
public final class TelegramApplicationOpenUrlCompletion {
|
||||||
public let completion: (Bool) -> Void
|
public let completion: (Bool) -> Void
|
||||||
@ -736,6 +737,7 @@ public protocol AccountContext: AnyObject {
|
|||||||
var currentAppConfiguration: Atomic<AppConfiguration> { get }
|
var currentAppConfiguration: Atomic<AppConfiguration> { get }
|
||||||
|
|
||||||
var cachedGroupCallContexts: AccountGroupCallContextCache { get }
|
var cachedGroupCallContexts: AccountGroupCallContextCache { get }
|
||||||
|
var meshAnimationCache: MeshAnimationCache { get }
|
||||||
|
|
||||||
func storeSecureIdPassword(password: String)
|
func storeSecureIdPassword(password: String)
|
||||||
func getStoredSecureIdPassword() -> String?
|
func getStoredSecureIdPassword() -> String?
|
||||||
|
@ -46,8 +46,8 @@ public func messageMediaFileCancelInteractiveFetch(context: AccountContext, mess
|
|||||||
context.fetchManager.cancelInteractiveFetches(category: fetchCategoryForFile(file), location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: file.resource)
|
context.fetchManager.cancelInteractiveFetches(category: fetchCategoryForFile(file), location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: file.resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func messageMediaImageInteractiveFetched(context: AccountContext, message: Message, image: TelegramMediaImage, resource: MediaResource, range: Range<Int>? = nil, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
|
public func messageMediaImageInteractiveFetched(context: AccountContext, message: Message, image: TelegramMediaImage, resource: MediaResource, range: Range<Int>? = nil, userInitiated: Bool = true, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
|
||||||
return messageMediaImageInteractiveFetched(fetchManager: context.fetchManager, messageId: message.id, messageReference: MessageReference(message), image: image, resource: resource, range: range, userInitiated: true, priority: .userInitiated, storeToDownloadsPeerType: storeToDownloadsPeerType)
|
return messageMediaImageInteractiveFetched(fetchManager: context.fetchManager, messageId: message.id, messageReference: MessageReference(message), image: image, resource: resource, range: range, userInitiated: userInitiated, priority: .userInitiated, storeToDownloadsPeerType: storeToDownloadsPeerType)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func messageMediaImageInteractiveFetched(fetchManager: FetchManager, messageId: MessageId, messageReference: MessageReference, image: TelegramMediaImage, resource: MediaResource, range: Range<Int>? = nil, userInitiated: Bool, priority: FetchManagerPriority, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
|
public func messageMediaImageInteractiveFetched(fetchManager: FetchManager, messageId: MessageId, messageReference: MessageReference, image: TelegramMediaImage, resource: MediaResource, range: Range<Int>? = nil, userInitiated: Bool, priority: FetchManagerPriority, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
|
||||||
|
@ -16,7 +16,7 @@ private func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func convertIndicatorColor(_ color: UIColor) -> UIColor {
|
private func convertIndicatorColor(_ color: UIColor) -> UIColor {
|
||||||
if color.isEqual(UIColor(rgb: 0x007ee5)) {
|
if color.isEqual(UIColor(rgb: 0x007aff)) {
|
||||||
return .gray
|
return .gray
|
||||||
} else if color.isEqual(UIColor(rgb: 0x2ea6ff)) {
|
} else if color.isEqual(UIColor(rgb: 0x2ea6ff)) {
|
||||||
return .white
|
return .white
|
||||||
|
@ -343,7 +343,7 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
|
|
||||||
let parameters: AvatarNodeParameters
|
let parameters: AvatarNodeParameters
|
||||||
|
|
||||||
if let peer = peer, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded) {
|
if let peer = peer, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, round: clipStyle == .round, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded) {
|
||||||
self.contents = nil
|
self.contents = nil
|
||||||
self.displaySuspended = true
|
self.displaySuspended = true
|
||||||
self.imageReady.set(self.imageNode.contentReady)
|
self.imageReady.set(self.imageNode.contentReady)
|
||||||
|
@ -453,18 +453,7 @@ private func formSupportApplePay(_ paymentForm: BotPaymentForm) -> Bool {
|
|||||||
guard let nativeProvider = paymentForm.nativeProvider else {
|
guard let nativeProvider = paymentForm.nativeProvider else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
let applePayProviders = Set<String>([
|
|
||||||
"stripe",
|
|
||||||
"sberbank",
|
|
||||||
"yandex",
|
|
||||||
"privatbank",
|
|
||||||
"tranzzo",
|
|
||||||
"paymaster",
|
|
||||||
"smartglocal",
|
|
||||||
])
|
|
||||||
if !applePayProviders.contains(nativeProvider.name) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
guard let nativeParamsData = nativeProvider.params.data(using: .utf8) else {
|
guard let nativeParamsData = nativeProvider.params.data(using: .utf8) else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
26
submodules/CalendarMessageScreen/BUILD
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "CalendarMessageScreen",
|
||||||
|
module_name = "CalendarMessageScreen",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display:Display",
|
||||||
|
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||||
|
"//submodules/ComponentFlow:ComponentFlow",
|
||||||
|
"//submodules/Postbox:Postbox",
|
||||||
|
"//submodules/TelegramCore:TelegramCore",
|
||||||
|
"//submodules/AccountContext:AccountContext",
|
||||||
|
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||||
|
"//submodules/PhotoResources:PhotoResources",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
1051
submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift
Normal file
@ -188,6 +188,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||||||
private let topStripeNode: ASDisplayNode
|
private let topStripeNode: ASDisplayNode
|
||||||
private let bottomStripeNode: ASDisplayNode
|
private let bottomStripeNode: ASDisplayNode
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
|
private let maskNode: ASImageNode
|
||||||
|
|
||||||
private let avatarNode: AvatarNode
|
private let avatarNode: AvatarNode
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
@ -206,6 +207,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.isLayerBacked = true
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.maskNode = ASImageNode()
|
||||||
|
|
||||||
self.topStripeNode = ASDisplayNode()
|
self.topStripeNode = ASDisplayNode()
|
||||||
self.topStripeNode.isLayerBacked = true
|
self.topStripeNode.isLayerBacked = true
|
||||||
|
|
||||||
@ -523,7 +526,9 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||||||
} else if last && strongSelf.bottomStripeNode.supernode != nil {
|
} else if last && strongSelf.bottomStripeNode.supernode != nil {
|
||||||
strongSelf.bottomStripeNode.removeFromSupernode()
|
strongSelf.bottomStripeNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
|
if strongSelf.maskNode.supernode != nil {
|
||||||
|
strongSelf.maskNode.removeFromSupernode()
|
||||||
|
}
|
||||||
transition.updateFrameAdditive(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)))
|
transition.updateFrameAdditive(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)))
|
||||||
case .blocks:
|
case .blocks:
|
||||||
if strongSelf.backgroundNode.supernode == nil {
|
if strongSelf.backgroundNode.supernode == nil {
|
||||||
@ -535,11 +540,18 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||||||
if strongSelf.bottomStripeNode.supernode == nil {
|
if strongSelf.bottomStripeNode.supernode == nil {
|
||||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||||
}
|
}
|
||||||
|
if strongSelf.maskNode.supernode == nil {
|
||||||
|
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
|
||||||
|
}
|
||||||
|
let hasCorners = itemListHasRoundedBlockLayout(params)
|
||||||
|
var hasTopCorners = false
|
||||||
|
var hasBottomCorners = false
|
||||||
switch neighbors.top {
|
switch neighbors.top {
|
||||||
case .sameSection(false):
|
case .sameSection(false):
|
||||||
strongSelf.topStripeNode.isHidden = true
|
strongSelf.topStripeNode.isHidden = true
|
||||||
default:
|
default:
|
||||||
strongSelf.topStripeNode.isHidden = false
|
hasTopCorners = true
|
||||||
|
strongSelf.topStripeNode.isHidden = hasCorners
|
||||||
}
|
}
|
||||||
let bottomStripeInset: CGFloat
|
let bottomStripeInset: CGFloat
|
||||||
switch neighbors.bottom {
|
switch neighbors.bottom {
|
||||||
@ -547,9 +559,14 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||||||
bottomStripeInset = leftInset
|
bottomStripeInset = leftInset
|
||||||
default:
|
default:
|
||||||
bottomStripeInset = 0.0
|
bottomStripeInset = 0.0
|
||||||
|
hasBottomCorners = true
|
||||||
|
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||||
|
|
||||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||||
|
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
|
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
|
||||||
transition.updateFrameAdditive(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - bottomStripeInset, height: separatorHeight)))
|
transition.updateFrameAdditive(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - bottomStripeInset, height: separatorHeight)))
|
||||||
}
|
}
|
||||||
|
@ -262,6 +262,12 @@ public final class CallListController: TelegramBaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if case .navigation = self.mode {
|
||||||
|
self.controllerNode.navigationBar = self.navigationBar
|
||||||
|
self.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
self.controllerNode.startNewCall = { [weak self] in
|
self.controllerNode.startNewCall = { [weak self] in
|
||||||
self?.beginCallImpl()
|
self?.beginCallImpl()
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ private func mappedInsertEntries(context: AccountContext, presentationData: Item
|
|||||||
return entries.map { entry -> ListViewInsertItem in
|
return entries.map { entry -> ListViewInsertItem in
|
||||||
switch entry.entry {
|
switch entry.entry {
|
||||||
case let .displayTab(_, text, value):
|
case let .displayTab(_, text, value):
|
||||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: true, sectionId: 0, style: .blocks, updated: { value in
|
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in
|
||||||
nodeInteraction.updateShowCallsTab(value)
|
nodeInteraction.updateShowCallsTab(value)
|
||||||
}), directionHint: entry.directionHint)
|
}), directionHint: entry.directionHint)
|
||||||
case let .displayTabInfo(_, text):
|
case let .displayTabInfo(_, text):
|
||||||
@ -136,7 +136,7 @@ private func mappedUpdateEntries(context: AccountContext, presentationData: Item
|
|||||||
return entries.map { entry -> ListViewUpdateItem in
|
return entries.map { entry -> ListViewUpdateItem in
|
||||||
switch entry.entry {
|
switch entry.entry {
|
||||||
case let .displayTab(_, text, value):
|
case let .displayTab(_, text, value):
|
||||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: true, sectionId: 0, style: .blocks, updated: { value in
|
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in
|
||||||
nodeInteraction.updateShowCallsTab(value)
|
nodeInteraction.updateShowCallsTab(value)
|
||||||
}), directionHint: entry.directionHint)
|
}), directionHint: entry.directionHint)
|
||||||
case let .displayTabInfo(_, text):
|
case let .displayTabInfo(_, text):
|
||||||
@ -177,6 +177,8 @@ final class CallListControllerNode: ASDisplayNode {
|
|||||||
return _ready.get()
|
return _ready.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
weak var navigationBar: NavigationBar?
|
||||||
|
|
||||||
var peerSelected: ((EnginePeer.Id) -> Void)?
|
var peerSelected: ((EnginePeer.Id) -> Void)?
|
||||||
var activateSearch: (() -> Void)?
|
var activateSearch: (() -> Void)?
|
||||||
var deletePeerChat: ((EnginePeer.Id) -> Void)?
|
var deletePeerChat: ((EnginePeer.Id) -> Void)?
|
||||||
@ -215,6 +217,8 @@ final class CallListControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
private let openGroupCallDisposable = MetaDisposable()
|
private let openGroupCallDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
private var previousContentOffset: ListViewVisibleContentOffset?
|
||||||
|
|
||||||
init(controller: CallListController, context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (EnginePeer.Id, Bool) -> Void, joinGroupCall: @escaping (EnginePeer.Id, EngineGroupCallDescription) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void) {
|
init(controller: CallListController, context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (EnginePeer.Id, Bool) -> Void, joinGroupCall: @escaping (EnginePeer.Id, EngineGroupCallDescription) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void) {
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -272,7 +276,7 @@ final class CallListControllerNode: ASDisplayNode {
|
|||||||
self.addSubnode(self.emptyButtonTextNode)
|
self.addSubnode(self.emptyButtonTextNode)
|
||||||
self.addSubnode(self.emptyButtonIconNode)
|
self.addSubnode(self.emptyButtonIconNode)
|
||||||
self.addSubnode(self.emptyButtonNode)
|
self.addSubnode(self.emptyButtonNode)
|
||||||
|
|
||||||
switch self.mode {
|
switch self.mode {
|
||||||
case .tab:
|
case .tab:
|
||||||
self.backgroundColor = presentationData.theme.chatList.backgroundColor
|
self.backgroundColor = presentationData.theme.chatList.backgroundColor
|
||||||
@ -607,6 +611,39 @@ final class CallListControllerNode: ASDisplayNode {
|
|||||||
strongSelf.updateEmptyPlaceholder(theme: state.presentationData.theme, strings: state.presentationData.strings, type: type, isHidden: !isEmpty)
|
strongSelf.updateEmptyPlaceholder(theme: state.presentationData.theme, strings: state.presentationData.strings, type: type, isHidden: !isEmpty)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
if case .navigation = mode {
|
||||||
|
self.listNode.itemNodeHitTest = { [weak self] point in
|
||||||
|
if let strongSelf = self {
|
||||||
|
return point.x > strongSelf.leftOverlayNode.frame.maxX && point.x < strongSelf.rightOverlayNode.frame.minX
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.listNode.visibleContentOffsetChanged = { [weak self] offset in
|
||||||
|
if let strongSelf = self {
|
||||||
|
var previousContentOffsetValue: CGFloat?
|
||||||
|
if let previousContentOffset = strongSelf.previousContentOffset, case let .known(value) = previousContentOffset {
|
||||||
|
previousContentOffsetValue = value
|
||||||
|
}
|
||||||
|
switch offset {
|
||||||
|
case let .known(value):
|
||||||
|
let transition: ContainedViewLayoutTransition
|
||||||
|
if let previousContentOffsetValue = previousContentOffsetValue, value <= 0.0, previousContentOffsetValue > 30.0 {
|
||||||
|
transition = .animated(duration: 0.2, curve: .easeInOut)
|
||||||
|
} else {
|
||||||
|
transition = .immediate
|
||||||
|
}
|
||||||
|
strongSelf.navigationBar?.updateBackgroundAlpha(min(30.0, value) / 30.0, transition: transition)
|
||||||
|
case .unknown, .none:
|
||||||
|
strongSelf.navigationBar?.updateBackgroundAlpha(1.0, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.previousContentOffset = offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -838,8 +875,25 @@ final class CallListControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
var insets = layout.insets(options: [.input])
|
var insets = layout.insets(options: [.input])
|
||||||
insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top)
|
insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top)
|
||||||
insets.left += layout.safeInsets.left
|
|
||||||
insets.right += layout.safeInsets.right
|
let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0))
|
||||||
|
if case .navigation = self.mode {
|
||||||
|
insets.left += inset
|
||||||
|
insets.right += inset
|
||||||
|
|
||||||
|
self.leftOverlayNode.frame = CGRect(x: 0.0, y: 0.0, width: insets.left, height: layout.size.height)
|
||||||
|
self.rightOverlayNode.frame = CGRect(x: layout.size.width - insets.right, y: 0.0, width: insets.right, height: layout.size.height)
|
||||||
|
|
||||||
|
if self.leftOverlayNode.supernode == nil {
|
||||||
|
self.insertSubnode(self.leftOverlayNode, aboveSubnode: self.listNode)
|
||||||
|
}
|
||||||
|
if self.rightOverlayNode.supernode == nil {
|
||||||
|
self.insertSubnode(self.rightOverlayNode, aboveSubnode: self.listNode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
insets.left += layout.safeInsets.left
|
||||||
|
insets.right += layout.safeInsets.right
|
||||||
|
}
|
||||||
|
|
||||||
self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
|
self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
|
||||||
self.listNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
|
self.listNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
|
||||||
|
@ -1733,7 +1733,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
var animateInputActivitiesFrame = false
|
var animateInputActivitiesFrame = false
|
||||||
let inputActivities = inputActivities?.filter({
|
let inputActivities = inputActivities?.filter({
|
||||||
switch $0.1 {
|
switch $0.1 {
|
||||||
case .speakingInGroupCall, .interactingWithEmoji, .seeingEmojiInteraction:
|
case .speakingInGroupCall, .seeingEmojiInteraction:
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
|
@ -60,7 +60,9 @@ final class ChatListInputActivitiesNode: ASDisplayNode {
|
|||||||
text = strings.DialogList_Typing
|
text = strings.DialogList_Typing
|
||||||
case .choosingSticker:
|
case .choosingSticker:
|
||||||
text = strings.Activity_ChoosingSticker
|
text = strings.Activity_ChoosingSticker
|
||||||
case .speakingInGroupCall, .seeingEmojiInteraction, .interactingWithEmoji:
|
case let .interactingWithEmoji(emoticon, _, _):
|
||||||
|
text = strings.Activity_TappingInteractiveEmoji(emoticon).string
|
||||||
|
case .speakingInGroupCall, .seeingEmojiInteraction:
|
||||||
text = ""
|
text = ""
|
||||||
}
|
}
|
||||||
let string = NSAttributedString(string: text, font: textFont, textColor: color)
|
let string = NSAttributedString(string: text, font: textFont, textColor: color)
|
||||||
@ -80,7 +82,9 @@ final class ChatListInputActivitiesNode: ASDisplayNode {
|
|||||||
state = .typingText(string, lightColor)
|
state = .typingText(string, lightColor)
|
||||||
case .choosingSticker:
|
case .choosingSticker:
|
||||||
state = .choosingSticker(string, lightColor)
|
state = .choosingSticker(string, lightColor)
|
||||||
case .seeingEmojiInteraction, .interactingWithEmoji:
|
case .interactingWithEmoji:
|
||||||
|
state = .interactingWithEmoji(string, lightColor)
|
||||||
|
case .seeingEmojiInteraction:
|
||||||
state = .none
|
state = .none
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -128,7 +128,7 @@ public class ChatTitleActivityContentNode: ASDisplayNode {
|
|||||||
if case .center = alignment {
|
if case .center = alignment {
|
||||||
self.textNode.position = CGPoint(x: 0.0, y: size.height / 2.0 + offset)
|
self.textNode.position = CGPoint(x: 0.0, y: size.height / 2.0 + offset)
|
||||||
} else {
|
} else {
|
||||||
self.textNode.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0 + offset)
|
self.textNode.position = CGPoint(x: size.width / 2.0 + 3.0, y: size.height / 2.0 + offset)
|
||||||
}
|
}
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ public enum ChatTitleActivityNodeState: Equatable {
|
|||||||
case recordingVideo(NSAttributedString, UIColor)
|
case recordingVideo(NSAttributedString, UIColor)
|
||||||
case playingGame(NSAttributedString, UIColor)
|
case playingGame(NSAttributedString, UIColor)
|
||||||
case choosingSticker(NSAttributedString, UIColor)
|
case choosingSticker(NSAttributedString, UIColor)
|
||||||
|
case interactingWithEmoji(NSAttributedString, UIColor)
|
||||||
|
|
||||||
func contentNode() -> ChatTitleActivityContentNode? {
|
func contentNode() -> ChatTitleActivityContentNode? {
|
||||||
switch self {
|
switch self {
|
||||||
@ -43,6 +44,8 @@ public enum ChatTitleActivityNodeState: Equatable {
|
|||||||
return ChatPlayingActivityContentNode(text: text, color: color)
|
return ChatPlayingActivityContentNode(text: text, color: color)
|
||||||
case let .choosingSticker(text, color):
|
case let .choosingSticker(text, color):
|
||||||
return ChatChoosingStickerActivityContentNode(text: text, color: color)
|
return ChatChoosingStickerActivityContentNode(text: text, color: color)
|
||||||
|
case let .interactingWithEmoji(text, _):
|
||||||
|
return ChatTitleActivityContentNode(text: text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ private func updateChildAnyComponent<EnvironmentType>(
|
|||||||
let size = component._update(
|
let size = component._update(
|
||||||
view: view,
|
view: view,
|
||||||
availableSize: availableSize,
|
availableSize: availableSize,
|
||||||
|
environment: context.environment,
|
||||||
transition: transition
|
transition: transition
|
||||||
)
|
)
|
||||||
context.layoutResult.size = size
|
context.layoutResult.size = size
|
||||||
@ -609,7 +610,7 @@ public extension CombinedComponent {
|
|||||||
return UIView()
|
return UIView()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize {
|
func update(view: View, availableSize: CGSize, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||||
let context = view.getCombinedComponentContext(Self.self)
|
let context = view.getCombinedComponentContext(Self.self)
|
||||||
|
|
||||||
let storedBody: Body
|
let storedBody: Body
|
||||||
|
@ -100,7 +100,7 @@ public final class EmptyComponentState: ComponentState {
|
|||||||
public protocol _TypeErasedComponent {
|
public protocol _TypeErasedComponent {
|
||||||
func _makeView() -> UIView
|
func _makeView() -> UIView
|
||||||
func _makeContext() -> _TypeErasedComponentContext
|
func _makeContext() -> _TypeErasedComponentContext
|
||||||
func _update(view: UIView, availableSize: CGSize, transition: Transition) -> CGSize
|
func _update(view: UIView, availableSize: CGSize, environment: Any, transition: Transition) -> CGSize
|
||||||
func _isEqual(to other: _TypeErasedComponent) -> Bool
|
func _isEqual(to other: _TypeErasedComponent) -> Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ public protocol Component: _TypeErasedComponent, Equatable {
|
|||||||
|
|
||||||
func makeView() -> View
|
func makeView() -> View
|
||||||
func makeState() -> State
|
func makeState() -> State
|
||||||
func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize
|
func update(view: View, availableSize: CGSize, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Component {
|
public extension Component {
|
||||||
@ -123,8 +123,8 @@ public extension Component {
|
|||||||
return ComponentContext<Self>(component: self, environment: Environment<EnvironmentType>(), state: self.makeState())
|
return ComponentContext<Self>(component: self, environment: Environment<EnvironmentType>(), state: self.makeState())
|
||||||
}
|
}
|
||||||
|
|
||||||
func _update(view: UIView, availableSize: CGSize, transition: Transition) -> CGSize {
|
func _update(view: UIView, availableSize: CGSize, environment: Any, transition: Transition) -> CGSize {
|
||||||
return self.update(view: view as! Self.View, availableSize: availableSize, transition: transition)
|
return self.update(view: view as! Self.View, availableSize: availableSize, environment: environment as! Environment<EnvironmentType>, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _isEqual(to other: _TypeErasedComponent) -> Bool {
|
func _isEqual(to other: _TypeErasedComponent) -> Bool {
|
||||||
@ -173,8 +173,8 @@ public class AnyComponent<EnvironmentType>: _TypeErasedComponent, Equatable {
|
|||||||
return self.wrapped._makeContext()
|
return self.wrapped._makeContext()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func _update(view: UIView, availableSize: CGSize, transition: Transition) -> CGSize {
|
public func _update(view: UIView, availableSize: CGSize, environment: Any, transition: Transition) -> CGSize {
|
||||||
return self.wrapped._update(view: view, availableSize: availableSize, transition: transition)
|
return self.wrapped._update(view: view, availableSize: availableSize, environment: environment as! Environment<EnvironmentType>, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func _isEqual(to other: _TypeErasedComponent) -> Bool {
|
public func _isEqual(to other: _TypeErasedComponent) -> Bool {
|
||||||
|
@ -55,7 +55,7 @@ public class _EnvironmentValue {
|
|||||||
public final class EnvironmentValue<T: Equatable>: _EnvironmentValue, Equatable {
|
public final class EnvironmentValue<T: Equatable>: _EnvironmentValue, Equatable {
|
||||||
private var storage: EnvironmentValueStorage<T>
|
private var storage: EnvironmentValueStorage<T>
|
||||||
|
|
||||||
fileprivate var value: T {
|
public var value: T {
|
||||||
switch self.storage {
|
switch self.storage {
|
||||||
case let .direct(value):
|
case let .direct(value):
|
||||||
return value
|
return value
|
||||||
|
@ -25,7 +25,7 @@ public final class Rectangle: Component {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(view: UIView, availableSize: CGSize, transition: Transition) -> CGSize {
|
public func update(view: UIView, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
var size = availableSize
|
var size = availableSize
|
||||||
if let width = self.width {
|
if let width = self.width {
|
||||||
size.width = min(size.width, width)
|
size.width = min(size.width, width)
|
||||||
|
@ -95,7 +95,7 @@ public final class Text: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize {
|
public func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize)
|
return view.update(component: self, availableSize: availableSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,9 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
public final class ComponentHostView<EnvironmentType>: UIView {
|
public final class ComponentHostView<EnvironmentType>: UIView {
|
||||||
|
private var currentComponent: AnyComponent<EnvironmentType>?
|
||||||
|
private var currentContainerSize: CGSize?
|
||||||
|
private var currentSize: CGSize?
|
||||||
private var componentView: UIView?
|
private var componentView: UIView?
|
||||||
private(set) var isUpdating: Bool = false
|
private(set) var isUpdating: Bool = false
|
||||||
|
|
||||||
@ -14,7 +17,16 @@ public final class ComponentHostView<EnvironmentType>: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func update(transition: Transition, component: AnyComponent<EnvironmentType>, @EnvironmentBuilder environment: () -> Environment<EnvironmentType>, containerSize: CGSize) -> CGSize {
|
public func update(transition: Transition, component: AnyComponent<EnvironmentType>, @EnvironmentBuilder environment: () -> Environment<EnvironmentType>, containerSize: CGSize) -> CGSize {
|
||||||
self._update(transition: transition, component: component, maybeEnvironment: environment, updateEnvironment: true, containerSize: containerSize)
|
if let currentComponent = self.currentComponent, let currentContainerSize = self.currentContainerSize, let currentSize = self.currentSize {
|
||||||
|
if currentContainerSize == containerSize && currentComponent == component {
|
||||||
|
return currentSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.currentComponent = component
|
||||||
|
self.currentContainerSize = containerSize
|
||||||
|
let size = self._update(transition: transition, component: component, maybeEnvironment: environment, updateEnvironment: true, containerSize: containerSize)
|
||||||
|
self.currentSize = size
|
||||||
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
private func _update(transition: Transition, component: AnyComponent<EnvironmentType>, maybeEnvironment: () -> Environment<EnvironmentType>, updateEnvironment: Bool, containerSize: CGSize) -> CGSize {
|
private func _update(transition: Transition, component: AnyComponent<EnvironmentType>, maybeEnvironment: () -> Environment<EnvironmentType>, updateEnvironment: Bool, containerSize: CGSize) -> CGSize {
|
||||||
@ -54,7 +66,7 @@ public final class ComponentHostView<EnvironmentType>: UIView {
|
|||||||
} as () -> Environment<EnvironmentType>, updateEnvironment: false, containerSize: containerSize)
|
} as () -> Environment<EnvironmentType>, updateEnvironment: false, containerSize: containerSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
let updatedSize = component._update(view: componentView, availableSize: containerSize, transition: transition)
|
let updatedSize = component._update(view: componentView, availableSize: containerSize, environment: context.erasedEnvironment, transition: transition)
|
||||||
transition.setFrame(view: componentView, frame: CGRect(origin: CGPoint(), size: updatedSize))
|
transition.setFrame(view: componentView, frame: CGRect(origin: CGPoint(), size: updatedSize))
|
||||||
|
|
||||||
self.isUpdating = false
|
self.isUpdating = false
|
||||||
|
@ -11,3 +11,18 @@ final class EscapeGuard {
|
|||||||
self.status.isDeallocated = true
|
self.status.isDeallocated = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class EscapeNotification: NSObject {
|
||||||
|
let deallocated: () -> Void
|
||||||
|
|
||||||
|
public init(_ deallocated: @escaping () -> Void) {
|
||||||
|
self.deallocated = deallocated
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.deallocated()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func keep() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,9 +17,12 @@ public protocol ContextActionNodeProtocol: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||||
private let action: ContextMenuActionItem
|
private var presentationData: PresentationData
|
||||||
|
private(set) var action: ContextMenuActionItem
|
||||||
private let getController: () -> ContextControllerProtocol?
|
private let getController: () -> ContextControllerProtocol?
|
||||||
private let actionSelected: (ContextMenuActionResult) -> Void
|
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||||
|
private let requestLayout: () -> Void
|
||||||
|
private let requestUpdateAction: (AnyHashable, ContextMenuActionItem) -> Void
|
||||||
|
|
||||||
private let backgroundNode: ASDisplayNode
|
private let backgroundNode: ASDisplayNode
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
@ -38,10 +41,13 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
init(presentationData: PresentationData, action: ContextMenuActionItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
init(presentationData: PresentationData, action: ContextMenuActionItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, requestLayout: @escaping () -> Void, requestUpdateAction: @escaping (AnyHashable, ContextMenuActionItem) -> Void) {
|
||||||
|
self.presentationData = presentationData
|
||||||
self.action = action
|
self.action = action
|
||||||
self.getController = getController
|
self.getController = getController
|
||||||
self.actionSelected = actionSelected
|
self.actionSelected = actionSelected
|
||||||
|
self.requestLayout = requestLayout
|
||||||
|
self.requestUpdateAction = requestUpdateAction
|
||||||
|
|
||||||
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
|
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
|
||||||
|
|
||||||
@ -63,6 +69,8 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
|||||||
textColor = presentationData.theme.contextMenu.primaryColor
|
textColor = presentationData.theme.contextMenu.primaryColor
|
||||||
case .destructive:
|
case .destructive:
|
||||||
textColor = presentationData.theme.contextMenu.destructiveColor
|
textColor = presentationData.theme.contextMenu.destructiveColor
|
||||||
|
case .disabled:
|
||||||
|
textColor = presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.4)
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleFont: UIFont
|
let titleFont: UIFont
|
||||||
@ -156,6 +164,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||||
|
self.buttonNode.isUserInteractionEnabled = self.action.action != nil
|
||||||
|
|
||||||
if let iconSource = action.iconSource {
|
if let iconSource = action.iconSource {
|
||||||
self.iconDisposable = (iconSource.signal
|
self.iconDisposable = (iconSource.signal
|
||||||
@ -264,6 +273,8 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateTheme(presentationData: PresentationData) {
|
func updateTheme(presentationData: PresentationData) {
|
||||||
|
self.presentationData = presentationData
|
||||||
|
|
||||||
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
|
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
|
||||||
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
|
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
|
||||||
|
|
||||||
@ -273,6 +284,8 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
|||||||
textColor = presentationData.theme.contextMenu.primaryColor
|
textColor = presentationData.theme.contextMenu.primaryColor
|
||||||
case .destructive:
|
case .destructive:
|
||||||
textColor = presentationData.theme.contextMenu.destructiveColor
|
textColor = presentationData.theme.contextMenu.destructiveColor
|
||||||
|
case .disabled:
|
||||||
|
textColor = presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.4)
|
||||||
}
|
}
|
||||||
|
|
||||||
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
|
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
|
||||||
@ -305,18 +318,58 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
|||||||
@objc private func buttonPressed() {
|
@objc private func buttonPressed() {
|
||||||
self.performAction()
|
self.performAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateAction(item: ContextMenuActionItem) {
|
||||||
|
self.action = item
|
||||||
|
|
||||||
|
let textColor: UIColor
|
||||||
|
switch self.action.textColor {
|
||||||
|
case .primary:
|
||||||
|
textColor = self.presentationData.theme.contextMenu.primaryColor
|
||||||
|
case .destructive:
|
||||||
|
textColor = self.presentationData.theme.contextMenu.destructiveColor
|
||||||
|
case .disabled:
|
||||||
|
textColor = self.presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.4)
|
||||||
|
}
|
||||||
|
|
||||||
|
let textFont = Font.regular(self.presentationData.listsFontSize.baseDisplaySize)
|
||||||
|
let titleFont: UIFont
|
||||||
|
switch self.action.textFont {
|
||||||
|
case .regular:
|
||||||
|
titleFont = textFont
|
||||||
|
case let .custom(customFont):
|
||||||
|
titleFont = customFont
|
||||||
|
}
|
||||||
|
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: self.action.text, font: titleFont, textColor: textColor)
|
||||||
|
|
||||||
|
if self.action.iconSource == nil {
|
||||||
|
self.iconNode.image = self.action.icon(self.presentationData.theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.requestLayout()
|
||||||
|
}
|
||||||
|
|
||||||
func performAction() {
|
func performAction() {
|
||||||
guard let controller = self.getController() else {
|
guard let controller = self.getController() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.action.action(controller, { [weak self] result in
|
self.action.action?(ContextMenuActionItem.Action(
|
||||||
self?.actionSelected(result)
|
controller: controller,
|
||||||
})
|
dismissWithResult: { [weak self] result in
|
||||||
|
self?.actionSelected(result)
|
||||||
|
},
|
||||||
|
updateAction: { [weak self] id, updatedAction in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.requestUpdateAction(id, updatedAction)
|
||||||
|
}
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func setIsHighlighted(_ value: Bool) {
|
func setIsHighlighted(_ value: Bool) {
|
||||||
if value {
|
if value && self.buttonNode.isUserInteractionEnabled {
|
||||||
self.highlightedBackgroundNode.alpha = 1.0
|
self.highlightedBackgroundNode.alpha = 1.0
|
||||||
} else {
|
} else {
|
||||||
self.highlightedBackgroundNode.alpha = 0.0
|
self.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
@ -69,7 +69,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void, blurBackground: Bool) {
|
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, requestLayout: @escaping () -> Void, feedbackTap: @escaping () -> Void, blurBackground: Bool) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.feedbackTap = feedbackTap
|
self.feedbackTap = feedbackTap
|
||||||
self.blurBackground = blurBackground
|
self.blurBackground = blurBackground
|
||||||
@ -78,12 +78,16 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
|||||||
self.containerNode.clipsToBounds = true
|
self.containerNode.clipsToBounds = true
|
||||||
self.containerNode.cornerRadius = 14.0
|
self.containerNode.cornerRadius = 14.0
|
||||||
self.containerNode.backgroundColor = presentationData.theme.contextMenu.backgroundColor
|
self.containerNode.backgroundColor = presentationData.theme.contextMenu.backgroundColor
|
||||||
|
|
||||||
|
var requestUpdateAction: ((AnyHashable, ContextMenuActionItem) -> Void)?
|
||||||
|
|
||||||
var itemNodes: [ContextItemNode] = []
|
var itemNodes: [ContextItemNode] = []
|
||||||
for i in 0 ..< items.count {
|
for i in 0 ..< items.count {
|
||||||
switch items[i] {
|
switch items[i] {
|
||||||
case let .action(action):
|
case let .action(action):
|
||||||
itemNodes.append(.action(ContextActionNode(presentationData: presentationData, action: action, getController: getController, actionSelected: actionSelected)))
|
itemNodes.append(.action(ContextActionNode(presentationData: presentationData, action: action, getController: getController, actionSelected: actionSelected, requestLayout: requestLayout, requestUpdateAction: { id, action in
|
||||||
|
requestUpdateAction?(id, action)
|
||||||
|
})))
|
||||||
if i != items.count - 1 {
|
if i != items.count - 1 {
|
||||||
switch items[i + 1] {
|
switch items[i + 1] {
|
||||||
case .action, .custom:
|
case .action, .custom:
|
||||||
@ -116,7 +120,24 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
|||||||
self.itemNodes = itemNodes
|
self.itemNodes = itemNodes
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
requestUpdateAction = { [weak self] id, action in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loop: for itemNode in strongSelf.itemNodes {
|
||||||
|
switch itemNode {
|
||||||
|
case let .action(contextActionNode):
|
||||||
|
if contextActionNode.action.id == id {
|
||||||
|
contextActionNode.updateAction(item: action)
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.addSubnode(self.containerNode)
|
self.addSubnode(self.containerNode)
|
||||||
|
|
||||||
self.itemNodes.forEach({ itemNode in
|
self.itemNodes.forEach({ itemNode in
|
||||||
@ -469,7 +490,7 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
|||||||
return self.additionalActionsNode != nil
|
return self.additionalActionsNode != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
init(presentationData: PresentationData, items: ContextController.Items, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void, blurBackground: Bool) {
|
init(presentationData: PresentationData, items: ContextController.Items, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, requestLayout: @escaping () -> Void, feedbackTap: @escaping () -> Void, blurBackground: Bool) {
|
||||||
self.blurBackground = blurBackground
|
self.blurBackground = blurBackground
|
||||||
self.shadowNode = ASImageNode()
|
self.shadowNode = ASImageNode()
|
||||||
self.shadowNode.displaysAsynchronously = false
|
self.shadowNode.displaysAsynchronously = false
|
||||||
@ -488,14 +509,14 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
|||||||
additionalShadowNode.isHidden = true
|
additionalShadowNode.isHidden = true
|
||||||
self.additionalShadowNode = additionalShadowNode
|
self.additionalShadowNode = additionalShadowNode
|
||||||
|
|
||||||
self.additionalActionsNode = InnerActionsContainerNode(presentationData: presentationData, items: [firstItem], getController: getController, actionSelected: actionSelected, feedbackTap: feedbackTap, blurBackground: blurBackground)
|
self.additionalActionsNode = InnerActionsContainerNode(presentationData: presentationData, items: [firstItem], getController: getController, actionSelected: actionSelected, requestLayout: requestLayout, feedbackTap: feedbackTap, blurBackground: blurBackground)
|
||||||
items.items.removeFirst()
|
items.items.removeFirst()
|
||||||
} else {
|
} else {
|
||||||
self.additionalShadowNode = nil
|
self.additionalShadowNode = nil
|
||||||
self.additionalActionsNode = nil
|
self.additionalActionsNode = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
self.actionsNode = InnerActionsContainerNode(presentationData: presentationData, items: items.items, getController: getController, actionSelected: actionSelected, feedbackTap: feedbackTap, blurBackground: blurBackground)
|
self.actionsNode = InnerActionsContainerNode(presentationData: presentationData, items: items.items, getController: getController, actionSelected: actionSelected, requestLayout: requestLayout, feedbackTap: feedbackTap, blurBackground: blurBackground)
|
||||||
if let tip = items.tip {
|
if let tip = items.tip {
|
||||||
let textSelectionTipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData, tip: tip)
|
let textSelectionTipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData, tip: tip)
|
||||||
textSelectionTipNode.isUserInteractionEnabled = false
|
textSelectionTipNode.isUserInteractionEnabled = false
|
||||||
|