From e15f0982cb6676e033cee378256e82a16621832b Mon Sep 17 00:00:00 2001 From: Mikhail Filimonov Date: Fri, 27 Jun 2025 17:46:14 +0100 Subject: [PATCH 01/11] bug fixes --- .../Sources/TelegramEngine/Messages/Translate.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift index 73ce8e2649..02b5519c5d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift @@ -11,6 +11,7 @@ public enum TranslationError { case textTooLong case invalidLanguage case limitExceeded + case tryAlternative } func _internal_translate(network: Network, text: String, toLang: String, entities: [MessageTextEntity] = []) -> Signal<(String, [MessageTextEntity])?, TranslationError> { @@ -29,6 +30,8 @@ func _internal_translate(network: Network, text: String, toLang: String, entitie return .textTooLong } else if error.errorDescription == "TO_LANG_INVALID" { return .invalidLanguage + } else if error.errorDescription == "TRANSLATIONS_DISABLED_ALT" { + return .tryAlternative } else { return .generic } From f610e8b3b31f31f8a832a3fa53e309e601a11bc6 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 30 Jun 2025 00:06:27 +0200 Subject: [PATCH 02/11] Various improvements --- .../AccountContext/Sources/Premium.swift | 2 -- .../PremiumUI/Sources/PremiumDemoScreen.swift | 28 +--------------- .../PremiumUI/Sources/PremiumGiftScreen.swift | 2 -- .../Sources/PremiumIntroScreen.swift | 23 +------------ .../Sources/PremiumLimitsListScreen.swift | 18 ----------- .../IncomingMessagePrivacyScreen.swift | 2 +- .../Sources/ChannelStatsController.swift | 21 +++++++----- .../Sources/TransactionInfoScreen.swift | 32 ++++++++++++------- .../Sources/SharedAccountContext.swift | 6 +--- 9 files changed, 37 insertions(+), 97 deletions(-) diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index 360282a1fe..ed2b980d59 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -42,7 +42,6 @@ public enum PremiumIntroSource { case folderTags case animatedEmoji case messageEffects - case paidMessages case todo case auth(String) } @@ -82,7 +81,6 @@ public enum PremiumDemoSubject { case folderTags case business case messageEffects - case paidMessages case todo case businessLocation diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index 4c582e1ed6..5d5771dc07 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -1098,26 +1098,7 @@ private final class DemoSheetContent: CombinedComponent { ) ) ) - - availableItems[.paidMessages] = DemoPagerComponent.Item( - AnyComponentWithIdentity( - id: PremiumDemoScreen.Subject.paidMessages, - component: AnyComponent( - PageComponent( - content: AnyComponent(PhoneDemoComponent( - context: component.context, - position: .top, - videoFile: configuration.videos["paid_messages"], - decoration: .badgeStars - )), - title: strings.Premium_PaidMessages, - text: strings.Premium_PaidMessagesInfo, - textColor: textColor - ) - ) - ) - ) - + availableItems[.todo] = DemoPagerComponent.Item( AnyComponentWithIdentity( id: PremiumDemoScreen.Subject.todo, @@ -1234,8 +1215,6 @@ private final class DemoSheetContent: CombinedComponent { text = strings.Premium_FolderTagsStandaloneInfo case .messageEffects: text = strings.Premium_MessageEffectsInfo - case .paidMessages: - text = strings.Premium_PaidMessagesInfo case .todo: text = strings.Premium_TodoInfo default: @@ -1322,8 +1301,6 @@ private final class DemoSheetContent: CombinedComponent { case .emojiStatus: buttonText = strings.Premium_EmojiStatus_Proceed buttonAnimationName = "premium_unlock" - case .paidMessages: - buttonText = strings.Premium_PaidMessages_Proceed case .todo: buttonText = strings.Premium_PaidMessages_Proceed default: @@ -1515,7 +1492,6 @@ public class PremiumDemoScreen: ViewControllerComponentContainer { case business case folderTags case messageEffects - case paidMessages case todo case businessLocation @@ -1575,8 +1551,6 @@ public class PremiumDemoScreen: ViewControllerComponentContainer { return .folderTags case .messageEffects: return .messageEffects - case .paidMessages: - return .paidMessages case .todo: return .todo case .businessLocation: diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index 5d3c60997c..85d095b7d7 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -539,8 +539,6 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { demoSubject = .messagePrivacy case .messageEffects: demoSubject = .messageEffects - case .paidMessages: - demoSubject = .paidMessages case .business: demoSubject = .business default: diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index d35676d1af..85599ec2aa 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -303,12 +303,6 @@ public enum PremiumSource: Equatable { } else { return false } - case .paidMessages: - if case .messageEffects = rhs { - return true - } else { - return false - } case .todo: if case .todo = rhs { return true @@ -368,7 +362,6 @@ public enum PremiumSource: Equatable { case messageTags case folderTags case messageEffects - case paidMessages case todo case auth(String) @@ -464,8 +457,6 @@ public enum PremiumSource: Equatable { return "folder_tags" case .messageEffects: return "effects" - case .paidMessages: - return "paid_messages" case .todo: return "todo" case .auth: @@ -498,7 +489,6 @@ public enum PremiumPerk: CaseIterable { case business case folderTags case messageEffects - case paidMessages case todo case businessLocation @@ -535,7 +525,7 @@ public enum PremiumPerk: CaseIterable { .folderTags, .business, .messageEffects, - .paidMessages + .todo ] } @@ -609,8 +599,6 @@ public enum PremiumPerk: CaseIterable { return "folder_tags" case .messageEffects: return "effects" - case .paidMessages: - return "paid_messages" case .todo: return "todo" case .business: @@ -682,8 +670,6 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_Business case .messageEffects: return strings.Premium_MessageEffects - case .paidMessages: - return strings.Premium_PaidMessages case .todo: return strings.Premium_Todo case .businessLocation: @@ -753,8 +739,6 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_BusinessInfo case .messageEffects: return strings.Premium_MessageEffectsInfo - case .paidMessages: - return strings.Premium_PaidMessagesInfo case .todo: return strings.Premium_TodoInfo case .businessLocation: @@ -824,8 +808,6 @@ public enum PremiumPerk: CaseIterable { return "Premium/Perk/Business" case .messageEffects: return "Premium/Perk/MessageEffects" - case .paidMessages: - return "Premium/Perk/PaidMessages" case .todo: return "Premium/Perk/Todo" case .businessLocation: @@ -866,7 +848,6 @@ struct PremiumIntroConfiguration { .colors, .wallpapers, .profileBadge, - .paidMessages, .messagePrivacy, .advancedChatManagement, .noAds, @@ -2171,8 +2152,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { demoSubject = .messagePrivacy case .messageEffects: demoSubject = .messageEffects - case .paidMessages: - demoSubject = .paidMessages case .todo: demoSubject = .todo case .business: diff --git a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift index 858a67f7ce..7c6e798d43 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift @@ -843,24 +843,6 @@ public class PremiumLimitsListScreen: ViewController { ) ) ) - availableItems[.paidMessages] = DemoPagerComponent.Item( - AnyComponentWithIdentity( - id: PremiumDemoScreen.Subject.paidMessages, - component: AnyComponent( - PageComponent( - content: AnyComponent(PhoneDemoComponent( - context: context, - position: .top, - videoFile: videos["paid_messages"], - decoration: .badgeStars - )), - title: strings.Premium_PaidMessages, - text: strings.Premium_PaidMessagesInfo, - textColor: textColor - ) - ) - ) - ) availableItems[.todo] = DemoPagerComponent.Item( AnyComponentWithIdentity( diff --git a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift index 11d5cbe41c..2b7957f22d 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift @@ -363,7 +363,7 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP openPremiumInfo: { var replaceImpl: ((ViewController) -> Void)? let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .messagePrivacy, forceDark: false, action: { - let controller = context.sharedContext.makePremiumIntroController(context: context, source: .paidMessages, forceDark: false, dismissed: nil) + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .messageEffects, forceDark: false, dismissed: nil) replaceImpl?(controller) }, dismissed: nil) replaceImpl = { [weak controller] c in diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index abfc97fd36..ff37e68268 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -1165,15 +1165,20 @@ private enum StatsEntry: ItemListNodeEntry { detailText = "\(fromDateString) – \(toDateString)" } } else if case .fragment = transaction.peer { - title = NSAttributedString(string: presentationData.strings.Monetization_Transaction_Withdrawal("Fragment").string, font: font, textColor: theme.list.itemPrimaryTextColor) - labelColor = theme.list.itemDestructiveColor - if transaction.flags.contains(.isPending) { - detailText = presentationData.strings.Monetization_Transaction_Pending - } else if transaction.flags.contains(.isFailed) { - detailText = stringForMediumCompactDate(timestamp: transaction.date, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, withTime: false) + " – \(presentationData.strings.Monetization_Transaction_Failed)" - detailColor = .destructive - } else { + if transaction.flags.contains(.isRefund) { + title = NSAttributedString(string: presentationData.strings.Monetization_Transaction_Refund, font: font, textColor: theme.list.itemPrimaryTextColor) detailText = stringForMediumCompactDate(timestamp: transaction.date, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat) + } else { + title = NSAttributedString(string: presentationData.strings.Monetization_Transaction_Withdrawal("Fragment").string, font: font, textColor: theme.list.itemPrimaryTextColor) + labelColor = theme.list.itemDestructiveColor + if transaction.flags.contains(.isPending) { + detailText = presentationData.strings.Monetization_Transaction_Pending + } else if transaction.flags.contains(.isFailed) { + detailText = stringForMediumCompactDate(timestamp: transaction.date, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, withTime: false) + " – \(presentationData.strings.Monetization_Transaction_Failed)" + detailColor = .destructive + } else { + detailText = stringForMediumCompactDate(timestamp: transaction.date, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat) + } } } else if transaction.flags.contains(.isRefund) { title = NSAttributedString(string: presentationData.strings.Monetization_Transaction_Refund, font: font, textColor: theme.list.itemPrimaryTextColor) diff --git a/submodules/StatisticsUI/Sources/TransactionInfoScreen.swift b/submodules/StatisticsUI/Sources/TransactionInfoScreen.swift index 5d2b16cd61..3a87d208c3 100644 --- a/submodules/StatisticsUI/Sources/TransactionInfoScreen.swift +++ b/submodules/StatisticsUI/Sources/TransactionInfoScreen.swift @@ -145,20 +145,28 @@ private final class SheetContent: CombinedComponent { explorerUrl = nil showPeer = true } else if case .fragment = component.transaction.peer { - labelColor = theme.list.itemDestructiveColor - amountString = tonAmountAttributedString(formatTonAmountText(component.transaction.count.amount.value, dateTimeFormat: dateTimeFormat), integralFont: integralFont, fractionalFont: fractionalFont, color: labelColor, decimalSeparator: dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString - dateString = stringForFullDate(timestamp: component.transaction.date, strings: strings, dateTimeFormat: dateTimeFormat) - - if component.transaction.flags.contains(.isPending) { - titleString = strings.Monetization_TransactionInfo_Pending + if component.transaction.flags.contains(.isRefund) { + labelColor = theme.list.itemDisclosureActions.constructive.fillColor + titleString = strings.Monetization_TransactionInfo_Refund + amountString = tonAmountAttributedString(formatTonAmountText(component.transaction.count.amount.value, dateTimeFormat: dateTimeFormat, showPlus: true), integralFont: integralFont, fractionalFont: fractionalFont, color: labelColor, decimalSeparator: dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString + dateString = stringForFullDate(timestamp: component.transaction.date, strings: strings, dateTimeFormat: dateTimeFormat) buttonTitle = strings.Common_OK - } else if component.transaction.flags.contains(.isFailed) { - titleString = strings.Monetization_TransactionInfo_Failed - buttonTitle = strings.Common_OK - titleColor = theme.list.itemDestructiveColor } else { - titleString = strings.Monetization_TransactionInfo_Withdrawal("Fragment").string - buttonTitle = strings.Monetization_TransactionInfo_ViewInExplorer + labelColor = theme.list.itemDestructiveColor + amountString = tonAmountAttributedString(formatTonAmountText(component.transaction.count.amount.value, dateTimeFormat: dateTimeFormat), integralFont: integralFont, fractionalFont: fractionalFont, color: labelColor, decimalSeparator: dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString + dateString = stringForFullDate(timestamp: component.transaction.date, strings: strings, dateTimeFormat: dateTimeFormat) + + if component.transaction.flags.contains(.isPending) { + titleString = strings.Monetization_TransactionInfo_Pending + buttonTitle = strings.Common_OK + } else if component.transaction.flags.contains(.isFailed) { + titleString = strings.Monetization_TransactionInfo_Failed + buttonTitle = strings.Common_OK + titleColor = theme.list.itemDestructiveColor + } else { + titleString = strings.Monetization_TransactionInfo_Withdrawal("Fragment").string + buttonTitle = strings.Monetization_TransactionInfo_ViewInExplorer + } } explorerUrl = component.transaction.transactionUrl } else if component.transaction.flags.contains(.isRefund) { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 91f821e5a3..cc39fe338d 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2720,10 +2720,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSource = .messageEffects case .animatedEmoji: mappedSource = .animatedEmoji - case .paidMessages: - mappedSource = .paidMessages case .todo: - mappedSource = .paidMessages + mappedSource = .todo case let .auth(price): mappedSource = .auth(price) } @@ -2800,8 +2798,6 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSubject = .folderTags case .messageEffects: mappedSubject = .messageEffects - case .paidMessages: - mappedSubject = .paidMessages case .todo: mappedSubject = .todo case .business: From adbc7bad61ecb6204ecacc1821ad5241afaaaab3 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 1 Jul 2025 14:57:33 +0200 Subject: [PATCH 03/11] Various improvements --- Telegram/BUILD | 6 +- .../Telegram-iOS/en.lproj/Localizable.strings | 4 +- build-system/generate_spm.py | 68 ++++--- .../Source/ASDisplayNode+Layout.mm | 4 + .../Source/ASExperimentalFeatures.mm | 2 + .../Source/ASInternalHelpers.mm | 2 + submodules/AsyncDisplayKit/Source/ASLayout.mm | 2 + .../AsyncDisplayKit/Source/ASLayoutSpec.mm | 4 + .../Source/ASMainThreadDeallocation.mm | 2 + .../AsyncDisplayKit/ASLayoutElementPrivate.h | 3 + .../Sources/SheetComponent.swift | 2 +- .../LegacyComponents/Sources/ActionStage.mm | 6 + .../LegacyComponents/Sources/Freedom.mm | 5 + .../Sources/NSValue+JNWAdditions.m | 3 + .../Sources/PGPhotoEnhanceLUTGenerator.m | 3 + .../Sources/POPDecayAnimationInternal.h | 5 + .../LegacyComponents/Sources/TGGifConverter.m | 2 +- .../Sources/TGMediaVideoConverter.m | 1 + .../LegacyComponents/Sources/TGStringUtils.mm | 5 + submodules/MtProtoKit/Sources/MTGzip.m | 6 + .../Sources/MTInternalMessageParser.m | 6 + .../OpusBinding/Sources/OggOpusReader.m | 10 +- .../Sources/ManagedAudioSession.swift | 8 + .../Components/MediaStreamComponent.swift | 2 +- submodules/TelegramUI/BUILD | 1 + .../Sources/ChatMessageNotificationItem.swift | 6 +- .../Sources/StarsWithdrawalScreen.swift | 76 ++++---- .../Sources/ApplicationContext.swift | 1 + .../Chat/ChatControllerLoadDisplayNode.swift | 2 +- .../Chat/ChatControllerThemeManagement.swift | 1 + .../TelegramUI/Sources/ChatController.swift | 180 ++++++++++++------ .../Sources/ChatControllerAdminBanUsers.swift | 2 +- .../ChatControllerOpenAttachmentMenu.swift | 1 + .../ChatInterfaceStateContextMenus.swift | 4 +- .../ChatRecordingPreviewInputPanelNode.swift | 7 +- .../Sources/CreateChannelController.swift | 2 + .../Sources/CreateGroupController.swift | 2 + .../Sources/LegacyDataImportSplash.swift | 1 + .../Sources/LockedWindowCoveringView.swift | 1 + .../Sources/TelegramRootController.swift | 23 ++- 40 files changed, 319 insertions(+), 152 deletions(-) diff --git a/Telegram/BUILD b/Telegram/BUILD index 347ad23818..fb07598b4e 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -1786,12 +1786,12 @@ ios_application( #"//third-party/webrtc:webrtc_objc", #"//submodules/AsyncDisplayKit", #"//submodules/ObjCRuntimeUtils", - "//submodules/OpusBinding", + #"//submodules/OpusBinding", #"//third-party/boringssl:ssl", #"//third-party/boringssl:crypto", #"//submodules/TelegramVoip", - "//third-party/recaptcha:RecaptchaEnterprise", - #"//submodules/TelegramUI", + #"//third-party/recaptcha:RecaptchaEnterprise", + "//submodules/TelegramUI", ], ) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 74034789c8..032ee57efd 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14542,8 +14542,8 @@ Sorry for the inconvenience."; "Chat.PostApproval.Message.UserAgreementPriceTon" = "šŸ’° You have been charged %1$@.\n\nāŒ› **%2$@** will receive the TON once the post has been live for 24 hours.\n\nšŸ”„ If admins the post before it has been live for 24 hours, your TON will be refunded."; "Chat.PostApproval.Message.AdminAgreementPriceStars" = "šŸ’° The user have been charged %1$@.\n\nāŒ› **%2$@** will receive the Stars once the post has been live for 24 hours.\n\nšŸ”„ If you remove the post before it has been live for 24 hours, the user's Stars will be refunded."; "Chat.PostApproval.Message.AdminAgreementPriceTon" = "šŸ’° The user have been charged %1$@.\n\nāŒ› **%2$@** will receive the TON once the post has been live for 24 hours.\n\nšŸ”„ If you remove the post before it has been live for 24 hours, the user's TON will be refunded."; -"Chat.PostApproval.Message.AdminDeclined" = "You declined the post."; -"Chat.PostApproval.Message.AdminDeclinedComment" = "You declined the post with the following comment:\n\n%@"; +"Chat.PostApproval.Message.AdminDeclined" = ""; +"Chat.PostApproval.Message.AdminDeclinedComment" = "%@"; "Chat.PostApproval.Message.ActionApprove" = "Approve"; "Chat.PostApproval.Message.ActionReject" = "Decline"; diff --git a/build-system/generate_spm.py b/build-system/generate_spm.py index 7391fea90b..c97e9890f0 100644 --- a/build-system/generate_spm.py +++ b/build-system/generate_spm.py @@ -6,6 +6,7 @@ import sys import json import shutil import hashlib +import tempfile # Read the modules JSON file modules_json_path = "bazel-bin/Telegram/spm_build_root_modules.json" @@ -17,6 +18,7 @@ with open(modules_json_path, 'r') as f: spm_files_dir = "spm-files" previous_spm_files = set() +cleanup_temp_dirs = [] def scan_spm_files(path: str): global previous_spm_files @@ -119,9 +121,6 @@ def parse_define_flag(flag: str) -> tuple[str, str | None]: if (value.startswith('"') and value.endswith('"')) or (value.startswith("'") and value.endswith("'")): value = value[1:-1] # Remove quotes value = value.replace("\\\"", "\"") - - #if key == "PACKAGE_VERSION": - # print(f"PACKAGE_VERSION={value}") return (key, value) else: @@ -328,7 +327,8 @@ for name, module in sorted(modules.items()): sources_zip_directory = None if module["type"] == "apple_static_xcframework_import": - pass + sources_zip_directory = tempfile.mkdtemp() + cleanup_temp_dirs.append(sources_zip_directory) for source in module["sources"] + module.get("hdrs", []) + module.get("textual_hdrs", []): # Process all sources (both regular and generated) with symlinks @@ -346,29 +346,42 @@ for name, module in sorted(modules.items()): sys.exit(1) source_file_name = source[len(module["path"]) + 1:] - # Create symlink for this source file - symlink_location = os.path.join(module_directory, source_file_name) - - # Create parent directory for symlink if it doesn't exist - symlink_parent = os.path.dirname(symlink_location) - create_spm_directory(symlink_parent) - - # Calculate relative path from symlink back to original file - # Count directory depth: spm-files/module_name/... -> spm-files - num_parent_dirs = symlink_location.count(os.path.sep) - relative_prefix = "".join(["../"] * num_parent_dirs) - symlink_target = relative_prefix + source - - # Create the symlink - link_spm_file(symlink_target, symlink_location) - - # Add to sources list (exclude certain file types) - if source.endswith(('.h', '.hpp', '.a', '.inc')): - if len(module_public_headers_prefix) != 0 and source_file_name.startswith(module_public_headers_prefix): - public_include_files.append(source_file_name[len(module_public_headers_prefix) + 1:]) - exclude_source_files.append(source_file_name) + if sources_zip_directory is not None: + zip_location = os.path.join(sources_zip_directory, source_file_name) + zip_parent = os.path.dirname(zip_location) + if not os.path.exists(zip_parent): + os.makedirs(zip_parent) + shutil.copy2(source, zip_location) else: - include_source_files.append(source_file_name) + # Create symlink for this source file + symlink_location = os.path.join(module_directory, source_file_name) + + # Create parent directory for symlink if it doesn't exist + symlink_parent = os.path.dirname(symlink_location) + create_spm_directory(symlink_parent) + + # Calculate relative path from symlink back to original file + # Count directory depth: spm-files/module_name/... -> spm-files + num_parent_dirs = symlink_location.count(os.path.sep) + relative_prefix = "".join(["../"] * num_parent_dirs) + symlink_target = relative_prefix + source + + # Create the symlink + link_spm_file(symlink_target, symlink_location) + + # Add to sources list (exclude certain file types) + if source.endswith(('.h', '.hpp', '.a', '.inc')): + if len(module_public_headers_prefix) != 0 and source_file_name.startswith(module_public_headers_prefix): + public_include_files.append(source_file_name[len(module_public_headers_prefix) + 1:]) + exclude_source_files.append(source_file_name) + else: + include_source_files.append(source_file_name) + + if sources_zip_directory is not None: + # Create zip file from sources directory + zip_output_path = os.path.join(module_directory, f"{name}.xcframework.zip") + shutil.make_archive(zip_output_path[:-4], 'zip', sources_zip_directory) + current_spm_files.add(zip_output_path) if name in module_to_source_files: print(f"{name}: duplicate module") @@ -562,6 +575,9 @@ for modulemap_path, modulemap in modulemaps.items(): # Clean up files and directories that are no longer needed files_to_remove = previous_spm_files - current_spm_files +for cleanup_temp_dir in cleanup_temp_dirs: + files_to_remove.add(cleanup_temp_dir) + # Sort by path depth (deeper paths first) to ensure we remove files before their parent directories sorted_files_to_remove = sorted(files_to_remove, key=lambda x: x.count(os.path.sep), reverse=True) diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm index 706c97286b..e4fad6d308 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm +++ b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm @@ -911,7 +911,9 @@ ASLayoutElementStyleExtensibilityForwarding NSArray *sublayouts = _calculatedDisplayNodeLayout.layout.sublayouts; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400 #pragma clang diagnostic push +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 #pragma clang diagnostic ignored "-Wvla-cxx-extension" +#endif #endif unowned ASLayout *cSublayouts[sublayouts.count]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400 @@ -936,7 +938,9 @@ ASLayoutElementStyleExtensibilityForwarding #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400 #pragma clang diagnostic push +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 #pragma clang diagnostic ignored "-Wvla-cxx-extension" +#endif #endif NSArray *layoutNodes = ASArrayByFlatMapping(sublayouts, ASLayout *layout, (ASDisplayNode *)layout.layoutElement); #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400 diff --git a/submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.mm b/submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.mm index eeab704d79..3902f8853b 100644 --- a/submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.mm +++ b/submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.mm @@ -34,7 +34,9 @@ NSArray *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400 #pragma clang diagnostic push +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 #pragma clang diagnostic ignored "-Wvla-cxx-extension" +#endif #endif return ASArrayByFlatMapping(allNames, NSString *name, ({ diff --git a/submodules/AsyncDisplayKit/Source/ASInternalHelpers.mm b/submodules/AsyncDisplayKit/Source/ASInternalHelpers.mm index 2ba089d772..2788e98a85 100644 --- a/submodules/AsyncDisplayKit/Source/ASInternalHelpers.mm +++ b/submodules/AsyncDisplayKit/Source/ASInternalHelpers.mm @@ -142,7 +142,9 @@ Class _Nullable ASGetClassFromType(const char * _Nullable type) size_t resultLength = typeLength - 3; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400 #pragma clang diagnostic push +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 #pragma clang diagnostic ignored "-Wvla-cxx-extension" +#endif #endif char className[resultLength + 1]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400 diff --git a/submodules/AsyncDisplayKit/Source/ASLayout.mm b/submodules/AsyncDisplayKit/Source/ASLayout.mm index 34ea8e78d5..828f678156 100644 --- a/submodules/AsyncDisplayKit/Source/ASLayout.mm +++ b/submodules/AsyncDisplayKit/Source/ASLayout.mm @@ -237,7 +237,9 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( // Fast-reverse-enumerate the sublayouts array by copying it into a C-array and push_front'ing each into the queue. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400 #pragma clang diagnostic push +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 #pragma clang diagnostic ignored "-Wvla-cxx-extension" +#endif #endif unowned ASLayout *rawSublayouts[sublayoutsCount]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400 diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/ASLayoutSpec.mm index 4a6cc853c5..797f2c8d82 100644 --- a/submodules/AsyncDisplayKit/Source/ASLayoutSpec.mm +++ b/submodules/AsyncDisplayKit/Source/ASLayoutSpec.mm @@ -161,7 +161,9 @@ ASLayoutElementStyleExtensibilityForwarding // Use tiny descriptions because these trees can get nested very deep. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400 #pragma clang diagnostic push +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 #pragma clang diagnostic ignored "-Wvla-cxx-extension" +#endif #endif const auto tinyDescriptions = ASArrayByFlatMapping(children, id object, ASObjectDescriptionMakeTiny(object)); #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400 @@ -299,7 +301,9 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__) const auto count = children.count; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400 #pragma clang diagnostic push +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 #pragma clang diagnostic ignored "-Wvla-cxx-extension" +#endif #endif ASLayout *rawSublayouts[count]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400 diff --git a/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm b/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm index 1cafb592d8..35818550a0 100644 --- a/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm +++ b/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm @@ -34,7 +34,9 @@ #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400 #pragma clang diagnostic push +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 #pragma clang diagnostic ignored "-Wvla-cxx-extension" +#endif #endif Ivar ivars[count]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400 diff --git a/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutElementPrivate.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutElementPrivate.h index 6f713f3908..54a3ef12c0 100644 --- a/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutElementPrivate.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutElementPrivate.h @@ -67,12 +67,15 @@ static const int kMaxLayoutElementBoolExtensions = 1; static const int kMaxLayoutElementStateIntegerExtensions = 4; static const int kMaxLayoutElementStateEdgeInsetExtensions = 1; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgnu-folding-constant" typedef struct ASLayoutElementStyleExtensions { // Values to store extensions BOOL boolExtensions[kMaxLayoutElementBoolExtensions]; NSInteger integerExtensions[kMaxLayoutElementStateIntegerExtensions]; UIEdgeInsets edgeInsetsExtensions[kMaxLayoutElementStateEdgeInsetExtensions]; } ASLayoutElementStyleExtensions; +#pragma clang diagnostic pop #define ASLayoutElementStyleExtensibilityForwarding \ - (void)setLayoutOptionExtensionBool:(BOOL)value atIndex:(int)idx\ diff --git a/submodules/Components/SheetComponent/Sources/SheetComponent.swift b/submodules/Components/SheetComponent/Sources/SheetComponent.swift index e95713a3a3..28aa486e23 100644 --- a/submodules/Components/SheetComponent/Sources/SheetComponent.swift +++ b/submodules/Components/SheetComponent/Sources/SheetComponent.swift @@ -38,7 +38,7 @@ public final class SheetComponentEnvironment: Equatable { } public let sheetComponentTag = GenericComponentViewTag() -public final class SheetComponent: Component { +public final class SheetComponent: Component { public typealias EnvironmentType = (ChildEnvironmentType, SheetComponentEnvironment) public class ExternalState { diff --git a/submodules/LegacyComponents/Sources/ActionStage.mm b/submodules/LegacyComponents/Sources/ActionStage.mm index 76d9c73ab3..0d55828276 100644 --- a/submodules/LegacyComponents/Sources/ActionStage.mm +++ b/submodules/LegacyComponents/Sources/ActionStage.mm @@ -244,7 +244,13 @@ ActionStage *ActionStageInstance() return @""; int length = (int)path.length; + +#pragma clang diagnostic push +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 +#pragma clang diagnostic ignored "-Wvla-cxx-extension" +#endif unichar newPath[path.length]; +#pragma clang diagnostic pop int newLength = 0; SEL sel = @selector(characterAtIndex:); diff --git a/submodules/LegacyComponents/Sources/Freedom.mm b/submodules/LegacyComponents/Sources/Freedom.mm index 0231a18de9..38436e31e9 100644 --- a/submodules/LegacyComponents/Sources/Freedom.mm +++ b/submodules/LegacyComponents/Sources/Freedom.mm @@ -257,7 +257,12 @@ static bool consumeField(const char *desc, int length, int &offset, uint32_t &ou { outName = (uint32_t)murMurHashBytes32((void *)(desc + offset), i - offset); +#pragma clang diagnostic push +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 +#pragma clang diagnostic ignored "-Wvla-cxx-extension" +#endif char buf[i - offset + 1]; +#pragma clang diagnostic pop memcpy(buf, desc + offset, i - offset); buf[i - offset] = 0; diff --git a/submodules/LegacyComponents/Sources/NSValue+JNWAdditions.m b/submodules/LegacyComponents/Sources/NSValue+JNWAdditions.m index d44a379ad0..a1c4e6270a 100644 --- a/submodules/LegacyComponents/Sources/NSValue+JNWAdditions.m +++ b/submodules/LegacyComponents/Sources/NSValue+JNWAdditions.m @@ -96,7 +96,10 @@ const char *type = self.objCType; static const NSInteger numberofNumberTypes = 10; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgnu-folding-constant" static const char *numberTypes[numberofNumberTypes] = { "i", "s", "l", "q", "I", "S", "L", "Q", "f", "d" }; +#pragma clang diagnostic pop for (NSInteger i = 0; i < numberofNumberTypes; i++) { if (strcmp(type, numberTypes[i]) == 0) { diff --git a/submodules/LegacyComponents/Sources/PGPhotoEnhanceLUTGenerator.m b/submodules/LegacyComponents/Sources/PGPhotoEnhanceLUTGenerator.m index 3b701050e0..011dd0138c 100644 --- a/submodules/LegacyComponents/Sources/PGPhotoEnhanceLUTGenerator.m +++ b/submodules/LegacyComponents/Sources/PGPhotoEnhanceLUTGenerator.m @@ -113,10 +113,13 @@ const NSUInteger PGPhotoEnhanceSegments = 4; GLubyte *bytes = [self _rawBytes]; NSUInteger bytesPerRow = [_retainedFramebuffer bytesPerRow]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgnu-folding-constant" NSUInteger hist[totalSegments][PGPhotoEnhanceHistogramBins]; NSUInteger cdfs[totalSegments][PGPhotoEnhanceHistogramBins]; NSUInteger cdfsMin[totalSegments]; NSUInteger cdfsMax[totalSegments]; +#pragma clang diagnostic pop memset(hist, 0, totalSegments * PGPhotoEnhanceHistogramBins * sizeof(NSUInteger)); memset(cdfs, 0, totalSegments * PGPhotoEnhanceHistogramBins * sizeof(NSUInteger)); diff --git a/submodules/LegacyComponents/Sources/POPDecayAnimationInternal.h b/submodules/LegacyComponents/Sources/POPDecayAnimationInternal.h index 0353b113fe..fa822bb748 100644 --- a/submodules/LegacyComponents/Sources/POPDecayAnimationInternal.h +++ b/submodules/LegacyComponents/Sources/POPDecayAnimationInternal.h @@ -26,7 +26,12 @@ static void decay_position(CGFloat *x, CGFloat *v, NSUInteger count, CFTimeInter // x0 = x; // x = x0 + v0 * deceleration * (1 - powf(deceleration, dt)) / (1 - deceleration) +#pragma clang diagnostic push +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 +#pragma clang diagnostic ignored "-Wvla-cxx-extension" +#endif float v0[count]; +#pragma clang diagnostic pop float kv = powf(deceleration, dt); float kx = deceleration * (1 - kv) / (1 - deceleration); diff --git a/submodules/LegacyComponents/Sources/TGGifConverter.m b/submodules/LegacyComponents/Sources/TGGifConverter.m index faca400a80..adc7fb82b6 100644 --- a/submodules/LegacyComponents/Sources/TGGifConverter.m +++ b/submodules/LegacyComponents/Sources/TGGifConverter.m @@ -257,7 +257,7 @@ const CGFloat TGGifConverterMaximumSide = 720.0f; size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pxBuffer); - CGContextRef context = CGBitmapContextCreate(pxData, size.width, size.height, 8, bytesPerRow, colorSpace, kCGImageAlphaNoneSkipFirst); + CGContextRef context = CGBitmapContextCreate(pxData, size.width, size.height, 8, bytesPerRow, colorSpace, (CGBitmapInfo)kCGImageAlphaNoneSkipFirst); NSAssert(context, @"Could not create a context"); CGContextDrawImage(context, CGRectMake(0, 0, size.width, size.height), frame); diff --git a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m index 40b02b7bed..b74097aa3e 100644 --- a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m +++ b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m @@ -840,6 +840,7 @@ { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" +#pragma clang diagnostic ignored "-Wgnu-folding-constant" const NSUInteger bufSize = 1024; NSUInteger numberOfBuffersToRead = MIN(32, floor(fileData.length / bufSize)); diff --git a/submodules/LegacyComponents/Sources/TGStringUtils.mm b/submodules/LegacyComponents/Sources/TGStringUtils.mm index daa796e70d..52208738fb 100644 --- a/submodules/LegacyComponents/Sources/TGStringUtils.mm +++ b/submodules/LegacyComponents/Sources/TGStringUtils.mm @@ -1139,7 +1139,12 @@ int32_t legacy_murMurHashBytes32(void *bytes, int length) int32_t phoneMatchHash(NSString *phone) { int length = (int)phone.length; +#pragma clang diagnostic push +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 +#pragma clang diagnostic ignored "-Wvla-cxx-extension" +#endif char cleanString[length]; +#pragma clang diagnostic pop int cleanLength = 0; for (int i = 0; i < length; i++) diff --git a/submodules/MtProtoKit/Sources/MTGzip.m b/submodules/MtProtoKit/Sources/MTGzip.m index c529dbad29..a131b25d61 100644 --- a/submodules/MtProtoKit/Sources/MTGzip.m +++ b/submodules/MtProtoKit/Sources/MTGzip.m @@ -10,7 +10,13 @@ NSUInteger length = [data length]; int windowBits = 15 + 32; //Default + gzip header instead of zlib header int retCode; +#pragma clang diagnostic push +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 +#pragma clang diagnostic ignored "-Wvla-cxx-extension" +#endif +#pragma clang diagnostic ignored "-Wgnu-folding-constant" unsigned char output[kMemoryChunkSize]; +#pragma clang diagnostic pop uInt gotBack; NSMutableData *result; z_stream stream; diff --git a/submodules/MtProtoKit/Sources/MTInternalMessageParser.m b/submodules/MtProtoKit/Sources/MTInternalMessageParser.m index 8523a8a6ba..9137814a02 100644 --- a/submodules/MtProtoKit/Sources/MTInternalMessageParser.m +++ b/submodules/MtProtoKit/Sources/MTInternalMessageParser.m @@ -688,7 +688,13 @@ NSUInteger length = [data length]; int windowBits = 15 + 32; //Default + gzip header instead of zlib header int retCode; +#pragma clang diagnostic push +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 +#pragma clang diagnostic ignored "-Wvla-cxx-extension" +#endif +#pragma clang diagnostic ignored "-Wgnu-folding-constant" unsigned char output[kMemoryChunkSize]; +#pragma clang diagnostic pop uInt gotBack; NSMutableData *result; z_stream stream; diff --git a/submodules/OpusBinding/Sources/OggOpusReader.m b/submodules/OpusBinding/Sources/OggOpusReader.m index 3df05d1b43..85fdc1e37b 100644 --- a/submodules/OpusBinding/Sources/OggOpusReader.m +++ b/submodules/OpusBinding/Sources/OggOpusReader.m @@ -90,13 +90,13 @@ static int is_opus(ogg_page *og) { return nil; } - int pages = 0; - int packetsout = 0; - int invalid = 0; + __unused int pages = 0; + __unused int packetsout = 0; + __unused int invalid = 0; int eos = 0; int headers = 0; - int serialno = 0; + __unused int serialno = 0; /* LOOP START */ while (ogg_sync_pageout(&ostate, &opage) == 1) { @@ -145,7 +145,7 @@ static int is_opus(ogg_page *og) { } /* get packet duration */ - samples = opus_packet_get_nb_samples(opacket.packet, opacket.bytes, sampleRate); + samples = opus_packet_get_nb_samples(opacket.packet, (int)opacket.bytes, sampleRate); if (samples <= 0) { invalid++; continue; // skipping invalid packet diff --git a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift index 78db670856..ab694dfc60 100644 --- a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift +++ b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift @@ -867,11 +867,19 @@ public final class ManagedAudioSessionImpl: NSObject, ManagedAudioSession { options.insert(.allowBluetoothA2DP) } case .voiceCall, .videoCall: + #if canImport(AlarmKit) //Xcode 26 + options.insert(.allowBluetoothHFP) + #else options.insert(.allowBluetooth) + #endif options.insert(.allowBluetoothA2DP) options.insert(.mixWithOthers) case let .record(_, video, mixWithOthers): + #if canImport(AlarmKit) //Xcode 26 + options.insert(.allowBluetoothHFP) + #else options.insert(.allowBluetooth) + #endif if video { options.insert(.allowBluetoothA2DP) } diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift index be21e8c204..b292d0058b 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift @@ -2039,7 +2039,7 @@ final class RoundGradientButtonComponent: Component { } } -public final class Throttler { +public final class Throttler { public var duration: TimeInterval = 0.25 public var queue: DispatchQueue = .main public var isEnabled: Bool { duration > 0 } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index a4c6b655e4..648f58c933 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -481,6 +481,7 @@ swift_library( "//submodules/TelegramUI/Components/BatchVideoRendering", "//submodules/TelegramUI/Components/ComposeTodoScreen", "//submodules/TelegramUI/Components/SuggestedPostApproveAlert", + "//submodules/TelegramUI/Components/Stars/BalanceNeededScreen", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift index 5f65b540b3..bf30319a26 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift @@ -139,7 +139,11 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { } if case let .channel(channel) = peer, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = firstMessage.peers[linkedMonoforumId] { - title = authorString + "@" + EnginePeer(mainChannel).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + if author.id == mainChannel.id { + title = EnginePeer(mainChannel).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + } else { + title = authorString + "@" + EnginePeer(mainChannel).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + } } else { if let threadData = item.threadData { title = "\(authorString) → \(threadData.info.title)" diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 35142a30df..0195e200c4 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -775,44 +775,54 @@ private final class SheetContent: CombinedComponent { completion(amount.value) case let .paidMessages(_, _, _, _, completion): completion(amount.value) - case let .suggestedPost(_, _, _, completion): - switch state.currency { - case .stars: - if let balance = state.starsBalance, amount > balance { - guard let starsContext = state.context.starsContext else { - return - } - let _ = (state.context.engine.payments.starsTopUpOptions() - |> take(1) - |> deliverOnMainQueue).startStandalone(next: { [weak controller, weak state] options in - guard let controller, let state else { + case let .suggestedPost(mode, _, _, completion): + let needsLocalBalance: Bool + switch mode { + case .admin: + needsLocalBalance = false + case let .sender(_, isFromAdmin): + needsLocalBalance = !isFromAdmin + } + + if needsLocalBalance { + switch state.currency { + case .stars: + if let balance = state.starsBalance, amount > balance { + guard let starsContext = state.context.starsContext else { return } - let purchaseController = state.context.sharedContext.makeStarsPurchaseScreen(context: state.context, starsContext: starsContext, options: options, purpose: .generic, completion: { _ in - }) - controller.push(purchaseController) - }) - - return - } - case .ton: - if let balance = state.tonBalance, amount > balance { - let needed = amount - balance - var fragmentUrl = "https://fragment.com/ads/topup" - if let data = state.context.currentAppConfiguration.with({ $0 }).data, let value = data["ton_topup_url"] as? String { - fragmentUrl = value - } - controller.push(BalanceNeededScreen( - context: state.context, - amount: needed, - buttonAction: { [weak state] in - guard let state else { + let _ = (state.context.engine.payments.starsTopUpOptions() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak controller, weak state] options in + guard let controller, let state else { return } - state.context.sharedContext.applicationBindings.openUrl(fragmentUrl) + let purchaseController = state.context.sharedContext.makeStarsPurchaseScreen(context: state.context, starsContext: starsContext, options: options, purpose: .generic, completion: { _ in + }) + controller.push(purchaseController) + }) + + return + } + case .ton: + if let balance = state.tonBalance, amount > balance { + let needed = amount - balance + var fragmentUrl = "https://fragment.com/ads/topup" + if let data = state.context.currentAppConfiguration.with({ $0 }).data, let value = data["ton_topup_url"] as? String { + fragmentUrl = value } - )) - return + controller.push(BalanceNeededScreen( + context: state.context, + amount: needed, + buttonAction: { [weak state] in + guard let state else { + return + } + state.context.sharedContext.applicationBindings.openUrl(fragmentUrl) + } + )) + return + } } } diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index aec4ca0639..51b4902c1e 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import Intents import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index cc69b28488..3347ad119f 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -1811,7 +1811,7 @@ extension ChatControllerImpl { return } - if actions.options.contains(.deleteGlobally), let message = messages.first(where: { message in message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) }), let attribute = message.attributes.first(where: { $0 is PublishedSuggestedPostMessageAttribute }) as? PublishedSuggestedPostMessageAttribute { + if actions.options.contains(.deleteGlobally), let message = messages.first(where: { message in message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) }), let attribute = message.attributes.first(where: { $0 is PublishedSuggestedPostMessageAttribute }) as? PublishedSuggestedPostMessageAttribute, message.timestamp > Int32(Date().timeIntervalSince1970) - 60 * 60 * 24 { let commit = { [weak self] in guard let self else { return diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift index 4b35a57113..dadcc49e02 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift @@ -122,6 +122,7 @@ import PeerNameColorScreen import ChatEmptyNode import ChatMediaInputStickerGridItem import AdsInfoScreen +import Photos extension ChatControllerImpl { public func presentThemeSelection() { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index f0f4ffbfcb..2a5fa16522 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -138,6 +138,8 @@ import QuickShareScreen import PostSuggestionsSettingsScreen import PromptUI import SuggestedPostApproveAlert +import AVFoundation +import BalanceNeededScreen public final class ChatControllerOverlayPresentationData { public let expandData: (ASDisplayNode?, () -> Void) @@ -2365,70 +2367,130 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) strongSelf.present(promptController, in: .window(.root)) case 1: - var timestamp: Int32? - var funds: (amount: CurrencyAmount, commissionPermille: Int)? - if let amount = attribute.amount { - let configuration = StarsSubscriptionConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) - funds = (amount, amount.currency == .stars ? Int(configuration.channelMessageSuggestionStarsCommissionPermille) : Int(configuration.channelMessageSuggestionTonCommissionPermille)) - } - - var isAdmin = false - if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = strongSelf.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { - isAdmin = true - } - - if attribute.timestamp == nil { - let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .suggestPost(needsTime: true, isAdmin: isAdmin, funds: funds), style: .default, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak strongSelf] time in - guard let strongSelf else { - return - } - progress?.set(.single(true)) - let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: time != 0 ? time : nil)).startStandalone(completed: { - progress?.set(.single(false)) - }) - }) - strongSelf.view.endEditing(true) - strongSelf.present(controller, in: .window(.root)) - - timestamp = Int32(Date().timeIntervalSince1970) + 1 * 60 * 60 - } else { - var textString: String - if isAdmin { - textString = strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_AdminConfirmationText(message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? "").string - - if let funds { - var commissionValue: String - commissionValue = "\(Double(funds.commissionPermille) * 0.1)" - if commissionValue.hasSuffix(".0") { - commissionValue = String(commissionValue[commissionValue.startIndex ..< commissionValue.index(commissionValue.endIndex, offsetBy: -2)]) - } else if commissionValue.hasSuffix(".00") { - commissionValue = String(commissionValue[commissionValue.startIndex ..< commissionValue.index(commissionValue.endIndex, offsetBy: -3)]) - } - - textString += "\n\n" - - switch funds.amount.currency { - case .stars: - let displayAmount = funds.amount.amount.totalValue * Double(funds.commissionPermille) / 1000.0 - textString += strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_AdminConfirmationPriceStars("\(displayAmount)", "\(commissionValue)").string - case .ton: - let displayAmount = Double(funds.amount.amount.value) / 1000000000.0 * Double(funds.commissionPermille) / 1000.0 - textString += strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_AdminConfirmationPriceTon("\(displayAmount)", "\(commissionValue)").string - } - } - } else { - textString = strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_UserConfirmationText + Task { @MainActor [weak strongSelf] in + guard let strongSelf else { + return } - strongSelf.present(SuggestedPostApproveAlert(presentationData: strongSelf.presentationData, title: strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_Title, text: textString, actions: [ - TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_Action, action: { [weak strongSelf] in + var timestamp: Int32? + var funds: (amount: CurrencyAmount, commissionPermille: Int)? + if let amount = attribute.amount { + let configuration = StarsSubscriptionConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) + funds = (amount, amount.currency == .stars ? Int(configuration.channelMessageSuggestionStarsCommissionPermille) : Int(configuration.channelMessageSuggestionTonCommissionPermille)) + } + + var isAdmin = false + if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = strongSelf.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { + isAdmin = true + } + + if !isAdmin, let funds { + switch funds.amount.currency { + case .stars: + var balance: StarsAmount? + if let starsContext = strongSelf.context.starsContext { + let state = await (starsContext.state |> take(1) |> deliverOnMainQueue).get() + balance = state?.balance + } + + if let balance, funds.amount.amount > balance { + guard let starsContext = strongSelf.context.starsContext else { + return + } + let _ = (strongSelf.context.engine.payments.starsTopUpOptions() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak strongSelf] options in + guard let strongSelf else { + return + } + let purchaseController = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .generic, completion: { _ in + }) + strongSelf.push(purchaseController) + }) + + return + } + case .ton: + var balance: StarsAmount? + if let tonContext = strongSelf.context.tonContext { + let state = await (tonContext.state |> take(1) |> deliverOnMainQueue).get() + balance = state?.balance + } + + if let balance, funds.amount.amount > balance { + let needed = funds.amount.amount - balance + var fragmentUrl = "https://fragment.com/ads/topup" + if let data = strongSelf.context.currentAppConfiguration.with({ $0 }).data, let value = data["ton_topup_url"] as? String { + fragmentUrl = value + } + strongSelf.push(BalanceNeededScreen( + context: strongSelf.context, + amount: needed, + buttonAction: { [weak strongSelf] in + guard let strongSelf else { + return + } + strongSelf.context.sharedContext.applicationBindings.openUrl(fragmentUrl) + } + )) + return + } + } + } + + if attribute.timestamp == nil { + let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .suggestPost(needsTime: true, isAdmin: isAdmin, funds: funds), style: .default, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak strongSelf] time in guard let strongSelf else { return } - let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: timestamp)).startStandalone() - }), - TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}) - ], actionLayout: .vertical, parseMarkdown: true, toastText: funds?.amount.currency == .stars ? strongSelf.presentationData.strings.Chat_PostSuggestion_StarsDisclaimer : nil), in: .window(.root)) + progress?.set(.single(true)) + let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: time != 0 ? time : nil)).startStandalone(completed: { + progress?.set(.single(false)) + }) + }) + strongSelf.view.endEditing(true) + strongSelf.present(controller, in: .window(.root)) + + timestamp = Int32(Date().timeIntervalSince1970) + 1 * 60 * 60 + } else { + var textString: String + if isAdmin { + textString = strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_AdminConfirmationText(message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? "").string + + if let funds { + var commissionValue: String + commissionValue = "\(Double(funds.commissionPermille) * 0.1)" + if commissionValue.hasSuffix(".0") { + commissionValue = String(commissionValue[commissionValue.startIndex ..< commissionValue.index(commissionValue.endIndex, offsetBy: -2)]) + } else if commissionValue.hasSuffix(".00") { + commissionValue = String(commissionValue[commissionValue.startIndex ..< commissionValue.index(commissionValue.endIndex, offsetBy: -3)]) + } + + textString += "\n\n" + + switch funds.amount.currency { + case .stars: + let displayAmount = funds.amount.amount.totalValue * Double(funds.commissionPermille) / 1000.0 + textString += strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_AdminConfirmationPriceStars("\(displayAmount)", "\(commissionValue)").string + case .ton: + let displayAmount = Double(funds.amount.amount.value) / 1000000000.0 * Double(funds.commissionPermille) / 1000.0 + textString += strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_AdminConfirmationPriceTon("\(displayAmount)", "\(commissionValue)").string + } + } + } else { + textString = strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_UserConfirmationText + } + + strongSelf.present(SuggestedPostApproveAlert(presentationData: strongSelf.presentationData, title: strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_Title, text: textString, actions: [ + TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_Action, action: { [weak strongSelf] in + guard let strongSelf else { + return + } + let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: timestamp)).startStandalone() + }), + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}) + ], actionLayout: .vertical, parseMarkdown: true, toastText: funds?.amount.currency == .stars ? strongSelf.presentationData.strings.Chat_PostSuggestion_StarsDisclaimer : nil), in: .window(.root)) + } } case 2: strongSelf.interfaceInteraction?.openSuggestPost(message, .default) diff --git a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift index 6369734e00..8cd85ac54b 100644 --- a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift +++ b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift @@ -389,7 +389,7 @@ extension ChatControllerImpl { return } - if let message = messages.values.compactMap({ $0 }).first(where: { message in message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) }), let attribute = message.attributes.first(where: { $0 is PublishedSuggestedPostMessageAttribute }) as? PublishedSuggestedPostMessageAttribute { + if let message = messages.values.compactMap({ $0 }).first(where: { message in message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) }), let attribute = message.attributes.first(where: { $0 is PublishedSuggestedPostMessageAttribute }) as? PublishedSuggestedPostMessageAttribute, message.timestamp > Int32(Date().timeIntervalSince1970) - 60 * 60 * 24 { let commit = { [weak self] in guard let self else { return diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 26c463dc18..63e4b1dd50 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -35,6 +35,7 @@ import CameraScreen import ShareController import ComposeTodoScreen import ComposePollUI +import Photos extension ChatControllerImpl { enum AttachMenuSubject { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 7c90e73501..ac76d68048 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -124,7 +124,7 @@ private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: EngineCo if let _ = attribute as? InlineBotMessageAttribute { hasUneditableAttributes = true break - } else if let _ = attribute as? PublishedSuggestedPostMessageAttribute { + } else if let _ = attribute as? PublishedSuggestedPostMessageAttribute, message.timestamp > Int32(Date().timeIntervalSince1970) - 60 * 60 * 24 { hasUneditableAttributes = true break } @@ -1924,7 +1924,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState }), false)) } else if !isUnremovableAction { var iconName: String = isSending ? "Chat/Context Menu/Clear" : "Chat/Context Menu/Delete" - if message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) { + if message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) && message.timestamp > Int32(Date().timeIntervalSince1970) - 60 * 60 * 24 { iconName = "Chat/Context Menu/DeletePaid" } diff --git a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift index 57fa738e09..13204771c7 100644 --- a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift @@ -21,12 +21,11 @@ import ComponentFlow import MediaScrubberComponent import AnimatedCountLabelNode -//Xcode 16 -#if canImport(ContactProvider) -extension AudioWaveformNode: @retroactive CustomMediaPlayerScrubbingForegroundNode { +#if SWIFT_PACKAGE +extension AudioWaveformNode: CustomMediaPlayerScrubbingForegroundNode { } #else -extension AudioWaveformNode: CustomMediaPlayerScrubbingForegroundNode { +extension AudioWaveformNode: @retroactive CustomMediaPlayerScrubbingForegroundNode { } #endif diff --git a/submodules/TelegramUI/Sources/CreateChannelController.swift b/submodules/TelegramUI/Sources/CreateChannelController.swift index 1d743308c0..c537e8b80d 100644 --- a/submodules/TelegramUI/Sources/CreateChannelController.swift +++ b/submodules/TelegramUI/Sources/CreateChannelController.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import Display +import SSignalKit import SwiftSignalKit import Postbox import TelegramCore @@ -20,6 +21,7 @@ import LegacyMediaPickerUI import TextFormat import AvatarEditorScreen import OldChannelsController +import AVFoundation private struct CreateChannelArguments { let context: AccountContext diff --git a/submodules/TelegramUI/Sources/CreateGroupController.swift b/submodules/TelegramUI/Sources/CreateGroupController.swift index 80c275fda4..d68f995c4c 100644 --- a/submodules/TelegramUI/Sources/CreateGroupController.swift +++ b/submodules/TelegramUI/Sources/CreateGroupController.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import Display +import SSignalKit import SwiftSignalKit import Postbox import TelegramCore @@ -33,6 +34,7 @@ import TextFormat import AvatarEditorScreen import SendInviteLinkScreen import OldChannelsController +import AVFoundation private struct CreateGroupArguments { let context: AccountContext diff --git a/submodules/TelegramUI/Sources/LegacyDataImportSplash.swift b/submodules/TelegramUI/Sources/LegacyDataImportSplash.swift index 801702b540..53b723f2bb 100644 --- a/submodules/TelegramUI/Sources/LegacyDataImportSplash.swift +++ b/submodules/TelegramUI/Sources/LegacyDataImportSplash.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import Display import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/TelegramUI/Sources/LockedWindowCoveringView.swift b/submodules/TelegramUI/Sources/LockedWindowCoveringView.swift index 6da82ea64e..3e28d52649 100644 --- a/submodules/TelegramUI/Sources/LockedWindowCoveringView.swift +++ b/submodules/TelegramUI/Sources/LockedWindowCoveringView.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import Display import TelegramPresentationData import AsyncDisplayKit diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index a887ae8853..c6f68a0004 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -775,18 +775,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon } } -//Xcode 16 -#if canImport(ContactProvider) -extension MediaEditorScreenImpl.Result: @retroactive MediaEditorScreenResult { - public var target: Stories.PendingTarget { - if let sendAsPeerId = self.options.sendAsPeerId { - return .peer(sendAsPeerId) - } else { - return .myStories - } - } -} -#else +#if SWIFT_PACKAGE extension MediaEditorScreenImpl.Result: MediaEditorScreenResult { public var target: Stories.PendingTarget { if let sendAsPeerId = self.options.sendAsPeerId { @@ -796,4 +785,14 @@ extension MediaEditorScreenImpl.Result: MediaEditorScreenResult { } } } +#else +extension MediaEditorScreenImpl.Result: @retroactive MediaEditorScreenResult { + public var target: Stories.PendingTarget { + if let sendAsPeerId = self.options.sendAsPeerId { + return .peer(sendAsPeerId) + } else { + return .myStories + } + } +} #endif From c5f2953e9093b5d237ad690ed53f26e6a6821444 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 1 Jul 2025 15:49:15 +0200 Subject: [PATCH 04/11] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 + .../Sources/AccountContext.swift | 7 +- .../Sources/AttachmentPanel.swift | 2 + .../Sources/CallListController.swift | 7 +- .../Sources/PresentationGroupCall.swift | 2 +- .../Sources/PeerInfoScreen.swift | 6 +- .../Sources/SendInviteLinkScreen.swift | 246 +++++++++++------- .../TelegramUI/Sources/ChatController.swift | 10 +- .../Sources/SharedAccountContext.swift | 6 +- 9 files changed, 188 insertions(+), 100 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 032ee57efd..4ac08046aa 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14667,3 +14667,5 @@ Sorry for the inconvenience."; "Chat.Todo.Message.Completed_any" = "%@ of {count} completed"; "Chat.Todo.Message.CompletedBy_1" = "%@ of {count} completed by {name}"; "Chat.Todo.Message.CompletedBy_any" = "%@ of {count} completed by {name}"; + +"SendInviteLink.TextCallsRestrictedSendOneInviteLink" = "**%@** restricts calling them. You can send them an invite link to call instead."; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index e7f0cf797a..1c5d42aa39 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1026,8 +1026,13 @@ public enum OldChannelsControllerIntent { } public enum SendInviteLinkScreenSubject { + public enum GroupCall { + case existing(link: String) + case create + } + case chat(peer: EnginePeer, link: String?) - case groupCall(link: String) + case groupCall(GroupCall) } public enum StarsWithdrawalScreenSubject { diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 4ba6792a0b..fe9de52114 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -1306,6 +1306,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { } else if let channel = peerViewMainPeer(view) as? TelegramChannel { if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = view.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { return nil + } else if let cachedData = view.cachedData as? CachedChannelData, let sendPaidMessageStarsValue = cachedData.sendPaidMessageStars, sendPaidMessageStarsValue == .zero { + return nil } else { return channel.sendPaidMessageStars } diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index 2fddede855..6c72ed9965 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -680,8 +680,11 @@ public final class CallListController: TelegramBaseController { } if let cachedUserData = view.cachedData as? CachedUserData, cachedUserData.callsPrivate { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + strongSelf.push(strongSelf.context.sharedContext.makeSendInviteLinkScreen(context: strongSelf.context, subject: .groupCall(.create), peers: [TelegramForbiddenInvitePeer( + peer: EnginePeer(peer), + canInviteWithPremium: false, + premiumRequiredToContact: false + )], theme: strongSelf.presentationData.theme)) return } diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 68f829c7f9..2f4d097495 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -3659,7 +3659,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { case let .privacy(peer): if let peer { if let currentInviteLinks = self.currentInviteLinks { - let inviteLinkScreen = self.accountContext.sharedContext.makeSendInviteLinkScreen(context: self.accountContext, subject: .groupCall(link: currentInviteLinks.listenerLink), peers: [TelegramForbiddenInvitePeer(peer: peer, canInviteWithPremium: false, premiumRequiredToContact: false)], theme: defaultDarkColorPresentationTheme) + let inviteLinkScreen = self.accountContext.sharedContext.makeSendInviteLinkScreen(context: self.accountContext, subject: .groupCall(.existing(link: currentInviteLinks.listenerLink)), peers: [TelegramForbiddenInvitePeer(peer: peer, canInviteWithPremium: false, premiumRequiredToContact: false)], theme: defaultDarkColorPresentationTheme) if let navigationController = self.accountContext.sharedContext.mainWindow?.viewController as? NavigationController { navigationController.pushViewController(inviteLinkScreen) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 082ec3e597..39b970207a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -7603,7 +7603,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } if cachedUserData.callsPrivate { - self.controller?.present(textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: self.presentationData.strings.Call_ConnectionErrorTitle, text: self.presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.controller?.push(self.context.sharedContext.makeSendInviteLinkScreen(context: self.context, subject: .groupCall(.create), peers: [TelegramForbiddenInvitePeer( + peer: EnginePeer(peer), + canInviteWithPremium: false, + premiumRequiredToContact: false + )], theme: self.presentationData.theme)) return } diff --git a/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift b/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift index 52d93e4eb3..3db8d0b7bc 100644 --- a/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift +++ b/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift @@ -118,6 +118,9 @@ private final class SendInviteLinkScreenComponent: Component { private var topOffsetDistance: CGFloat? + private var createCallDisposable: Disposable? + private var isInProgress: Bool = false + override init(frame: CGRect) { self.bottomOverscrollLimit = 200.0 @@ -178,6 +181,10 @@ private final class SendInviteLinkScreenComponent: Component { fatalError("init(coder:) has not been implemented") } + deinit { + self.createCallDisposable?.dispose() + } + func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.ignoreScrolling { self.updateScrolling(transition: .immediate) @@ -790,8 +797,18 @@ private final class SendInviteLinkScreenComponent: Component { } } } - case .groupCall: - text = environment.strings.SendInviteLink_TextCallsRestrictedSendInviteLink + case let .groupCall(groupCall): + switch groupCall { + case .create: + if component.peers.count == 1 { + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + text = environment.strings.SendInviteLink_TextCallsRestrictedSendOneInviteLink(component.peers[0].peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)).string + } else { + text = environment.strings.SendInviteLink_TextCallsRestrictedSendInviteLink + } + case .existing: + text = environment.strings.SendInviteLink_TextCallsRestrictedSendInviteLink + } } let body = MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor) @@ -828,79 +845,81 @@ private final class SendInviteLinkScreenComponent: Component { var itemsHeight: CGFloat = 0.0 var validIds: [AnyHashable] = [] - for i in 0 ..< component.peers.count { - let peer = component.peers[i] - - for _ in 0 ..< 1 { - //let id: AnyHashable = AnyHashable("\(peer.id)_\(j)") - let id = AnyHashable(peer.peer.id) - validIds.append(id) + if case .chat = component.subject { + for i in 0 ..< component.peers.count { + let peer = component.peers[i] - let item: ComponentView - var itemTransition = transition - if let current = self.items[id] { - item = current - } else { - itemTransition = .immediate - item = ComponentView() - self.items[id] = item - } - - let itemSubtitle: PeerListItemComponent.Subtitle - let canBeSelected : Bool - switch component.subject { - case let .chat(_, link): - canBeSelected = link != nil && !peer.premiumRequiredToContact - case .groupCall: - canBeSelected = true - } - if peer.premiumRequiredToContact { - itemSubtitle = .text(text: environment.strings.SendInviteLink_StatusAvailableToPremiumOnly, icon: .lock) - } else { - itemSubtitle = .presence(component.peerPresences[peer.peer.id]) - } - - let itemSize = item.update( - transition: itemTransition, - component: AnyComponent(PeerListItemComponent( - context: component.context, - theme: environment.theme, - strings: environment.strings, - sideInset: 0.0, - title: peer.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast), - subtitle: itemSubtitle, - peer: peer.peer, - selectionState: !canBeSelected ? .none : .editing(isSelected: self.selectedItems.contains(peer.peer.id)), - hasNext: i != component.peers.count - 1, - action: { [weak self] peer in - guard let self else { - return - } - if !canBeSelected { - return - } - if self.selectedItems.contains(peer.id) { - self.selectedItems.remove(peer.id) - } else { - self.selectedItems.insert(peer.id) - } - self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) - } - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) - ) - let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: itemsHeight), size: itemSize) - - if let itemView = item.view { - if itemView.superview == nil { - self.itemContainerView.addSubview(itemView) + for _ in 0 ..< 1 { + //let id: AnyHashable = AnyHashable("\(peer.id)_\(j)") + let id = AnyHashable(peer.peer.id) + validIds.append(id) + + let item: ComponentView + var itemTransition = transition + if let current = self.items[id] { + item = current + } else { + itemTransition = .immediate + item = ComponentView() + self.items[id] = item } - itemTransition.setFrame(view: itemView, frame: itemFrame) + + let itemSubtitle: PeerListItemComponent.Subtitle + let canBeSelected : Bool + switch component.subject { + case let .chat(_, link): + canBeSelected = link != nil && !peer.premiumRequiredToContact + case .groupCall: + canBeSelected = true + } + if peer.premiumRequiredToContact { + itemSubtitle = .text(text: environment.strings.SendInviteLink_StatusAvailableToPremiumOnly, icon: .lock) + } else { + itemSubtitle = .presence(component.peerPresences[peer.peer.id]) + } + + let itemSize = item.update( + transition: itemTransition, + component: AnyComponent(PeerListItemComponent( + context: component.context, + theme: environment.theme, + strings: environment.strings, + sideInset: 0.0, + title: peer.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast), + subtitle: itemSubtitle, + peer: peer.peer, + selectionState: !canBeSelected ? .none : .editing(isSelected: self.selectedItems.contains(peer.peer.id)), + hasNext: i != component.peers.count - 1, + action: { [weak self] peer in + guard let self else { + return + } + if !canBeSelected { + return + } + if self.selectedItems.contains(peer.id) { + self.selectedItems.remove(peer.id) + } else { + self.selectedItems.insert(peer.id) + } + self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) + ) + let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: itemsHeight), size: itemSize) + + if let itemView = item.view { + if itemView.superview == nil { + self.itemContainerView.addSubview(itemView) + } + itemTransition.setFrame(view: itemView, frame: itemFrame) + } + + itemsHeight += itemSize.height + singleItemHeight = itemSize.height } - - itemsHeight += itemSize.height - singleItemHeight = itemSize.height } } var removeIds: [AnyHashable] = [] @@ -917,9 +936,13 @@ private final class SendInviteLinkScreenComponent: Component { initialContentHeight += min(itemsHeight, floor(singleItemHeight * 2.5)) - contentHeight += itemsHeight - contentHeight += 24.0 - initialContentHeight += 24.0 + if itemsHeight != 0.0 { + contentHeight += itemsHeight + contentHeight += 24.0 + initialContentHeight += 24.0 + } else { + contentHeight += 4.0 + } let actionButtonTitle: String let actionButtonBadge: String? @@ -933,7 +956,7 @@ private final class SendInviteLinkScreenComponent: Component { actionButtonBadge = (self.selectedItems.isEmpty || link == nil) ? nil : "\(self.selectedItems.count)" case .groupCall: actionButtonTitle = environment.strings.SendInviteLink_ActionInvite - actionButtonBadge = self.selectedItems.isEmpty ? nil : "\(self.selectedItems.count)" + actionButtonBadge = nil } let actionButtonSize = actionButton.update( transition: transition, @@ -949,6 +972,7 @@ private final class SendInviteLinkScreenComponent: Component { animationName: nil, iconPosition: .right, iconSpacing: 4.0, + isLoading: self.isInProgress, action: { [weak self] in guard let self, let component = self.component, let controller = self.environment?.controller() else { return @@ -958,8 +982,62 @@ private final class SendInviteLinkScreenComponent: Component { switch component.subject { case let .chat(_, linkValue): link = linkValue - case let .groupCall(linkValue): - link = linkValue + case let .groupCall(groupCall): + switch groupCall { + case .create: + self.isInProgress = true + self.state?.updated(transition: .immediate) + + self.createCallDisposable = (component.context.engine.calls.createConferenceCall() + |> deliverOnMainQueue).startStrict(next: { [weak self] call in + guard let self, let component = self.component, let controller = self.environment?.controller() else { + return + } + + if self.selectedItems.isEmpty { + controller.dismiss() + } else { + let link = call.link + let selectedPeers = component.peers.filter { self.selectedItems.contains($0.peer.id) } + + self.presentPaidMessageAlertIfNeeded( + peers: selectedPeers.map { EngineRenderedPeer(peer: $0.peer) }, + requiresStars: component.sendPaidMessageStars, + completion: { [weak self] in + guard let self, let component = self.component, let controller = self.environment?.controller() else { + return + } + + for peerId in Array(self.selectedItems) { + var messageAttributes: [EngineMessage.Attribute] = [] + if let sendPaidMessageStars = component.sendPaidMessageStars[peerId] { + messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) + } + let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: [.message(text: link, attributes: messageAttributes, inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).startStandalone() + } + + let text: String + if selectedPeers.count == 1 { + text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string + } else if selectedPeers.count == 2 { + text = environment.strings.Conversation_ShareLinkTooltip_TwoChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), selectedPeers[1].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string + } else { + text = environment.strings.Conversation_ShareLinkTooltip_ManyChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), "\(selectedPeers.count - 1)").string + } + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + controller.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: false, text: text), elevatedLayout: false, action: { _ in return false }), in: .window(.root)) + + controller.dismiss() + } + ) + } + }) + + return + case let .existing(linkValue): + link = linkValue + } } if self.selectedItems.isEmpty { @@ -1082,7 +1160,6 @@ private final class SendInviteLinkScreenComponent: Component { public class SendInviteLinkScreen: ViewControllerComponentContainer { private let context: AccountContext - private let link: String? private let peers: [TelegramForbiddenInvitePeer] private var isDismissed: Bool = false @@ -1092,17 +1169,6 @@ public class SendInviteLinkScreen: ViewControllerComponentContainer { public init(context: AccountContext, subject: SendInviteLinkScreenSubject, peers: [TelegramForbiddenInvitePeer], theme: PresentationTheme? = nil) { self.context = context - switch subject { - case let .chat(peer, link): - var link = link - if link == nil, let addressName = peer.addressName { - link = "https://t.me/\(addressName)" - } - self.link = link - case let .groupCall(link): - self.link = link - } - #if DEBUG && false var peers = peers diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 2a5fa16522..1e3dc481c6 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2994,14 +2994,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return peerViewMainPeer(view) } |> deliverOnMainQueue).startStandalone(next: { peer in - guard let peer = peer else { + guard let peer else { return } if let cachedUserData = strongSelf.contentData?.state.peerView?.cachedData as? CachedUserData, cachedUserData.callsPrivate { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + strongSelf.push(strongSelf.context.sharedContext.makeSendInviteLinkScreen(context: strongSelf.context, subject: .groupCall(.create), peers: [TelegramForbiddenInvitePeer( + peer: EnginePeer(peer), + canInviteWithPremium: false, + premiumRequiredToContact: false + )], theme: strongSelf.presentationData.theme)) return } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index cc39fe338d..9a99810855 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2007,7 +2007,11 @@ public final class SharedAccountContextImpl: SharedAccountContext { if let cachedUserData = view.cachedData as? CachedUserData, cachedUserData.callsPrivate { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - parentController.present(textAlertController(context: context, title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + parentController.push(context.sharedContext.makeSendInviteLinkScreen(context: context, subject: .groupCall(.create), peers: [TelegramForbiddenInvitePeer( + peer: EnginePeer(peer), + canInviteWithPremium: false, + premiumRequiredToContact: false + )], theme: presentationData.theme)) return } From 7b186858259f801260a83d5e9ca57c2eca1e7f31 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 1 Jul 2025 16:10:32 +0200 Subject: [PATCH 05/11] Fix user suggest changes --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 +- .../Chat/ChatMessageActionOptions.swift | 73 ++++++++++++++----- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 4ac08046aa..2e8addd742 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14623,7 +14623,7 @@ Sorry for the inconvenience."; "VoiceOver.SuggestPost" = "Suggest Post"; "Chat.ContextMenu.SuggestedPost.EditMessage" = "Edit Message"; -"Chat.ContextMenu.SuggestedPost.EditPrice" = "Edit Prive"; +"Chat.ContextMenu.SuggestedPost.EditPrice" = "Edit Price"; "Chat.ContextMenu.SuggestedPost.EditTime" = "Edit Time"; "Chat.ContextMenu.SuggestedPost.Create" = "Suggest Post"; diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 4db78b786f..410da4a718 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -989,27 +989,64 @@ extension ChatControllerImpl { let subject: StarsWithdrawalScreenSubject if postSuggestionState.editingOriginalMessageId != nil { - subject = .postSuggestionModification(current: postSuggestionState.price ?? CurrencyAmount(amount: .zero, currency: .stars), timestamp: postSuggestionState.timestamp, completion: { [weak self] price, timestamp in - guard let self else { - return + var isFromAdmin = false + if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum { + if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { + isFromAdmin = true } - - let price: CurrencyAmount? = price.amount == .zero ? nil : price - - self.updateChatPresentationInterfaceState(interactive: true, { state in - var state = state - state = state.updatedInterfaceState { interfaceState in - var interfaceState = interfaceState - interfaceState = interfaceState.withUpdatedPostSuggestionState(ChatInterfaceState.PostSuggestionState( - editingOriginalMessageId: interfaceState.postSuggestionState?.editingOriginalMessageId, - price: price, - timestamp: timestamp - )) - return interfaceState + } + + if isFromAdmin { + subject = .postSuggestionModification(current: postSuggestionState.price ?? CurrencyAmount(amount: .zero, currency: .stars), timestamp: postSuggestionState.timestamp, completion: { [weak self] price, timestamp in + guard let self else { + return } - return state + + let price: CurrencyAmount? = price.amount == .zero ? nil : price + + self.updateChatPresentationInterfaceState(interactive: true, { state in + var state = state + state = state.updatedInterfaceState { interfaceState in + var interfaceState = interfaceState + interfaceState = interfaceState.withUpdatedPostSuggestionState(ChatInterfaceState.PostSuggestionState( + editingOriginalMessageId: interfaceState.postSuggestionState?.editingOriginalMessageId, + price: price, + timestamp: timestamp + )) + return interfaceState + } + return state + }) }) - }) + } else { + subject = .postSuggestion( + channel: .channel(channel), + isFromAdmin: false, + current: postSuggestionState.price ?? CurrencyAmount(amount: .zero, currency: .stars), + timestamp: postSuggestionState.timestamp, + completion: { [weak self] price, timestamp in + guard let self else { + return + } + + let price: CurrencyAmount? = price.amount == .zero ? nil : price + + self.updateChatPresentationInterfaceState(interactive: true, { state in + var state = state + state = state.updatedInterfaceState { interfaceState in + var interfaceState = interfaceState + interfaceState = interfaceState.withUpdatedPostSuggestionState(ChatInterfaceState.PostSuggestionState( + editingOriginalMessageId: interfaceState.postSuggestionState?.editingOriginalMessageId, + price: price, + timestamp: timestamp + )) + return interfaceState + } + return state + }) + } + ) + } } else { var isFromAdmin = false if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum { From 21e8bc02cadc8e9996eef72e882ec0b9f9976ba5 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 1 Jul 2025 19:38:35 +0200 Subject: [PATCH 06/11] Cleanup --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 + .../Components/AnimationCache/ImageDCT/BUILD | 2 + .../AnimationCache/ImageDCT/Sources/DCT.cpp | 42 ++----------------- .../Sources/StarsWithdrawalScreen.swift | 4 +- .../TelegramUI/Sources/ChatController.swift | 6 +++ third-party/webrtc/BUILD | 4 +- 6 files changed, 18 insertions(+), 42 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 2e8addd742..ccfb268e9c 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14448,6 +14448,8 @@ Sorry for the inconvenience."; "SuggestPost.SetTimeFormat.Date" = "%@"; "SuggestPost.SetTimeFormat.TodayAt" = "Today at %@"; "SuggestPost.SetTimeFormat.TomorrowAt" = "Tomorrow at %@"; +"SuggestPost.SetTimeFormat.Any" = "Anytime"; +"SuggestPost.SetTime.Label" = "Time"; "Chat.TodoItemCompletionTimestamp.Date" = "completed %@"; "Chat.TodoItemCompletionTimestamp.TodayAt" = "completed today at %@"; diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/BUILD b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/BUILD index 030187e3e1..fdf548af65 100644 --- a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/BUILD +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/BUILD @@ -16,6 +16,8 @@ objc_library( includes = [ "PublicHeaders", ], + copts = [ + ], sdk_frameworks = [ "Foundation", "Accelerate", diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.cpp b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.cpp index e918a229aa..d953cefbbe 100644 --- a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.cpp +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.cpp @@ -371,43 +371,9 @@ typedef int16_t tran_low_t; typedef int32_t tran_high_t; typedef int16_t tran_coef_t; -static const tran_coef_t cospi_1_64 = 16364; -static const tran_coef_t cospi_2_64 = 16305; -static const tran_coef_t cospi_3_64 = 16207; -static const tran_coef_t cospi_4_64 = 16069; -static const tran_coef_t cospi_5_64 = 15893; -static const tran_coef_t cospi_6_64 = 15679; -static const tran_coef_t cospi_7_64 = 15426; static const tran_coef_t cospi_8_64 = 15137; -static const tran_coef_t cospi_9_64 = 14811; -static const tran_coef_t cospi_10_64 = 14449; -static const tran_coef_t cospi_11_64 = 14053; -static const tran_coef_t cospi_12_64 = 13623; -static const tran_coef_t cospi_13_64 = 13160; -static const tran_coef_t cospi_14_64 = 12665; -static const tran_coef_t cospi_15_64 = 12140; static const tran_coef_t cospi_16_64 = 11585; -static const tran_coef_t cospi_17_64 = 11003; -static const tran_coef_t cospi_18_64 = 10394; -static const tran_coef_t cospi_19_64 = 9760; -static const tran_coef_t cospi_20_64 = 9102; -static const tran_coef_t cospi_21_64 = 8423; -static const tran_coef_t cospi_22_64 = 7723; -static const tran_coef_t cospi_23_64 = 7005; static const tran_coef_t cospi_24_64 = 6270; -static const tran_coef_t cospi_25_64 = 5520; -static const tran_coef_t cospi_26_64 = 4756; -static const tran_coef_t cospi_27_64 = 3981; -static const tran_coef_t cospi_28_64 = 3196; -static const tran_coef_t cospi_29_64 = 2404; -static const tran_coef_t cospi_30_64 = 1606; -static const tran_coef_t cospi_31_64 = 804; - -// 16384 * sqrt(2) * sin(kPi/9) * 2 / 3 -static const tran_coef_t sinpi_1_9 = 5283; -static const tran_coef_t sinpi_2_9 = 9929; -static const tran_coef_t sinpi_3_9 = 13377; -static const tran_coef_t sinpi_4_9 = 15212; #define DCT_CONST_BITS 14 #define DCT_CONST_ROUNDING (1 << (DCT_CONST_BITS - 1)) @@ -490,7 +456,7 @@ void vpx_fdct4x4_c(const int16_t *input, tran_low_t *output, int stride) { #define ROUND_POWER_OF_TWO(value, n) (((value) + (1 << ((n)-1))) >> (n)) -static inline tran_high_t dct_const_round_shift(tran_high_t input) { +/*static inline tran_high_t dct_const_round_shift(tran_high_t input) { tran_high_t rv = ROUND_POWER_OF_TWO(input, DCT_CONST_BITS); return (tran_high_t)rv; } @@ -507,11 +473,11 @@ static inline tran_high_t check_range(tran_high_t input) { assert(input <= INT16_MAX); #endif // CONFIG_COEFFICIENT_RANGE_CHECKING return input; -} +}*/ #define WRAPLOW(x) ((int32_t)check_range(x)) -void idct4_c(const tran_low_t *input, tran_low_t *output) { +/*void idct4_c(const tran_low_t *input, tran_low_t *output) { int16_t step[4]; tran_high_t temp1, temp2; @@ -553,7 +519,7 @@ void vpx_idct4x4_16_add_c(const tran_low_t *input, tran_low_t *dest, int stride) dest[j * stride + i] = ROUND_POWER_OF_TWO(temp_out[j], 4); } } -} +}*/ #if defined(__aarch64__) diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 0195e200c4..24ccaedbe6 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -603,7 +603,7 @@ private final class SheetContent: CombinedComponent { } )).string } else { - timeString = "Anytime" + timeString = strings.SuggestPost_SetTimeFormat_Any } let timestampSection = timestampSection.update( @@ -618,7 +618,7 @@ private final class SheetContent: CombinedComponent { title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "Time", + string: environment.strings.SuggestPost_SetTime_Label, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor )), diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 1e3dc481c6..e4dea9326f 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2379,6 +2379,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G funds = (amount, amount.currency == .stars ? Int(configuration.channelMessageSuggestionStarsCommissionPermille) : Int(configuration.channelMessageSuggestionTonCommissionPermille)) } + #if DEBUG + if "".isEmpty { + funds = nil + } + #endif + var isAdmin = false if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = strongSelf.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { isAdmin = true diff --git a/third-party/webrtc/BUILD b/third-party/webrtc/BUILD index 16f615376c..37837b1606 100644 --- a/third-party/webrtc/BUILD +++ b/third-party/webrtc/BUILD @@ -3061,7 +3061,7 @@ objc_library( "webrtc/rtc_base/system/gcd_helpers.m", ], copts = [ - "-Wno-all", + "-w", "-Ithird-party/webrtc/webrtc/", "-Ithird-party/webrtc/webrtc/sdk/objc", "-Ithird-party/webrtc/webrtc/sdk/objc/base", @@ -3124,7 +3124,7 @@ objc_library( module_name = "webrtc", srcs = combined_sources, copts = [ - "-Wno-all", + "-w", "-Ithird-party/webrtc/libsrtp/third_party/libsrtp/include", "-Ithird-party/webrtc/libsrtp/third_party/libsrtp/crypto/include", "-Ithird-party/webrtc/libsrtp", From 800f7d3ac28ef5ab890b886117c5e2b68d5f3e49 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 1 Jul 2025 19:38:45 +0200 Subject: [PATCH 07/11] Update tgcalls --- submodules/TgVoipWebrtc/BUILD | 3 ++- submodules/TgVoipWebrtc/tgcalls | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/submodules/TgVoipWebrtc/BUILD b/submodules/TgVoipWebrtc/BUILD index 0d5db1e358..14cc452523 100644 --- a/submodules/TgVoipWebrtc/BUILD +++ b/submodules/TgVoipWebrtc/BUILD @@ -153,7 +153,8 @@ objc_library( "-DRTC_ENABLE_VP9", "-DTGVOIP_NAMESPACE=tgvoip_webrtc", "-std=c++17", - "-Werror", + "-w", + #"-Werror", ] + optimization_flags, includes = [ "PublicHeaders", diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index a8accf0f90..47d3534333 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit a8accf0f9058a8c3d01104225349c046b34a3e3b +Subproject commit 47d35343336fc93c012f15d104d0eb521a9f0f1f From a6ad87dc63aea3288ddfc67641252f8f2dcda6b4 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 1 Jul 2025 19:38:56 +0200 Subject: [PATCH 08/11] Bump version --- versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.json b/versions.json index 492f69fe30..068e98bf9d 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "11.13", + "app": "11.13.1", "xcode": "16.2", "bazel": "8.2.1:22ff65b05869f6160e5157b1b425a14a62085d71d8baef571f462b8fe5a703a3", "macos": "15" From 37cea84896c0f5825669e02753d45b20d63f3871 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 2 Jul 2025 14:30:55 +0200 Subject: [PATCH 09/11] Update API --- .../Telegram-iOS/en.lproj/Localizable.strings | 8 +- submodules/TelegramApi/Sources/Api0.swift | 6 +- submodules/TelegramApi/Sources/Api24.swift | 40 ++++--- submodules/TelegramApi/Sources/Api36.swift | 34 ++++-- .../Sources/State/AccountTaskManager.swift | 2 +- .../Sources/State/ManagedRecentStickers.swift | 3 +- .../Sources/State/Serialization.swift | 2 +- .../SyncCore_TelegramMediaAction.swift | 10 +- .../TelegramEngine/Payments/StarGifts.swift | 59 ++++++++-- .../Payments/TelegramEnginePayments.swift | 2 +- .../ChatMessageGiftBubbleContentNode.swift | 107 ++++++++++++++++-- .../ChatMessageTodoBubbleContentNode.swift | 26 ++++- .../Sources/GiftViewScreen.swift | 80 ++++++++++++- .../Sources/UserApperanceScreen.swift | 3 +- .../TelegramUI/Sources/ChatController.swift | 12 ++ .../ChatControllerOpenAttachmentMenu.swift | 14 +++ 16 files changed, 348 insertions(+), 60 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index c29cb27779..43dd46251f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14496,7 +14496,8 @@ Sorry for the inconvenience."; "Chat.Todo.ContextMenu.SectionsInfo" = "You're viewing actions for one task.\nYou can switch to actions for the list."; "Chat.Todo.PremiumRequired" = "Only [Telegram Premium]() subscribers can mark tasks as done."; -"Chat.Todo.CompletionLimited" = "%@ has restricted others from editing this to do list."; +"Chat.Todo.CompletionLimited" = "%@ has restricted others from editing this checklist."; +"Chat.Todo.CompletionLimitedForward" = "You can’t make changes to forwarded checklists."; "Forward.ErrorTodoDisabledInChannels" = "Sorry, checklists can’t be forwarded to channels."; @@ -14662,3 +14663,8 @@ Sorry for the inconvenience."; "Chat.Todo.Message.Completed_any" = "%@ of {count} completed"; "Chat.Todo.Message.CompletedBy_1" = "%@ of {count} completed by {name}"; "Chat.Todo.Message.CompletedBy_any" = "%@ of {count} completed by {name}"; + +"Notification.StarGift.ReleasedBy" = "released by %@"; + +"Gift.View.ReleasedBy" = "released by %@"; +"Gift.Unique.CollectibleBy" = "collectible %1$@ by %2$@"; diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 980686598f..86b413f239 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -943,8 +943,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[2109703795] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) } dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) } dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($0) } - dict[-970274264] = { return Api.StarGift.parse_starGift($0) } - dict[1678891913] = { return Api.StarGift.parse_starGiftUnique($0) } + dict[2139438098] = { return Api.StarGift.parse_starGift($0) } + dict[-164136786] = { return Api.StarGift.parse_starGiftUnique($0) } dict[-650279524] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) } dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) } dict[-524291476] = { return Api.StarGiftAttribute.parse_starGiftAttributeOriginalDetails($0) } @@ -1432,7 +1432,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1779201615] = { return Api.payments.SavedStarGifts.parse_savedStarGifts($0) } dict[377215243] = { return Api.payments.StarGiftUpgradePreview.parse_starGiftUpgradePreview($0) } dict[-2069218660] = { return Api.payments.StarGiftWithdrawalUrl.parse_starGiftWithdrawalUrl($0) } - dict[-1877571094] = { return Api.payments.StarGifts.parse_starGifts($0) } + dict[785918357] = { return Api.payments.StarGifts.parse_starGifts($0) } dict[-1551326360] = { return Api.payments.StarGifts.parse_starGiftsNotModified($0) } dict[961445665] = { return Api.payments.StarsRevenueAdsAccountUrl.parse_starsRevenueAdsAccountUrl($0) } dict[1814066038] = { return Api.payments.StarsRevenueStats.parse_starsRevenueStats($0) } diff --git a/submodules/TelegramApi/Sources/Api24.swift b/submodules/TelegramApi/Sources/Api24.swift index ecb0e13970..50f5d74a4f 100644 --- a/submodules/TelegramApi/Sources/Api24.swift +++ b/submodules/TelegramApi/Sources/Api24.swift @@ -636,14 +636,14 @@ public extension Api { } public extension Api { enum StarGift: TypeConstructorDescription { - case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?) - case starGiftUnique(flags: Int32, id: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellStars: Int64?) + case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?, releasedBy: Api.Peer?) + case starGiftUnique(flags: Int32, id: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellStars: Int64?, releasedBy: Api.Peer?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title): + case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy): if boxed { - buffer.appendInt32(-970274264) + buffer.appendInt32(2139438098) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -658,10 +658,11 @@ public extension Api { if Int(flags) & Int(1 << 3) != 0 {serializeInt64(upgradeStars!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 4) != 0 {serializeInt64(resellMinStars!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 5) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 6) != 0 {releasedBy!.serialize(buffer, true)} break - case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars): + case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars, let releasedBy): if boxed { - buffer.appendInt32(1678891913) + buffer.appendInt32(-164136786) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -680,16 +681,17 @@ public extension Api { serializeInt32(availabilityTotal, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 3) != 0 {serializeString(giftAddress!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 4) != 0 {serializeInt64(resellStars!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 5) != 0 {releasedBy!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title): - return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("availabilityResale", availabilityResale as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any), ("resellMinStars", resellMinStars as Any), ("title", title as Any)]) - case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars): - return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellStars", resellStars as Any)]) + case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy): + return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("availabilityResale", availabilityResale as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any), ("resellMinStars", resellMinStars as Any), ("title", title as Any), ("releasedBy", releasedBy as Any)]) + case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars, let releasedBy): + return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellStars", resellStars as Any), ("releasedBy", releasedBy as Any)]) } } @@ -722,6 +724,10 @@ public extension Api { if Int(_1!) & Int(1 << 4) != 0 {_12 = reader.readInt64() } var _13: String? if Int(_1!) & Int(1 << 5) != 0 {_13 = parseString(reader) } + var _14: Api.Peer? + if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { + _14 = Api.parse(reader, signature: signature) as? Api.Peer + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -735,8 +741,9 @@ public extension Api { let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil let _c12 = (Int(_1!) & Int(1 << 4) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 5) == 0) || _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, availabilityResale: _7, convertStars: _8!, firstSaleDate: _9, lastSaleDate: _10, upgradeStars: _11, resellMinStars: _12, title: _13) + let _c14 = (Int(_1!) & Int(1 << 6) == 0) || _14 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { + return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, availabilityResale: _7, convertStars: _8!, firstSaleDate: _9, lastSaleDate: _10, upgradeStars: _11, resellMinStars: _12, title: _13, releasedBy: _14) } else { return nil @@ -773,6 +780,10 @@ public extension Api { if Int(_1!) & Int(1 << 3) != 0 {_12 = parseString(reader) } var _13: Int64? if Int(_1!) & Int(1 << 4) != 0 {_13 = reader.readInt64() } + var _14: Api.Peer? + if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { + _14 = Api.parse(reader, signature: signature) as? Api.Peer + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -786,8 +797,9 @@ public extension Api { let _c11 = _11 != nil let _c12 = (Int(_1!) & Int(1 << 3) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 4) == 0) || _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, title: _3!, slug: _4!, num: _5!, ownerId: _6, ownerName: _7, ownerAddress: _8, attributes: _9!, availabilityIssued: _10!, availabilityTotal: _11!, giftAddress: _12, resellStars: _13) + let _c14 = (Int(_1!) & Int(1 << 5) == 0) || _14 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { + return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, title: _3!, slug: _4!, num: _5!, ownerId: _6, ownerName: _7, ownerAddress: _8, attributes: _9!, availabilityIssued: _10!, availabilityTotal: _11!, giftAddress: _12, resellStars: _13, releasedBy: _14) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api36.swift b/submodules/TelegramApi/Sources/Api36.swift index c7e80100b6..b25d7db3c7 100644 --- a/submodules/TelegramApi/Sources/Api36.swift +++ b/submodules/TelegramApi/Sources/Api36.swift @@ -298,14 +298,14 @@ public extension Api.payments { } public extension Api.payments { enum StarGifts: TypeConstructorDescription { - case starGifts(hash: Int32, gifts: [Api.StarGift]) + case starGifts(hash: Int32, gifts: [Api.StarGift], chats: [Api.Chat], users: [Api.User]) case starGiftsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starGifts(let hash, let gifts): + case .starGifts(let hash, let gifts, let chats, let users): if boxed { - buffer.appendInt32(-1877571094) + buffer.appendInt32(785918357) } serializeInt32(hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) @@ -313,6 +313,16 @@ public extension Api.payments { for item in gifts { item.serialize(buffer, true) } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } break case .starGiftsNotModified: if boxed { @@ -325,8 +335,8 @@ public extension Api.payments { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .starGifts(let hash, let gifts): - return ("starGifts", [("hash", hash as Any), ("gifts", gifts as Any)]) + case .starGifts(let hash, let gifts, let chats, let users): + return ("starGifts", [("hash", hash as Any), ("gifts", gifts as Any), ("chats", chats as Any), ("users", users as Any)]) case .starGiftsNotModified: return ("starGiftsNotModified", []) } @@ -339,10 +349,20 @@ public extension Api.payments { if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGift.self) } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.payments.StarGifts.starGifts(hash: _1!, gifts: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.payments.StarGifts.starGifts(hash: _1!, gifts: _2!, chats: _3!, users: _4!) } else { return nil diff --git a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift index 48ff0f9e03..e620c3e0b4 100644 --- a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift @@ -122,7 +122,7 @@ final class AccountTaskManager { tasks.add(managedDisabledChannelStatusIconEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(_internal_loadedStickerPack(postbox: self.stateManager.postbox, network: self.stateManager.network, reference: .iconTopicEmoji, forceActualized: true).start()) tasks.add(managedPeerColorUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) - tasks.add(managedStarGiftsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) + tasks.add(managedStarGiftsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start()) self.managedTopReactionsDisposable.set(managedTopReactions(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) diff --git a/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift b/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift index 7ee61d5784..b7af0258ec 100644 --- a/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift +++ b/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift @@ -354,7 +354,8 @@ func managedUniqueStarGifts(accountPeerId: PeerId, postbox: Postbox, network: Ne ], availability: StarGift.UniqueGift.Availability(issued: 0, total: 0), giftAddress: nil, - resellStars: nil + resellStars: nil, + releasedBy: nil ) if let entry = CodableEntry(RecentStarGiftItem(gift)) { items.append(OrderedItemListEntry(id: RecentStarGiftItemId(id).rawValue, contents: entry)) diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index 95b93fef4c..ab9b2a4f2e 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 206 + return 207 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index a079328a77..f935eac984 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -869,7 +869,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { return [peerId] case let .prizeStars(_, _, boostPeerId, _, _): return boostPeerId.flatMap { [$0] } ?? [] - case let .starGift(_, _, _, _, _, _, _, _, _, _, _, _, peerId, senderId, _): + case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, peerId, senderId, _): var peerIds: [PeerId] = [] if let peerId { peerIds.append(peerId) @@ -877,8 +877,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { if let senderId { peerIds.append(senderId) } + if let releasedBy = gift.releasedBy { + peerIds.append(releasedBy) + } return peerIds - case let .starGiftUnique(_, _, _, _, _, _, _, peerId, senderId, _, _, _, _): + case let .starGiftUnique(gift, _, _, _, _, _, _, peerId, senderId, _, _, _, _): var peerIds: [PeerId] = [] if let peerId { peerIds.append(peerId) @@ -886,6 +889,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { if let senderId { peerIds.append(senderId) } + if let releasedBy = gift.releasedBy { + peerIds.append(releasedBy) + } return peerIds case let .conferenceCall(conferenceCall): return conferenceCall.otherParticipants diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index 1e93ee353b..02a94cafb6 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -54,6 +54,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { case soldOut case flags case upgradeStars + case releasedBy } public struct Availability: Equatable, Codable, PostboxCoding { @@ -149,8 +150,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding { public let soldOut: SoldOut? public let flags: Flags public let upgradeStars: Int64? + public let releasedBy: EnginePeer.Id? - public init(id: Int64, title: String?, file: TelegramMediaFile, price: Int64, convertStars: Int64, availability: Availability?, soldOut: SoldOut?, flags: Flags, upgradeStars: Int64?) { + public init(id: Int64, title: String?, file: TelegramMediaFile, price: Int64, convertStars: Int64, availability: Availability?, soldOut: SoldOut?, flags: Flags, upgradeStars: Int64?, releasedBy: EnginePeer.Id?) { self.id = id self.title = title self.file = file @@ -160,6 +162,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.soldOut = soldOut self.flags = flags self.upgradeStars = upgradeStars + self.releasedBy = releasedBy } public init(from decoder: Decoder) throws { @@ -179,6 +182,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.soldOut = try container.decodeIfPresent(SoldOut.self, forKey: .soldOut) self.flags = Flags(rawValue: try container .decodeIfPresent(Int32.self, forKey: .flags) ?? 0) self.upgradeStars = try container.decodeIfPresent(Int64.self, forKey: .upgradeStars) + self.releasedBy = try container.decodeIfPresent(EnginePeer.Id.self, forKey: .releasedBy) } public init(decoder: PostboxDecoder) { @@ -191,6 +195,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.soldOut = decoder.decodeObjectForKey(CodingKeys.soldOut.rawValue, decoder: { StarGift.Gift.SoldOut(decoder: $0) }) as? StarGift.Gift.SoldOut self.flags = Flags(rawValue: decoder.decodeInt32ForKey(CodingKeys.flags.rawValue, orElse: 0)) self.upgradeStars = decoder.decodeOptionalInt64ForKey(CodingKeys.upgradeStars.rawValue) + self.releasedBy = decoder.decodeOptionalInt64ForKey(CodingKeys.releasedBy.rawValue).flatMap { EnginePeer.Id($0) } } public func encode(to encoder: Encoder) throws { @@ -209,6 +214,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { try container.encodeIfPresent(self.soldOut, forKey: .soldOut) try container.encode(self.flags.rawValue, forKey: .flags) try container.encodeIfPresent(self.upgradeStars, forKey: .upgradeStars) + try container.encodeIfPresent(self.releasedBy, forKey: .releasedBy) } public func encode(_ encoder: PostboxEncoder) { @@ -237,6 +243,11 @@ public enum StarGift: Equatable, Codable, PostboxCoding { } else { encoder.encodeNil(forKey: CodingKeys.upgradeStars.rawValue) } + if let releasedBy = self.releasedBy { + encoder.encodeInt64(releasedBy.toInt64(), forKey: CodingKeys.releasedBy.rawValue) + } else { + encoder.encodeNil(forKey: CodingKeys.releasedBy.rawValue) + } } } @@ -253,6 +264,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { case availability case giftAddress case resellStars + case releasedBy } public enum Attribute: Equatable, Codable, PostboxCoding { @@ -506,8 +518,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding { public let availability: Availability public let giftAddress: String? public let resellStars: Int64? + public let releasedBy: EnginePeer.Id? - public init(id: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability, giftAddress: String?, resellStars: Int64?) { + public init(id: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability, giftAddress: String?, resellStars: Int64?, releasedBy: EnginePeer.Id?) { self.id = id self.title = title self.number = number @@ -517,6 +530,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.availability = availability self.giftAddress = giftAddress self.resellStars = resellStars + self.releasedBy = releasedBy } public init(from decoder: Decoder) throws { @@ -538,6 +552,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.availability = try container.decode(UniqueGift.Availability.self, forKey: .availability) self.giftAddress = try container.decodeIfPresent(String.self, forKey: .giftAddress) self.resellStars = try container.decodeIfPresent(Int64.self, forKey: .resellStars) + self.releasedBy = try container.decodeIfPresent(EnginePeer.Id.self, forKey: .releasedBy) } public init(decoder: PostboxDecoder) { @@ -558,6 +573,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.availability = decoder.decodeObjectForKey(CodingKeys.availability.rawValue, decoder: { UniqueGift.Availability(decoder: $0) }) as! UniqueGift.Availability self.giftAddress = decoder.decodeOptionalStringForKey(CodingKeys.giftAddress.rawValue) self.resellStars = decoder.decodeOptionalInt64ForKey(CodingKeys.resellStars.rawValue) + self.releasedBy = decoder.decodeOptionalInt64ForKey(CodingKeys.releasedBy.rawValue).flatMap { EnginePeer.Id($0) } } public func encode(to encoder: Encoder) throws { @@ -578,6 +594,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { try container.encode(self.availability, forKey: .availability) try container.encodeIfPresent(self.giftAddress, forKey: .giftAddress) try container.encodeIfPresent(self.resellStars, forKey: .resellStars) + try container.encodeIfPresent(self.releasedBy, forKey: .releasedBy) } public func encode(_ encoder: PostboxEncoder) { @@ -605,6 +622,11 @@ public enum StarGift: Equatable, Codable, PostboxCoding { } else { encoder.encodeNil(forKey: CodingKeys.resellStars.rawValue) } + if let releasedBy = self.releasedBy { + encoder.encodeInt64(releasedBy.toInt64(), forKey: CodingKeys.releasedBy.rawValue) + } else { + encoder.encodeNil(forKey: CodingKeys.releasedBy.rawValue) + } } public func withResellStars(_ resellStars: Int64?) -> UniqueGift { @@ -617,7 +639,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding { attributes: self.attributes, availability: self.availability, giftAddress: self.giftAddress, - resellStars: resellStars + resellStars: resellStars, + releasedBy: self.releasedBy ) } } @@ -683,10 +706,21 @@ public enum StarGift: Equatable, Codable, PostboxCoding { } } +public extension StarGift { + var releasedBy: EnginePeer.Id? { + switch self { + case let .generic(gift): + return gift.releasedBy + case let .unique(gift): + return gift.releasedBy + } + } +} + extension StarGift { init?(apiStarGift: Api.StarGift) { switch apiStarGift { - case let .starGift(apiFlags, id, sticker, stars, availabilityRemains, availabilityTotal, availabilityResale, convertStars, firstSale, lastSale, upgradeStars, minResaleStars, title): + case let .starGift(apiFlags, id, sticker, stars, availabilityRemains, availabilityTotal, availabilityResale, convertStars, firstSale, lastSale, upgradeStars, minResaleStars, title, releasedBy): var flags = StarGift.Gift.Flags() if (apiFlags & (1 << 2)) != 0 { flags.insert(.isBirthdayGift) @@ -708,8 +742,8 @@ extension StarGift { guard let file = telegramMediaFileFromApiDocument(sticker, altDocuments: nil) else { return nil } - self = .generic(StarGift.Gift(id: id, title: title, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars)) - case let .starGiftUnique(_, id, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, reselltars): + self = .generic(StarGift.Gift(id: id, title: title, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars, releasedBy: releasedBy?.peerId)) + case let .starGiftUnique(_, id, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellStars, releasedBy): let owner: StarGift.UniqueGift.Owner if let ownerAddress { owner = .address(ownerAddress) @@ -720,7 +754,7 @@ extension StarGift { } else { return nil } - self = .unique(StarGift.UniqueGift(id: id, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal), giftAddress: giftAddress, resellStars: reselltars)) + self = .unique(StarGift.UniqueGift(id: id, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal), giftAddress: giftAddress, resellStars: resellStars, releasedBy: releasedBy?.peerId)) } } } @@ -739,7 +773,7 @@ func _internal_cachedStarGifts(postbox: Postbox) -> Signal Signal { +func _internal_keepCachedStarGiftsUpdated(postbox: Postbox, network: Network, accountPeerId: EnginePeer.Id) -> Signal { let updateSignal = _internal_cachedStarGifts(postbox: postbox) |> take(1) |> mapToSignal { list -> Signal in @@ -755,7 +789,10 @@ func _internal_keepCachedStarGiftsUpdated(postbox: Postbox, network: Network) -> return postbox.transaction { transaction in switch result { - case let .starGifts(hash, gifts): + case let .starGifts(hash, gifts, chats, users): + let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) + updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) + let starGiftsLists = StarGiftsList(items: gifts.compactMap { StarGift(apiStarGift: $0) }, hashValue: hash) transaction.setPreferencesEntry(key: PreferencesKeys.starGifts(), value: PreferencesEntry(starGiftsLists)) case .starGiftsNotModified: @@ -769,8 +806,8 @@ func _internal_keepCachedStarGiftsUpdated(postbox: Postbox, network: Network) -> return updateSignal } -func managedStarGiftsUpdates(postbox: Postbox, network: Network) -> Signal { - let poll = _internal_keepCachedStarGiftsUpdated(postbox: postbox, network: network) +func managedStarGiftsUpdates(postbox: Postbox, network: Network, accountPeerId: EnginePeer.Id) -> Signal { + let poll = _internal_keepCachedStarGiftsUpdated(postbox: postbox, network: network, accountPeerId: accountPeerId) return (poll |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index 80b066523a..2ab196f326 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -114,7 +114,7 @@ public extension TelegramEngine { } public func keepStarGiftsUpdated() -> Signal { - return _internal_keepCachedStarGiftsUpdated(postbox: self.account.postbox, network: self.account.network) + return _internal_keepCachedStarGiftsUpdated(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId) } public func convertStarGift(reference: StarGiftReference) -> Signal { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index 08856d1830..296bab013a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -56,6 +56,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { private let ribbonBackgroundNode: ASImageNode private let ribbonTextNode: TextNode + private let creatorButtonNode: HighlightTrackingButtonNode + private let creatorButtonTitleNode: TextNode + private var shimmerEffectNode: ShimmerEffectForegroundNode? private let buttonNode: HighlightTrackingButtonNode private let buttonStarsNode: PremiumStarsNode @@ -152,17 +155,17 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.symbolValueTextNode = TextNode() self.symbolValueTextNode.isUserInteractionEnabled = false self.symbolValueTextNode.displaysAsynchronously = false - - self.buttonNode = HighlightTrackingButtonNode() - self.buttonNode.clipsToBounds = true - self.buttonNode.cornerRadius = 17.0 - + self.placeholderNode = StickerShimmerEffectNode() self.placeholderNode.isUserInteractionEnabled = false self.placeholderNode.alpha = 0.75 self.animationNode = DefaultAnimatedStickerNodeImpl() + self.buttonNode = HighlightTrackingButtonNode() + self.buttonNode.clipsToBounds = true + self.buttonNode.cornerRadius = 17.0 + self.buttonStarsNode = PremiumStarsNode() self.buttonContentNode = ASDisplayNode() @@ -171,6 +174,13 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.buttonTitleNode = TextNode() self.buttonTitleNode.displaysAsynchronously = false + self.creatorButtonNode = HighlightTrackingButtonNode() + self.creatorButtonNode.clipsToBounds = true + self.creatorButtonNode.cornerRadius = 9.0 + + self.creatorButtonTitleNode = TextNode() + self.creatorButtonTitleNode.displaysAsynchronously = false + self.ribbonBackgroundNode = ASImageNode() self.ribbonBackgroundNode.displaysAsynchronously = false @@ -196,9 +206,12 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.addSubnode(self.buttonNode) self.buttonNode.addSubnode(self.buttonStarsNode) self.buttonNode.addSubnode(self.buttonContentNode) - + self.buttonContentNode.addSubnode(self.buttonTitleNode) + self.addSubnode(self.creatorButtonNode) + self.creatorButtonNode.addSubnode(self.creatorButtonTitleNode) + self.addSubnode(self.ribbonBackgroundNode) self.addSubnode(self.ribbonTextNode) @@ -213,8 +226,20 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } } } - self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + + self.creatorButtonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.creatorButtonNode.layer.removeAnimation(forKey: "opacity") + strongSelf.creatorButtonNode.alpha = 0.4 + } else { + strongSelf.creatorButtonNode.alpha = 1.0 + strongSelf.creatorButtonNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + self.creatorButtonNode.addTarget(self, action: #selector(self.creatorButtonPressed), forControlEvents: .touchUpInside) } required public init?(coder aDecoder: NSCoder) { @@ -239,6 +264,31 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.maskView?.addSubview(maskOverlayView) } + @objc private func creatorButtonPressed() { + guard let item = self.item else { + return + } + + var releasedBy: EnginePeer.Id? + for media in item.message.media { + if let action = media as? TelegramMediaAction { + switch action.action { + case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + releasedBy = gift.releasedBy + case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _): + releasedBy = gift.releasedBy + default: + break + } + break + } + } + guard let releasedBy, let peer = item.message.peers[releasedBy] else { + return + } + item.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default) + } + @objc private func buttonPressed() { guard let item = self.item else { return @@ -330,6 +380,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let makeMeasureTextLayout = TextNode.asyncLayout(nil) let makeMoreTextLayout = TextNode.asyncLayout(self.moreTextNode) + let makeCreatorButtonTitleLayout = TextNode.asyncLayout(self.creatorButtonTitleNode) + let makeModelTitleLayout = TextNode.asyncLayout(self.modelTitleTextNode) let makeModelValueLayout = TextNode.asyncLayout(self.modelValueTextNode) let makeBackdropTitleLayout = TextNode.asyncLayout(self.backdropTitleTextNode) @@ -373,6 +425,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { var textSpacing: CGFloat = 0.0 var isStarGift = false + var creatorButtonTitle = "" + var modelTitle: String? var modelValue: String? var backdropTitle: String? @@ -494,6 +548,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, _, channelPeerId, senderPeerId, _): if case let .generic(gift) = gift { + if let releasedBy = gift.releasedBy, let peer = item.message.peers[releasedBy], let addressName = peer.addressName { + creatorButtonTitle = item.presentationData.strings.Notification_StarGift_ReleasedBy("**@\(addressName)**").string + } + isStarGift = true var authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" @@ -763,7 +821,22 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: buttonTitle, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (ribbonTextLayout, ribbonTextApply) = makeRibbonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: ribbonTitle, font: Font.semibold(11.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let creatorButtonAttributedString = parseMarkdownIntoAttributedString(creatorButtonTitle, attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(12.0), textColor: primaryTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(12.0), textColor: primaryTextColor), + link: MarkdownAttributeSet(font: Font.regular(12.0), textColor: primaryTextColor), + linkAttribute: { url in + return ("URL", url) + } + ), textAlignment: .center) + + let (creatorButtonTitleLayout, creatorButtonTitleApply) = makeCreatorButtonTitleLayout(TextNodeLayoutArguments(attributedString: creatorButtonAttributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + if !creatorButtonTitle.isEmpty { + textSpacing += 28.0 + } + giftSize.height = titleLayout.size.height + textSpacing + clippedTextHeight + 164.0 if let _ = modelTitle { @@ -848,6 +921,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.buttonNode.isHidden = buttonTitle.isEmpty strongSelf.buttonTitleNode.isHidden = buttonTitle.isEmpty + + strongSelf.creatorButtonNode.isHidden = creatorButtonTitle.isEmpty + strongSelf.creatorButtonTitleNode.isHidden = creatorButtonTitle.isEmpty if strongSelf.item == nil && !isStoryEntity { strongSelf.animationNode.started = { [weak self] in @@ -888,6 +964,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.labelNode.isHidden = !hasServiceMessage strongSelf.buttonNode.backgroundColor = overlayColor + strongSelf.creatorButtonNode.backgroundColor = overlayColor strongSelf.animationNode.updateLayout(size: iconSize) strongSelf.placeholderNode.frame = animationFrame @@ -906,13 +983,23 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let _ = buttonTitleApply() let _ = ribbonTextApply() let _ = moreApply() - + let _ = creatorButtonTitleApply() + + + + let labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((boundingWidth - labelLayout.size.width) / 2.0), y: 2.0), size: labelLayout.size) strongSelf.labelNode.frame = labelFrame let titleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - titleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 151.0), size: titleLayout.size) strongSelf.titleNode.frame = titleFrame + let creatorButtonSize = CGSize(width: creatorButtonTitleLayout.size.width + 18.0, height: 18.0) + let creatorButtonOriginY = titleFrame.maxY + 4.0 + strongSelf.creatorButtonTitleNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 1.0), size: creatorButtonTitleLayout.size) + + animation.animator.updateFrame(layer: strongSelf.creatorButtonNode.layer, frame: CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - creatorButtonSize.width) / 2.0), y: creatorButtonOriginY), size: creatorButtonSize), completion: nil) + let clippingTextFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0), y: titleFrame.maxY + textSpacing), size: CGSize(width: subtitleLayout.size.width, height: clippedTextHeight)) let subtitleFrame = CGRect(origin: .zero, size: subtitleLayout.size) @@ -1355,8 +1442,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { return ChatMessageBubbleContentTapAction(content: .none) } } - - if self.buttonNode.frame.contains(point) { + + if self.buttonNode.frame.contains(point) || self.creatorButtonNode.frame.contains(point) { return ChatMessageBubbleContentTapAction(content: .ignore) } else if self.textClippingNode.frame.contains(point) && !self.isExpanded && !self.moreTextNode.alpha.isZero { return ChatMessageBubbleContentTapAction(content: .custom({ [weak self] in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift index 781a04f358..b3693be49f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift @@ -372,6 +372,19 @@ private func generatePercentageAnimationImages(presentationData: ChatPresentatio return images } +private class TodoTrackingButtonNode: HighlightableButtonNode { + private var internalHighlighted = false + + public var shouldHighlightAtPoint: (CGPoint) -> Bool = { _ in return true } + + open override func beginTracking(with touch: UITouch, with event: UIEvent?) -> Bool { + if self.shouldHighlightAtPoint(touch.location(in: self.view)) { + return super.beginTracking(with: touch, with: event) + } + return false + } +} + private final class ChatMessageTodoItemNode: ASDisplayNode { private var backgroundWallpaperNode: ChatMessageBubbleBackdrop? private var backgroundNode: ChatMessageBackground? @@ -390,7 +403,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode { fileprivate var titleNode: TextNodeWithEntities? fileprivate var nameNode: TextNode? - private let buttonNode: HighlightTrackingButtonNode + private let buttonNode: TodoTrackingButtonNode let separatorNode: ASDisplayNode var context: AccountContext? @@ -433,7 +446,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode { self.highlightedBackgroundNode.alpha = 0.0 self.highlightedBackgroundNode.isUserInteractionEnabled = false - self.buttonNode = HighlightTrackingButtonNode() + self.buttonNode = TodoTrackingButtonNode() self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true @@ -447,6 +460,15 @@ private final class ChatMessageTodoItemNode: ASDisplayNode { self.addSubnode(self.buttonNode) self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + self.buttonNode.shouldHighlightAtPoint = { [weak self] location in + guard let self else { + return true + } + if case .none = self.tapActionAtPoint(location, gesture: .tap, isEstimating: true).content { + return true + } + return false + } self.buttonNode.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { if highlighted { diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 60d94351d5..c070935f64 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -160,6 +160,9 @@ private final class GiftViewSheetContent: CombinedComponent { } if case let .unique(gift) = arguments.gift { + if let releasedBy = gift.releasedBy { + peerIds.append(releasedBy) + } if case let .peerId(peerId) = gift.owner { peerIds.append(peerId) } @@ -192,6 +195,9 @@ private final class GiftViewSheetContent: CombinedComponent { }) } } else if case let .generic(gift) = arguments.gift { + if let releasedBy = gift.releasedBy { + peerIds.append(releasedBy) + } if arguments.canUpgrade || arguments.upgradeStars != nil { self.sampleDisposable.add((context.engine.payments.starGiftUpgradePreview(giftId: gift.id) |> deliverOnMainQueue).start(next: { [weak self] attributes in @@ -1527,6 +1533,9 @@ private final class GiftViewSheetContent: CombinedComponent { let buttons = Child(ButtonsComponent.self) let animation = Child(GiftCompositionComponent.self) let title = Child(MultilineTextComponent.self) + let subtitle = Child(MultilineTextComponent.self) + + let descriptionButton = Child(RoundedRectangle.self) let description = Child(MultilineTextComponent.self) let transferButton = Child(PlainButtonComponent.self) @@ -1570,6 +1579,7 @@ private final class GiftViewSheetContent: CombinedComponent { let sideInset: CGFloat = 16.0 + environment.safeInsets.left var titleString: String + var subtitleString: String? var animationFile: TelegramMediaFile? let stars: Int64 let convertStars: Int64? @@ -1606,6 +1616,10 @@ private final class GiftViewSheetContent: CombinedComponent { } else if let arguments = subject.arguments { switch arguments.gift { case let .generic(gift): + if let releasedBy = gift.releasedBy, let peer = state.peerMap[releasedBy], let addressName = peer.addressName { + subtitleString = strings.Gift_View_ReleasedBy("[@\(addressName)]()").string + } + animationFile = gift.file stars = gift.price text = arguments.text @@ -2145,9 +2159,15 @@ private final class GiftViewSheetContent: CombinedComponent { } } else { var descriptionText: String + var hasDescriptionButton = false if let uniqueGift { titleString = uniqueGift.title descriptionText = "\(strings.Gift_Unique_Collectible) #\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))" + + if let releasedBy = uniqueGift.releasedBy, let peer = state.peerMap[releasedBy], let addressName = peer.addressName { + descriptionText = strings.Gift_Unique_CollectibleBy("#\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))", "[@\(addressName)]()").string + hasDescriptionButton = true + } } else if soldOut { descriptionText = strings.Gift_View_UnavailableDescription } else if upgraded { @@ -2197,7 +2217,7 @@ private final class GiftViewSheetContent: CombinedComponent { component: MultilineTextComponent( text: .plain(NSAttributedString( string: titleString, - font: uniqueGift != nil ? Font.bold(20.0) : Font.bold(25.0), + font: Font.bold(20.0), textColor: uniqueGift != nil ? .white : theme.actionSheet.primaryTextColor, paragraphAlignment: .center )), @@ -2208,13 +2228,41 @@ private final class GiftViewSheetContent: CombinedComponent { transition: .immediate ) context.add(title - .position(CGPoint(x: context.availableSize.width / 2.0, y: uniqueGift != nil ? 190.0 : 177.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: uniqueGift != nil ? 190.0 : 173.0)) .appear(.default(alpha: true)) .disappear(.default(alpha: true)) ) + var descriptionOffset: CGFloat = 0.0 + if let subtitleString { + let subtitle = subtitle.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: subtitleString, + font: Font.regular(13.0), + textColor: theme.actionSheet.secondaryTextColor, + paragraphAlignment: .center + )), + horizontalAlignment: .center, + maximumNumberOfLines: 1 + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude), + transition: .immediate + ) + context.add(subtitle + .position(CGPoint(x: context.availableSize.width / 2.0, y: uniqueGift != nil ? 210.0 : 196.0)) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) + descriptionOffset += subtitle.size.height + } + if !descriptionText.isEmpty { - let linkColor = theme.actionSheet.controlAccentColor + var linkColor = theme.actionSheet.controlAccentColor + if hasDescriptionButton { + linkColor = UIColor.white + } + if state.cachedSmallStarImage == nil || state.cachedSmallStarImage?.1 !== environment.theme { state.cachedSmallStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Premium/Stars/ButtonStar"), color: .white)!, theme) } @@ -2226,7 +2274,11 @@ private final class GiftViewSheetContent: CombinedComponent { let textColor: UIColor if let _ = uniqueGift { textFont = Font.regular(13.0) - textColor = vibrantColor + if hasDescriptionButton { + textColor = vibrantColor.mixedWith(UIColor.white, alpha: 0.5) + } else { + textColor = vibrantColor + } } else { textFont = soldOut ? Font.medium(15.0) : Font.regular(15.0) textColor = soldOut ? theme.list.itemDestructiveColor : theme.list.itemPrimaryTextColor @@ -2245,6 +2297,7 @@ private final class GiftViewSheetContent: CombinedComponent { if let range = attributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 { attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string)) } + let description = description.update( component: MultilineTextComponent( text: .plain(attributedString), @@ -2269,12 +2322,29 @@ private final class GiftViewSheetContent: CombinedComponent { availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude), transition: .immediate ) + + if hasDescriptionButton { + let descriptionButton = descriptionButton.update( + component: RoundedRectangle(color: UIColor.white.withAlphaComponent(0.15), cornerRadius: 9.5), + environment: {}, + availableSize: CGSize(width: description.size.width + 18.0, height: 19.0), + transition: .immediate + ) + context.add(descriptionButton + .position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + descriptionOffset + description.size.height / 2.0 - UIScreenPixel)) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) + } + context.add(description - .position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + description.size.height / 2.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + descriptionOffset + description.size.height / 2.0)) .appear(.default(alpha: true)) .disappear(.default(alpha: true)) ) + originY += descriptionOffset + if uniqueGift != nil { originY += 16.0 } else { diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift index 1eba83a146..a627e17bb6 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift @@ -460,7 +460,8 @@ final class UserAppearanceScreenComponent: Component { ], availability: StarGift.UniqueGift.Availability(issued: 0, total: 0), giftAddress: nil, - resellStars: nil + resellStars: nil, + releasedBy: nil ) signal = component.context.engine.accountData.setStarGiftStatus(starGift: gift, expirationDate: emojiStatus.expirationDate) } else { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index f0f4ffbfcb..654c5c5828 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4991,6 +4991,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } self.dismissAllTooltips() + if let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId), let _ = message.forwardInfo { + let controller = UndoOverlayController( + presentationData: self.presentationData, + content: .universalImage(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: .white)!, size: nil, title: nil, text: self.presentationData.strings.Chat_Todo_CompletionLimitedForward, customUndoText: nil, timeout: nil), + action: { _ in + return false + } + ) + self.present(controller, in: .current) + return + } + guard self.presentationInterfaceState.subject != .scheduledMessages else { self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.ScheduledMessages_TodoUnavailable, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 26c463dc18..af650fba24 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -2116,6 +2116,20 @@ extension ChatControllerImpl { return } + guard self.context.isPremium else { + let context = self.context + var replaceImpl: ((ViewController) -> Void)? + let demoController = context.sharedContext.makePremiumDemoController(context: context, subject: .todo, forceDark: false, action: { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .todo, forceDark: false, dismissed: nil) + replaceImpl?(controller) + }, dismissed: nil) + replaceImpl = { [weak demoController] c in + demoController?.replace(with: c) + } + self.push(demoController) + return + } + let canEdit = canEditMessage(context: self.context, limitsConfiguration: self.context.currentLimitsConfiguration.with { EngineConfiguration.Limits($0) }, message: message) let controller = ComposeTodoScreen( From 82768efe11cb7283058b5ec76c1b1e2d0d123e29 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Wed, 2 Jul 2025 19:25:23 +0200 Subject: [PATCH 10/11] Fix --- .../Sources/ChatMessageSuggestedPostInfoNode.swift | 4 +--- .../Sources/PeerSelectionController.swift | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift index d42fdd4fee..4d7b205375 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift @@ -137,9 +137,7 @@ public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode { } } else { var channelName = "" - if item.message.author is TelegramChannel { - channelName = item.message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? " " - } + channelName = item.message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? " " if changedText && changedMedia && changedPrice && changedTime { titleText = item.presentationData.strings.Chat_PostSuggestion_ChannelChangePTC(channelName).string } else if changedText && changedPrice && changedTime { diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift index 60ffa8920e..db095daadd 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift @@ -289,7 +289,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon TelegramEngine.EngineData.Item.Peer.Peer(id: linkedMonoforumId) ) } else { - mainPeer = .single(nil) + mainPeer = .single(EnginePeer.channel(peer)) } let _ = (mainPeer |> deliverOnMainQueue).startStandalone(next: { [weak self] mainPeer in From 72c466ca13c7a9ef40e5ea171d532af165e9f869 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 2 Jul 2025 22:03:34 +0200 Subject: [PATCH 11/11] Various improvements --- .../ChatMessageGiftBubbleContentNode.swift | 61 +++++++++++++---- .../Sources/ChatGiftPreviewItem.swift | 2 +- .../Sources/GiftSetupScreen.swift | 46 +++++++++---- .../Sources/GiftViewScreen.swift | 67 ++++++++++++++----- .../Sources/ChatTextInputAttributes.swift | 2 + 5 files changed, 133 insertions(+), 45 deletions(-) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index 296bab013a..4e25af6b4e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -57,6 +57,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { private let ribbonTextNode: TextNode private let creatorButtonNode: HighlightTrackingButtonNode + private var creatorButtonBackgroundNode: NavigationBackgroundNode? private let creatorButtonTitleNode: TextNode private var shimmerEffectNode: ShimmerEffectForegroundNode? @@ -644,6 +645,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { if case let .unique(uniqueGift) = gift { isStarGift = true + if let releasedBy = gift.releasedBy, let peer = item.message.peers[releasedBy], let addressName = peer.addressName { + creatorButtonTitle = item.presentationData.strings.Notification_StarGift_ReleasedBy("**@\(addressName)**").string + } + let isSelfGift = item.message.id.peerId == item.context.account.peerId let authorName: String if isUpgrade { @@ -831,9 +836,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } ), textAlignment: .center) - let (creatorButtonTitleLayout, creatorButtonTitleApply) = makeCreatorButtonTitleLayout(TextNodeLayoutArguments(attributedString: creatorButtonAttributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + let (creatorButtonTitleLayout, creatorButtonTitleApply) = makeCreatorButtonTitleLayout(TextNodeLayoutArguments(attributedString: creatorButtonAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) - if !creatorButtonTitle.isEmpty { + if modelTitle == nil && !creatorButtonTitle.isEmpty { textSpacing += 28.0 } @@ -841,6 +846,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { if let _ = modelTitle { giftSize.height += 70.0 + + if !creatorButtonTitle.isEmpty { + giftSize.height += 28.0 + } } if !buttonTitle.isEmpty { @@ -920,11 +929,13 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.animationNode.isHidden = isStoryEntity strongSelf.buttonNode.isHidden = buttonTitle.isEmpty + strongSelf.buttonNode.isUserInteractionEnabled = !item.presentationData.isPreview strongSelf.buttonTitleNode.isHidden = buttonTitle.isEmpty strongSelf.creatorButtonNode.isHidden = creatorButtonTitle.isEmpty + strongSelf.creatorButtonNode.isUserInteractionEnabled = !item.presentationData.isPreview strongSelf.creatorButtonTitleNode.isHidden = creatorButtonTitle.isEmpty - + if strongSelf.item == nil && !isStoryEntity { strongSelf.animationNode.started = { [weak self] in if let strongSelf = self { @@ -964,7 +975,6 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.labelNode.isHidden = !hasServiceMessage strongSelf.buttonNode.backgroundColor = overlayColor - strongSelf.creatorButtonNode.backgroundColor = overlayColor strongSelf.animationNode.updateLayout(size: iconSize) strongSelf.placeholderNode.frame = animationFrame @@ -985,22 +995,45 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let _ = moreApply() let _ = creatorButtonTitleApply() - - - let labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((boundingWidth - labelLayout.size.width) / 2.0), y: 2.0), size: labelLayout.size) strongSelf.labelNode.frame = labelFrame let titleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - titleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 151.0), size: titleLayout.size) strongSelf.titleNode.frame = titleFrame + + let clippingTextFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0), y: titleFrame.maxY + textSpacing), size: CGSize(width: subtitleLayout.size.width, height: clippedTextHeight)) + + var attributesOffsetY: CGFloat = 0.0 let creatorButtonSize = CGSize(width: creatorButtonTitleLayout.size.width + 18.0, height: 18.0) - let creatorButtonOriginY = titleFrame.maxY + 4.0 + let creatorButtonOriginY = modelTitle == nil ? titleFrame.maxY + 4.0 : clippingTextFrame.maxY + 5.0 + let creatorButtonFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - creatorButtonSize.width) / 2.0), y: creatorButtonOriginY), size: creatorButtonSize) + if !creatorButtonTitle.isEmpty { + attributesOffsetY += creatorButtonSize.height + 10.0 + } + if !strongSelf.creatorButtonNode.isHidden, modelTitle != nil { + strongSelf.creatorButtonNode.backgroundColor = .clear + + let creatorButtonBackgroundNode: NavigationBackgroundNode + if let current = strongSelf.creatorButtonBackgroundNode { + creatorButtonBackgroundNode = current + } else { + creatorButtonBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x000000, alpha: 0.12), enableBlur: true, enableSaturation: false) + strongSelf.creatorButtonNode.insertSubnode(creatorButtonBackgroundNode, at: 0) + strongSelf.creatorButtonBackgroundNode = creatorButtonBackgroundNode + } + creatorButtonBackgroundNode.update(size: creatorButtonFrame.size, cornerRadius: 9.0, transition: .immediate) + } else { + strongSelf.creatorButtonNode.backgroundColor = overlayColor + + if let creatorButtonBackgroundNode = strongSelf.creatorButtonBackgroundNode { + strongSelf.creatorButtonBackgroundNode = nil + creatorButtonBackgroundNode.removeFromSupernode() + } + } strongSelf.creatorButtonTitleNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 1.0), size: creatorButtonTitleLayout.size) - animation.animator.updateFrame(layer: strongSelf.creatorButtonNode.layer, frame: CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - creatorButtonSize.width) / 2.0), y: creatorButtonOriginY), size: creatorButtonSize), completion: nil) - - let clippingTextFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0), y: titleFrame.maxY + textSpacing), size: CGSize(width: subtitleLayout.size.width, height: clippedTextHeight)) + animation.animator.updateFrame(layer: strongSelf.creatorButtonNode.layer, frame: creatorButtonFrame, completion: nil) let subtitleFrame = CGRect(origin: .zero, size: subtitleLayout.size) strongSelf.subtitleNode.textNode.frame = subtitleFrame @@ -1084,7 +1117,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } let _ = titleApply() titleTextNode.frame = CGRect( - origin: CGPoint(x: titleMaxX - titleLayout.size.width, y: clippingTextFrame.maxY + yOffset), + origin: CGPoint(x: titleMaxX - titleLayout.size.width, y: clippingTextFrame.maxY + attributesOffsetY + yOffset), size: titleLayout.size ) } @@ -1094,7 +1127,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } let _ = valueApply() valueTextNode.frame = CGRect( - origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + yOffset), + origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + attributesOffsetY + yOffset), size: valueLayout.size ) } @@ -1125,7 +1158,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { var buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0) var buttonOriginY = clippingTextFrame.maxY + 10.0 if modelTitleLayoutAndApply != nil { - buttonOriginY = clippingTextFrame.maxY + 80.0 + buttonOriginY = clippingTextFrame.maxY + attributesOffsetY + 80.0 } strongSelf.buttonTitleNode.frame = CGRect(origin: CGPoint(x: 19.0, y: 8.0), size: buttonTitleLayout.size) diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift index ed635f4557..c91d2fb778 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift @@ -294,7 +294,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode { } var contentSize = CGSize(width: params.width, height: 4.0 + 4.0) - contentSize.height = 346.0 + contentSize.height = 370.0 insets = itemListNeighborsGroupedInsets(neighbors, params) if params.width <= 320.0 { insets.top = 0.0 diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index de249ea4da..eec3b4716c 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -559,25 +559,42 @@ final class GiftSetupScreenComponent: Component { self.hideName = true } + var releasedBy: EnginePeer.Id? if case let .starGift(gift, true) = component.subject, gift.upgradeStars != nil { self.includeUpgrade = true } + if case let .starGift(gift, _) = component.subject { + releasedBy = gift.releasedBy + } - let _ = (component.context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId), - TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId), - TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: component.peerId) - ) - |> deliverOnMainQueue).start(next: { [weak self] peer, accountPeer, sendPaidMessageStars in + var peerIds: [EnginePeer.Id] = [ + component.context.account.peerId, + component.peerId + ] + if let releasedBy { + peerIds.append(releasedBy) + } + + let _ = combineLatest(queue: Queue.mainQueue(), + component.context.engine.data.get(EngineDataMap( + peerIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.Peer in + return TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + } + )), + component.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: component.peerId) + ) + ).start(next: { [weak self] peers, sendPaidMessageStars in guard let self else { return } - if let peer { - self.peerMap[peer.id] = peer - } - if let accountPeer { - self.peerMap[accountPeer.id] = accountPeer + var peersMap: [EnginePeer.Id: EnginePeer] = [:] + for (peerId, maybePeer) in peers { + if let peer = maybePeer { + peersMap[peerId] = peer + } } + self.peerMap = peersMap self.sendPaidMessageStars = sendPaidMessageStars self.state?.updated() @@ -847,7 +864,7 @@ final class GiftSetupScreenComponent: Component { let giftConfiguration = GiftConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 }) var introSectionItems: [AnyComponentWithIdentity] = [] - introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(Rectangle(color: .clear, height: 346.0, tag: self.introPlaceholderTag)))) + introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(Rectangle(color: .clear, height: 370.0, tag: self.introPlaceholderTag)))) if self.sendPaidMessageStars == nil { introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(ListMultilineTextFieldItemComponent( @@ -965,6 +982,7 @@ final class GiftSetupScreenComponent: Component { if let accountPeer = self.peerMap[component.context.account.peerId] { var upgradeStars: Int64? let subject: ChatGiftPreviewItem.Subject + var releasedBy: EnginePeer.Id? switch component.subject { case let .premium(product): if self.payWithStars, let starsPrice = product.starsPrice { @@ -976,12 +994,16 @@ final class GiftSetupScreenComponent: Component { case let .starGift(gift, _): subject = .starGift(gift: gift) upgradeStars = gift.upgradeStars + releasedBy = gift.releasedBy } var peers: [EnginePeer] = [accountPeer] if let peer = self.peerMap[component.peerId] { peers.append(peer) } + if let releasedBy, let peer = self.peerMap[releasedBy] { + peers.append(peer) + } let introContentSize = self.introContent.update( transition: transition, diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index c070935f64..b0875d295a 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -1535,7 +1535,7 @@ private final class GiftViewSheetContent: CombinedComponent { let title = Child(MultilineTextComponent.self) let subtitle = Child(MultilineTextComponent.self) - let descriptionButton = Child(RoundedRectangle.self) + let descriptionButton = Child(PlainButtonComponent.self) let description = Child(MultilineTextComponent.self) let transferButton = Child(PlainButtonComponent.self) @@ -1602,6 +1602,7 @@ private final class GiftViewSheetContent: CombinedComponent { var isSelfGift = false var isChannelGift = false var isMyUniqueGift = false + var releasedByPeer: EnginePeer? if case let .soldOutGift(gift) = subject { animationFile = gift.file @@ -1618,6 +1619,7 @@ private final class GiftViewSheetContent: CombinedComponent { case let .generic(gift): if let releasedBy = gift.releasedBy, let peer = state.peerMap[releasedBy], let addressName = peer.addressName { subtitleString = strings.Gift_View_ReleasedBy("[@\(addressName)]()").string + releasedByPeer = peer } animationFile = gift.file @@ -2167,6 +2169,7 @@ private final class GiftViewSheetContent: CombinedComponent { if let releasedBy = uniqueGift.releasedBy, let peer = state.peerMap[releasedBy], let addressName = peer.addressName { descriptionText = strings.Gift_Unique_CollectibleBy("#\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))", "[@\(addressName)]()").string hasDescriptionButton = true + releasedByPeer = peer } } else if soldOut { descriptionText = strings.Gift_View_UnavailableDescription @@ -2235,16 +2238,34 @@ private final class GiftViewSheetContent: CombinedComponent { var descriptionOffset: CGFloat = 0.0 if let subtitleString { + let textColor = theme.actionSheet.secondaryTextColor + let textFont = Font.regular(13.0) + let subtitleAttributedString = parseMarkdownIntoAttributedString( + subtitleString, + attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.controlAccentColor), linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + }), + textAlignment: .center + ) + let subtitle = subtitle.update( component: MultilineTextComponent( - text: .plain(NSAttributedString( - string: subtitleString, - font: Font.regular(13.0), - textColor: theme.actionSheet.secondaryTextColor, - paragraphAlignment: .center - )), + text: .plain(subtitleAttributedString), horizontalAlignment: .center, - maximumNumberOfLines: 1 + maximumNumberOfLines: 1, + highlightColor: theme.actionSheet.controlAccentColor.withAlphaComponent(0.1), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { [weak state] attributes, _ in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String, let peer = releasedByPeer { + state?.openPeer(peer) + } + } ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude), transition: .immediate @@ -2275,7 +2296,7 @@ private final class GiftViewSheetContent: CombinedComponent { if let _ = uniqueGift { textFont = Font.regular(13.0) if hasDescriptionButton { - textColor = vibrantColor.mixedWith(UIColor.white, alpha: 0.5) + textColor = vibrantColor.mixedWith(UIColor.white, alpha: 0.4) } else { textColor = vibrantColor } @@ -2307,14 +2328,14 @@ private final class GiftViewSheetContent: CombinedComponent { highlightColor: linkColor.withAlphaComponent(0.1), highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0), highlightAction: { attributes in - if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + if !hasDescriptionButton, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) } else { return nil } }, tapAction: { [weak state] attributes, _ in - if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { + if !hasDescriptionButton, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { state?.openStarsIntro() } } @@ -2322,10 +2343,26 @@ private final class GiftViewSheetContent: CombinedComponent { availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude), transition: .immediate ) + context.add(description + .position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + descriptionOffset + description.size.height / 2.0)) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) if hasDescriptionButton { let descriptionButton = descriptionButton.update( - component: RoundedRectangle(color: UIColor.white.withAlphaComponent(0.15), cornerRadius: 9.5), + component: PlainButtonComponent( + content: AnyComponent( + RoundedRectangle(color: UIColor.white.withAlphaComponent(0.15), cornerRadius: 9.5) + ), + effectAlignment: .center, + action: { [weak state] in + if let releasedByPeer { + state?.openPeer(releasedByPeer) + } + }, + animateScale: false + ), environment: {}, availableSize: CGSize(width: description.size.width + 18.0, height: 19.0), transition: .immediate @@ -2337,12 +2374,6 @@ private final class GiftViewSheetContent: CombinedComponent { ) } - context.add(description - .position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + descriptionOffset + description.size.height / 2.0)) - .appear(.default(alpha: true)) - .disappear(.default(alpha: true)) - ) - originY += descriptionOffset if uniqueGift != nil { diff --git a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift index 3777fa27a6..aceef6e4d8 100644 --- a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift +++ b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift @@ -588,6 +588,8 @@ private let textUrlEdgeCharacters: CharacterSet = { var set: CharacterSet = .alphanumerics set.formUnion(.symbols) set.formUnion(.punctuationCharacters) + set.remove("(") + set.remove(")") return set }()