Merge branch 'master' into beta

This commit is contained in:
Ali 2021-10-26 14:27:10 +04:00
commit 09820ce6a0
5807 changed files with 1225538 additions and 3065 deletions

View File

@ -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

View File

@ -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",

View File

@ -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)

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 737 B

After

Width:  |  Height:  |  Size: 737 B

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 749 B

After

Width:  |  Height:  |  Size: 749 B

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 715 B

After

Width:  |  Height:  |  Size: 715 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 665 B

After

Width:  |  Height:  |  Size: 665 B

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 889 B

After

Width:  |  Height:  |  Size: 889 B

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 917 B

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -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 %@";

View File

@ -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

View File

@ -20,6 +20,7 @@ swift_library(
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
"//submodules/MeshAnimationCache:MeshAnimationCache"
],
visibility = [
"//visibility:public",

View File

@ -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?

View File

@ -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> {

View File

@ -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

View File

@ -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)

View File

@ -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
}

View 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",
],
)

File diff suppressed because it is too large Load Diff

View 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)))
}

View File

@ -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()
}

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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

View File

@ -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() {
}
}

View File

@ -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

View File

@ -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

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