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/.*\.cc$","@-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
|
||||
|
||||
|
@ -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(
|
||||
name = "LaunchScreen",
|
||||
srcs = glob([
|
||||
@ -351,7 +372,6 @@ plist_fragment(
|
||||
|
||||
official_apple_pay_merchants = [
|
||||
"merchant.ph.telegra.Telegraph",
|
||||
"merchant.yandex.ph.telegra.Telegraph",
|
||||
"merchant.sberbank.ph.telegra.Telegraph",
|
||||
"merchant.sberbank.test.ph.telegra.Telegraph",
|
||||
"merchant.privatbank.test.telergramios",
|
||||
@ -359,6 +379,7 @@ official_apple_pay_merchants = [
|
||||
"merchant.paymaster.test.telegramios",
|
||||
"merchant.smartglocal.prod.telegramios",
|
||||
"merchant.smartglocal.test.telegramios",
|
||||
"merchant.yoomoney.test.telegramios",
|
||||
]
|
||||
|
||||
official_bundle_ids = [
|
||||
@ -1855,11 +1876,14 @@ ios_application(
|
||||
":VersionInfoPlist",
|
||||
":UrlTypesInfoPlist",
|
||||
],
|
||||
ipa_post_processor = ":AddAlternateIcons",
|
||||
alternate_icons = [
|
||||
":{}".format(name) for name in alternate_icon_folders
|
||||
],
|
||||
#ipa_post_processor = ":AddAlternateIcons",
|
||||
resources = [
|
||||
":LaunchScreen",
|
||||
":DefaultAppIcon",
|
||||
":AdditionalIcons",
|
||||
#":AdditionalIcons",
|
||||
],
|
||||
frameworks = [
|
||||
":MtProtoKitFramework",
|
||||
|
@ -326,7 +326,7 @@ private let gradientColors: [NSArray] = [
|
||||
[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)
|
||||
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?.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]
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
@ -368,11 +373,11 @@ private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId:
|
||||
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) {
|
||||
return roundImage
|
||||
} 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? {
|
||||
if let resource = smallestImageRepresentation(peer.profileImageRepresentations)?.resource, let path = mediaBox.completedResourcePath(resource) {
|
||||
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) {
|
||||
do {
|
||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
if let _ = fileSize(cachedPath) {
|
||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||
} 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() {
|
||||
let _ = try? data.write(to: URL(fileURLWithPath: cachedPath), options: .atomic)
|
||||
}
|
||||
do {
|
||||
//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
|
||||
}
|
||||
|
||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||
}
|
||||
}
|
||||
|
||||
let cachedPath = mediaBox.cachedRepresentationPathForId("lettersAvatar-\(peer.displayLetters.joined(separator: ","))", representationId: "intents.png", keepDuration: .shortLived)
|
||||
if let _ = fileSize(cachedPath) {
|
||||
do {
|
||||
//let data = try Data(contentsOf: URL(fileURLWithPath: cachedPath), options: [])
|
||||
//return INImage(imageData: data)
|
||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||
} 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() {
|
||||
let _ = try? data.write(to: URL(fileURLWithPath: cachedPath), options: .atomic)
|
||||
}
|
||||
do {
|
||||
//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
|
||||
}
|
||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -614,17 +598,40 @@ private final class NotificationServiceHandler {
|
||||
return nil
|
||||
}
|
||||
|
||||
let _ = (self.accountManager.currentAccountRecord(allocateIfNotExists: false)
|
||||
let _ = (self.accountManager.accountRecords()
|
||||
|> take(1)
|
||||
|> 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
|
||||
}
|
||||
|
||||
let _ = (standaloneStateManager(
|
||||
accountManager: strongSelf.accountManager,
|
||||
networkArguments: networkArguments,
|
||||
id: record.0,
|
||||
id: recordId,
|
||||
encryptionParameters: strongSelf.encryptionParameters,
|
||||
rootPath: rootPath,
|
||||
auxiliaryMethods: accountAuxiliaryMethods
|
||||
@ -634,6 +641,8 @@ private final class NotificationServiceHandler {
|
||||
return
|
||||
}
|
||||
guard let stateManager = stateManager else {
|
||||
let content = NotificationContent()
|
||||
updateCurrentContent(content)
|
||||
completed()
|
||||
return
|
||||
}
|
||||
@ -642,18 +651,31 @@ private final class NotificationServiceHandler {
|
||||
strongSelf.notificationKeyDisposable.set((existingMasterNotificationsKey(postbox: stateManager.postbox)
|
||||
|> deliverOn(strongSelf.queue)).start(next: { notificationsKey in
|
||||
guard let strongSelf = self else {
|
||||
let content = NotificationContent()
|
||||
updateCurrentContent(content)
|
||||
completed()
|
||||
|
||||
return
|
||||
}
|
||||
guard let notificationsKey = notificationsKey else {
|
||||
let content = NotificationContent()
|
||||
updateCurrentContent(content)
|
||||
completed()
|
||||
|
||||
return
|
||||
}
|
||||
guard let decryptedPayload = decryptedNotificationPayload(key: notificationsKey, data: payloadData) else {
|
||||
let content = NotificationContent()
|
||||
updateCurrentContent(content)
|
||||
completed()
|
||||
|
||||
return
|
||||
}
|
||||
guard let payloadJson = try? JSONSerialization.jsonObject(with: decryptedPayload, options: []) as? [String: Any] else {
|
||||
let content = NotificationContent()
|
||||
updateCurrentContent(content)
|
||||
completed()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -679,6 +701,10 @@ private final class NotificationServiceHandler {
|
||||
if let channelIdValue = Int64(channelIdString) {
|
||||
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 {
|
||||
@ -751,7 +777,7 @@ private final class NotificationServiceHandler {
|
||||
}
|
||||
|
||||
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 silentValue = Int(silentString), silentValue != 0 {
|
||||
@ -904,7 +930,9 @@ private final class NotificationServiceHandler {
|
||||
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 mediaData = mediaData {
|
||||
@ -1053,7 +1081,9 @@ private final class NotificationServiceHandler {
|
||||
)
|
||||
|> deliverOn(strongSelf.queue)).start(next: { value in
|
||||
var content = NotificationContent()
|
||||
content.badge = Int(value.0)
|
||||
if isCurrentAccount {
|
||||
content.badge = Int(value.0)
|
||||
}
|
||||
|
||||
updateCurrentContent(content)
|
||||
|
||||
@ -1096,7 +1126,9 @@ private final class NotificationServiceHandler {
|
||||
)
|
||||
|> deliverOn(strongSelf.queue)).start(next: { value in
|
||||
var content = NotificationContent()
|
||||
content.badge = Int(value.0)
|
||||
if isCurrentAccount {
|
||||
content.badge = Int(value.0)
|
||||
}
|
||||
|
||||
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.TypeVideo" = "Videos (%@)";
|
||||
"ChatSettings.AutoDownloadSettings.TypeMedia" = "Media (%@)";
|
||||
"ChatSettings.AutoDownloadSettings.TypeFile" = "Files (%@)";
|
||||
"ChatSettings.AutoDownloadSettings.OffForAll" = "Disabled";
|
||||
"ChatSettings.AutoDownloadSettings.Delimeter" = ", ";
|
||||
@ -6706,6 +6707,8 @@ Sorry for the inconvenience.";
|
||||
"Activity.ChoosingSticker" = "choosing sticker";
|
||||
"DialogList.SingleChoosingStickerSuffix" = "%@ is choosing sticker";
|
||||
|
||||
"Activity.TappingInteractiveEmoji" = "tapping on %@";
|
||||
|
||||
"WallpaperPreview.Animate" = "Animate";
|
||||
"WallpaperPreview.AnimateDescription" = "Colors will move when you send messages";
|
||||
|
||||
@ -6781,13 +6784,7 @@ Sorry for the inconvenience.";
|
||||
|
||||
"SponsoredMessageMenu.Info" = "What are sponsored\nmessages?";
|
||||
"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.
|
||||
|
||||
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.";
|
||||
"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.";
|
||||
"SponsoredMessageInfo.Action" = "Learn More";
|
||||
"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.KeepHeic" = "Keep HEIC";
|
||||
"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)
|
||||
|
||||
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])
|
||||
|
@ -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/TelegramCore:TelegramCore",
|
||||
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
|
||||
"//submodules/MeshAnimationCache:MeshAnimationCache"
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -10,6 +10,7 @@ import AsyncDisplayKit
|
||||
import Display
|
||||
import DeviceLocationManager
|
||||
import TemporaryCachedPeerDataManager
|
||||
import MeshAnimationCache
|
||||
|
||||
public final class TelegramApplicationOpenUrlCompletion {
|
||||
public let completion: (Bool) -> Void
|
||||
@ -736,6 +737,7 @@ public protocol AccountContext: AnyObject {
|
||||
var currentAppConfiguration: Atomic<AppConfiguration> { get }
|
||||
|
||||
var cachedGroupCallContexts: AccountGroupCallContextCache { get }
|
||||
var meshAnimationCache: MeshAnimationCache { get }
|
||||
|
||||
func storeSecureIdPassword(password: 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)
|
||||
}
|
||||
|
||||
public func messageMediaImageInteractiveFetched(context: AccountContext, message: Message, image: TelegramMediaImage, resource: MediaResource, range: Range<Int>? = nil, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
|
||||
return messageMediaImageInteractiveFetched(fetchManager: context.fetchManager, messageId: message.id, messageReference: MessageReference(message), image: image, resource: resource, range: range, userInitiated: true, priority: .userInitiated, storeToDownloadsPeerType: storeToDownloadsPeerType)
|
||||
public func messageMediaImageInteractiveFetched(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: 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> {
|
||||
|
@ -16,7 +16,7 @@ private func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter:
|
||||
}
|
||||
|
||||
private func convertIndicatorColor(_ color: UIColor) -> UIColor {
|
||||
if color.isEqual(UIColor(rgb: 0x007ee5)) {
|
||||
if color.isEqual(UIColor(rgb: 0x007aff)) {
|
||||
return .gray
|
||||
} else if color.isEqual(UIColor(rgb: 0x2ea6ff)) {
|
||||
return .white
|
||||
|
@ -343,7 +343,7 @@ public final class AvatarNode: ASDisplayNode {
|
||||
|
||||
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.displaySuspended = true
|
||||
self.imageReady.set(self.imageNode.contentReady)
|
||||
|
@ -453,18 +453,7 @@ private func formSupportApplePay(_ paymentForm: BotPaymentForm) -> Bool {
|
||||
guard let nativeProvider = paymentForm.nativeProvider else {
|
||||
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 {
|
||||
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 bottomStripeNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private let avatarNode: AvatarNode
|
||||
private let titleNode: TextNode
|
||||
@ -206,6 +207,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
@ -523,7 +526,9 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
||||
} else if last && strongSelf.bottomStripeNode.supernode != nil {
|
||||
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)))
|
||||
case .blocks:
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
@ -535,11 +540,18 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
if strongSelf.maskNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
|
||||
}
|
||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
||||
var hasTopCorners = false
|
||||
var hasBottomCorners = false
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
strongSelf.topStripeNode.isHidden = false
|
||||
hasTopCorners = true
|
||||
strongSelf.topStripeNode.isHidden = hasCorners
|
||||
}
|
||||
let bottomStripeInset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
@ -547,9 +559,14 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
||||
bottomStripeInset = leftInset
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
}
|
||||
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: 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)))
|
||||
}
|
||||
|
@ -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?.beginCallImpl()
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ private func mappedInsertEntries(context: AccountContext, presentationData: Item
|
||||
return entries.map { entry -> ListViewInsertItem in
|
||||
switch entry.entry {
|
||||
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)
|
||||
}), directionHint: entry.directionHint)
|
||||
case let .displayTabInfo(_, text):
|
||||
@ -136,7 +136,7 @@ private func mappedUpdateEntries(context: AccountContext, presentationData: Item
|
||||
return entries.map { entry -> ListViewUpdateItem in
|
||||
switch entry.entry {
|
||||
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)
|
||||
}), directionHint: entry.directionHint)
|
||||
case let .displayTabInfo(_, text):
|
||||
@ -177,6 +177,8 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
return _ready.get()
|
||||
}
|
||||
|
||||
weak var navigationBar: NavigationBar?
|
||||
|
||||
var peerSelected: ((EnginePeer.Id) -> Void)?
|
||||
var activateSearch: (() -> Void)?
|
||||
var deletePeerChat: ((EnginePeer.Id) -> Void)?
|
||||
@ -215,6 +217,8 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
|
||||
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) {
|
||||
self.controller = controller
|
||||
self.context = context
|
||||
@ -272,7 +276,7 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
self.addSubnode(self.emptyButtonTextNode)
|
||||
self.addSubnode(self.emptyButtonIconNode)
|
||||
self.addSubnode(self.emptyButtonNode)
|
||||
|
||||
|
||||
switch self.mode {
|
||||
case .tab:
|
||||
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)
|
||||
}
|
||||
}))
|
||||
|
||||
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 {
|
||||
@ -838,8 +875,25 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
|
||||
var insets = layout.insets(options: [.input])
|
||||
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.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
|
||||
|
@ -1733,7 +1733,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var animateInputActivitiesFrame = false
|
||||
let inputActivities = inputActivities?.filter({
|
||||
switch $0.1 {
|
||||
case .speakingInGroupCall, .interactingWithEmoji, .seeingEmojiInteraction:
|
||||
case .speakingInGroupCall, .seeingEmojiInteraction:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
|
@ -60,7 +60,9 @@ final class ChatListInputActivitiesNode: ASDisplayNode {
|
||||
text = strings.DialogList_Typing
|
||||
case .choosingSticker:
|
||||
text = strings.Activity_ChoosingSticker
|
||||
case .speakingInGroupCall, .seeingEmojiInteraction, .interactingWithEmoji:
|
||||
case let .interactingWithEmoji(emoticon, _, _):
|
||||
text = strings.Activity_TappingInteractiveEmoji(emoticon).string
|
||||
case .speakingInGroupCall, .seeingEmojiInteraction:
|
||||
text = ""
|
||||
}
|
||||
let string = NSAttributedString(string: text, font: textFont, textColor: color)
|
||||
@ -80,7 +82,9 @@ final class ChatListInputActivitiesNode: ASDisplayNode {
|
||||
state = .typingText(string, lightColor)
|
||||
case .choosingSticker:
|
||||
state = .choosingSticker(string, lightColor)
|
||||
case .seeingEmojiInteraction, .interactingWithEmoji:
|
||||
case .interactingWithEmoji:
|
||||
state = .interactingWithEmoji(string, lightColor)
|
||||
case .seeingEmojiInteraction:
|
||||
state = .none
|
||||
}
|
||||
} else {
|
||||
|
@ -128,7 +128,7 @@ public class ChatTitleActivityContentNode: ASDisplayNode {
|
||||
if case .center = alignment {
|
||||
self.textNode.position = CGPoint(x: 0.0, y: size.height / 2.0 + offset)
|
||||
} 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
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ public enum ChatTitleActivityNodeState: Equatable {
|
||||
case recordingVideo(NSAttributedString, UIColor)
|
||||
case playingGame(NSAttributedString, UIColor)
|
||||
case choosingSticker(NSAttributedString, UIColor)
|
||||
case interactingWithEmoji(NSAttributedString, UIColor)
|
||||
|
||||
func contentNode() -> ChatTitleActivityContentNode? {
|
||||
switch self {
|
||||
@ -43,6 +44,8 @@ public enum ChatTitleActivityNodeState: Equatable {
|
||||
return ChatPlayingActivityContentNode(text: text, color: color)
|
||||
case let .choosingSticker(text, 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(
|
||||
view: view,
|
||||
availableSize: availableSize,
|
||||
environment: context.environment,
|
||||
transition: transition
|
||||
)
|
||||
context.layoutResult.size = size
|
||||
@ -609,7 +610,7 @@ public extension CombinedComponent {
|
||||
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 storedBody: Body
|
||||
|
@ -100,7 +100,7 @@ public final class EmptyComponentState: ComponentState {
|
||||
public protocol _TypeErasedComponent {
|
||||
func _makeView() -> UIView
|
||||
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
|
||||
}
|
||||
|
||||
@ -111,7 +111,7 @@ public protocol Component: _TypeErasedComponent, Equatable {
|
||||
|
||||
func makeView() -> View
|
||||
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 {
|
||||
@ -123,8 +123,8 @@ public extension Component {
|
||||
return ComponentContext<Self>(component: self, environment: Environment<EnvironmentType>(), state: self.makeState())
|
||||
}
|
||||
|
||||
func _update(view: UIView, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||
return self.update(view: view as! Self.View, availableSize: availableSize, transition: transition)
|
||||
func _update(view: UIView, availableSize: CGSize, environment: Any, transition: Transition) -> CGSize {
|
||||
return self.update(view: view as! Self.View, availableSize: availableSize, environment: environment as! Environment<EnvironmentType>, transition: transition)
|
||||
}
|
||||
|
||||
func _isEqual(to other: _TypeErasedComponent) -> Bool {
|
||||
@ -173,8 +173,8 @@ public class AnyComponent<EnvironmentType>: _TypeErasedComponent, Equatable {
|
||||
return self.wrapped._makeContext()
|
||||
}
|
||||
|
||||
public func _update(view: UIView, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||
return self.wrapped._update(view: view, availableSize: availableSize, transition: transition)
|
||||
public func _update(view: UIView, availableSize: CGSize, environment: Any, transition: Transition) -> CGSize {
|
||||
return self.wrapped._update(view: view, availableSize: availableSize, environment: environment as! Environment<EnvironmentType>, transition: transition)
|
||||
}
|
||||
|
||||
public func _isEqual(to other: _TypeErasedComponent) -> Bool {
|
||||
|
@ -55,7 +55,7 @@ public class _EnvironmentValue {
|
||||
public final class EnvironmentValue<T: Equatable>: _EnvironmentValue, Equatable {
|
||||
private var storage: EnvironmentValueStorage<T>
|
||||
|
||||
fileprivate var value: T {
|
||||
public var value: T {
|
||||
switch self.storage {
|
||||
case let .direct(value):
|
||||
return value
|
||||
|
@ -25,7 +25,7 @@ public final class Rectangle: Component {
|
||||
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
|
||||
if let width = self.width {
|
||||
size.width = min(size.width, width)
|
||||
|
@ -95,7 +95,7 @@ public final class Text: Component {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,9 @@ import Foundation
|
||||
import UIKit
|
||||
|
||||
public final class ComponentHostView<EnvironmentType>: UIView {
|
||||
private var currentComponent: AnyComponent<EnvironmentType>?
|
||||
private var currentContainerSize: CGSize?
|
||||
private var currentSize: CGSize?
|
||||
private var componentView: UIView?
|
||||
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 {
|
||||
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 {
|
||||
@ -54,7 +66,7 @@ public final class ComponentHostView<EnvironmentType>: UIView {
|
||||
} 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))
|
||||
|
||||
self.isUpdating = false
|
||||
|
@ -11,3 +11,18 @@ final class EscapeGuard {
|
||||
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 {
|
||||
private let action: ContextMenuActionItem
|
||||
private var presentationData: PresentationData
|
||||
private(set) var action: ContextMenuActionItem
|
||||
private let getController: () -> ContextControllerProtocol?
|
||||
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||
private let requestLayout: () -> Void
|
||||
private let requestUpdateAction: (AnyHashable, ContextMenuActionItem) -> Void
|
||||
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
@ -38,10 +41,13 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
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.getController = getController
|
||||
self.actionSelected = actionSelected
|
||||
self.requestLayout = requestLayout
|
||||
self.requestUpdateAction = requestUpdateAction
|
||||
|
||||
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
|
||||
|
||||
@ -63,6 +69,8 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
textColor = presentationData.theme.contextMenu.primaryColor
|
||||
case .destructive:
|
||||
textColor = presentationData.theme.contextMenu.destructiveColor
|
||||
case .disabled:
|
||||
textColor = presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.4)
|
||||
}
|
||||
|
||||
let titleFont: UIFont
|
||||
@ -156,6 +164,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
}
|
||||
}
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
self.buttonNode.isUserInteractionEnabled = self.action.action != nil
|
||||
|
||||
if let iconSource = action.iconSource {
|
||||
self.iconDisposable = (iconSource.signal
|
||||
@ -264,6 +273,8 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
}
|
||||
|
||||
func updateTheme(presentationData: PresentationData) {
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
|
||||
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
|
||||
|
||||
@ -273,6 +284,8 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
textColor = presentationData.theme.contextMenu.primaryColor
|
||||
case .destructive:
|
||||
textColor = presentationData.theme.contextMenu.destructiveColor
|
||||
case .disabled:
|
||||
textColor = presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.4)
|
||||
}
|
||||
|
||||
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
|
||||
@ -305,18 +318,58 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
@objc private func buttonPressed() {
|
||||
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() {
|
||||
guard let controller = self.getController() else {
|
||||
return
|
||||
}
|
||||
self.action.action(controller, { [weak self] result in
|
||||
self?.actionSelected(result)
|
||||
})
|
||||
self.action.action?(ContextMenuActionItem.Action(
|
||||
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) {
|
||||
if value {
|
||||
if value && self.buttonNode.isUserInteractionEnabled {
|
||||
self.highlightedBackgroundNode.alpha = 1.0
|
||||
} else {
|
||||
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.feedbackTap = feedbackTap
|
||||
self.blurBackground = blurBackground
|
||||
@ -78,12 +78,16 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
||||
self.containerNode.clipsToBounds = true
|
||||
self.containerNode.cornerRadius = 14.0
|
||||
self.containerNode.backgroundColor = presentationData.theme.contextMenu.backgroundColor
|
||||
|
||||
var requestUpdateAction: ((AnyHashable, ContextMenuActionItem) -> Void)?
|
||||
|
||||
var itemNodes: [ContextItemNode] = []
|
||||
for i in 0 ..< items.count {
|
||||
switch items[i] {
|
||||
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 {
|
||||
switch items[i + 1] {
|
||||
case .action, .custom:
|
||||
@ -116,7 +120,24 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
||||
self.itemNodes = itemNodes
|
||||
|
||||
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.itemNodes.forEach({ itemNode in
|
||||
@ -469,7 +490,7 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
||||
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.shadowNode = ASImageNode()
|
||||
self.shadowNode.displaysAsynchronously = false
|
||||
@ -488,14 +509,14 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
||||
additionalShadowNode.isHidden = true
|
||||
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()
|
||||
} else {
|
||||
self.additionalShadowNode = 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 {
|
||||
let textSelectionTipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData, tip: tip)
|
||||
textSelectionTipNode.isUserInteractionEnabled = false
|
||||
|