From c696a0aaed004e16b7b64c63d3beeda08a604da8 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 9 Feb 2025 00:46:02 +0400 Subject: [PATCH 01/14] Various fixes --- .../Telegram-iOS/en.lproj/Localizable.strings | 1 + .../AccountContext/Sources/AccountContext.swift | 1 + .../Sources/PremiumOptionComponent.swift | 3 +-- .../ChatMessageGiftBubbleContentNode.swift | 17 ++++++++++++----- .../PeerInfoScreen/Sources/PeerInfoData.swift | 4 ++-- .../Sources/PeerInfoPaneContainerNode.swift | 9 ++++++--- .../PeerInfoScreen/Sources/PeerInfoScreen.swift | 7 ++----- .../Sources/UserApperanceScreen.swift | 2 +- .../TelegramUI/Sources/OpenResolvedUrl.swift | 12 ++++++++++++ submodules/TelegramUI/Sources/OpenUrl.swift | 2 ++ .../Sources/FullscreenControlsComponent.swift | 8 ++++++++ 11 files changed, 48 insertions(+), 18 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 48060e73c8..fce72fef5b 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13568,6 +13568,7 @@ Sorry for the inconvenience."; "Notification.StarGift.Unpack" = "Unpack"; +"Notification.StarGift.Collectible" = "Collectible"; "Notification.StarGift.Model" = "Model"; "Notification.StarGift.Backdrop" = "Backdrop"; "Notification.StarGift.Symbol" = "Symbol"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index dff4dcd6f8..95514ec2bb 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -190,6 +190,7 @@ public enum ResolvedUrlSettingsSection { case autoremoveMessages case twoStepAuth case enableLog + case phonePrivacy } public struct ResolvedBotChoosePeerTypes: OptionSet { diff --git a/submodules/PremiumUI/Sources/PremiumOptionComponent.swift b/submodules/PremiumUI/Sources/PremiumOptionComponent.swift index df785dff42..2bc42b0c5c 100644 --- a/submodules/PremiumUI/Sources/PremiumOptionComponent.swift +++ b/submodules/PremiumUI/Sources/PremiumOptionComponent.swift @@ -153,8 +153,7 @@ final class PremiumOptionComponent: CombinedComponent { transition: context.transition ) - let discountPosition = CGPoint(x: insets.left + title.size.width + 6.0 + discountSize.width / 2.0, y: insets.top + title.size.height / 2.0) - + let discountPosition = CGPoint(x: insets.left + title.size.width + 6.0 + discountSize.width / 2.0, y: insets.top + title.size.height / 2.0 - 2.0) context.add(discountBackground .position(discountPosition) ) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index 3de1f1658e..7fa100d6f0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -426,7 +426,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { peerName = EnginePeer(channel).compactDisplayTitle } title = item.presentationData.strings.Notification_StarsGiveaway_Title - text = item.presentationData.strings.Notification_StarsGiveaway_Subtitle(peerName, item.presentationData.strings.Notification_StarsGiveaway_Subtitle_Stars(Int32(count))).string + let starsString = item.presentationData.strings.Notification_StarsGiveaway_Subtitle_Stars(Int32(count)).replacingOccurrences(of: " ", with: "\u{00A0}") + text = item.presentationData.strings.Notification_StarsGiveaway_Subtitle(peerName, starsString).string case let .giftCode(_, fromGiveaway, unclaimed, channelId, monthsValue, _, _, _, _, giftText, giftEntities): if channelId == nil { months = monthsValue @@ -523,7 +524,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } if peerName.isEmpty { if let convertStars, convertStars > 0 { - text = item.presentationData.strings.Notification_StarGift_Subtitle(item.presentationData.strings.Notification_StarGift_Subtitle_Stars(Int32(convertStars))).string + let starsString = item.presentationData.strings.Notification_StarGift_Subtitle_Stars(Int32(convertStars)).replacingOccurrences(of: " ", with: "\u{00A0}") + text = item.presentationData.strings.Notification_StarGift_Subtitle(starsString).string } else { text = item.presentationData.strings.Notification_StarGift_Bot_Subtitle } @@ -531,7 +533,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { if upgradeStars != nil { text = item.presentationData.strings.Notification_StarGift_Subtitle_Upgrade_Other(peerName).string } else if let convertStars, convertStars > 0 { - let formattedString = item.presentationData.strings.Notification_StarGift_Subtitle_Other(peerName, item.presentationData.strings.Notification_StarGift_Subtitle_Other_Stars(Int32(convertStars))) + let starsString = item.presentationData.strings.Notification_StarGift_Subtitle_Other_Stars(Int32(convertStars)).replacingOccurrences(of: " ", with: "\u{00A0}") + let formattedString = item.presentationData.strings.Notification_StarGift_Subtitle_Other(peerName, starsString) text = formattedString.string if let starsRange = formattedString.ranges.last { entities.append(MessageTextEntity(range: starsRange.range.lowerBound ..< starsRange.range.upperBound, type: .Bold)) @@ -560,6 +563,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded, _, _, _): if case let .unique(uniqueGift) = gift { isStarGift = true + + let isSelfGift = item.message.id.peerId == item.context.account.peerId let authorName: String if isUpgrade { if item.message.author?.id == item.context.account.peerId { @@ -570,12 +575,14 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } else { authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" } - if item.message.id.peerId.isTelegramNotifications { + if isSelfGift { + title = item.presentationData.strings.Notification_StarGift_Self_Title + } else if item.message.id.peerId.isTelegramNotifications { title = item.presentationData.strings.Notification_StarGift_TitleShort } else { title = isStoryEntity ? uniqueGift.title : item.presentationData.strings.Notification_StarGift_Title(authorName).string } - text = isStoryEntity ? "**Collectible #\(uniqueGift.number)**" : "**\(uniqueGift.title) #\(uniqueGift.number)**" + text = isStoryEntity ? "**\(item.presentationData.strings.Notification_StarGift_Collectible) #\(uniqueGift.number)**" : "**\(uniqueGift.title) #\(uniqueGift.number)**" ribbonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_Gift buttonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_View modelTitle = item.presentationData.strings.Notification_StarGift_Model diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index c16d3c4379..3892bbae0b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -952,7 +952,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, } } -func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, existingRequestsContext: PeerInvitationImportersContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, privacySettings: Signal) -> Signal { +func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, existingRequestsContext: PeerInvitationImportersContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, privacySettings: Signal, forceHasGifts: Bool) -> Signal { return peerInfoScreenInputData(context: context, peerId: peerId, isSettings: isSettings) |> mapToSignal { inputData -> Signal in let wasUpgradedGroup = Atomic(value: nil) @@ -1606,7 +1606,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen } if availablePanes != nil, let cachedData = peerView.cachedData as? CachedChannelData { - if (cachedData.starGiftsCount ?? 0) > 0 || (profileGiftsState.count ?? 0) > 0 { + if (cachedData.starGiftsCount ?? 0) > 0 || (profileGiftsState.count ?? 0) > 0 || forceHasGifts { availablePanes?.insert(.gifts, at: hasStories ? 1 : 0) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift index 62a60640cd..2ccef75a18 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift @@ -1273,9 +1273,12 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat self.isReady.set(.single(true)) } } - if let previousCurrentPaneKey = previousCurrentPaneKey, self.currentPaneKey != previousCurrentPaneKey { - self.currentPaneUpdated?(self.expandOnSwitch) - self.expandOnSwitch = false + if let previousCurrentPaneKey, self.currentPaneKey != previousCurrentPaneKey || self.expandOnSwitch { + if self.currentPaneKey == nil && previousCurrentPaneKey == .gifts { + } else { + self.currentPaneUpdated?(self.expandOnSwitch) + self.expandOnSwitch = false + } } if updateCurrentPaneStatus { self.currentPaneStatusPromise.set(self.currentPane?.node.status ?? .single(nil)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 9d643cc5ea..77131b15b5 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -4603,7 +4603,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.privacySettings.set(.single(nil)) } - screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: hintGroupInCommon, existingRequestsContext: requestsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, privacySettings: self.privacySettings.get()) + screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: hintGroupInCommon, existingRequestsContext: requestsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, privacySettings: self.privacySettings.get(), forceHasGifts: initialPaneKey == .gifts) var previousTimestamp: Double? self.headerNode.displayPremiumIntro = { [weak self] sourceView, peerStatus, emojiStatusFileAndPack, white in @@ -6497,16 +6497,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } let boostTitle: String - var isNew = false switch channel.info { case .group: boostTitle = presentationData.strings.PeerInfo_Group_Boost - isNew = true case .broadcast: boostTitle = presentationData.strings.PeerInfo_Channel_Boost } - - items.append(.action(ContextMenuActionItem(text: boostTitle, badge: isNew ? ContextMenuActionBadge(value: presentationData.strings.Settings_New, color: .accent, style: .label) : nil, icon: { theme in + items.append(.action(ContextMenuActionItem(text: boostTitle, badge: nil, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Boost"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.dismissWithoutContent) diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift index 7dbe2cd3b7..0bbf2fa9fe 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift @@ -402,7 +402,7 @@ final class UserAppearanceScreenComponent: Component { if case .info = action { var replaceImpl: ((ViewController) -> Void)? let controller = component.context.sharedContext.makePremiumDemoController(context: component.context, subject: .colors, forceDark: false, action: { - let controller = component.context.sharedContext.makePremiumIntroController(context: component.context, source: .settings, forceDark: false, dismissed: nil) + let controller = component.context.sharedContext.makePremiumIntroController(context: component.context, source: .nameColor, forceDark: false, dismissed: nil) replaceImpl?(controller) }, dismissed: nil) replaceImpl = { [weak controller] c in diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 0bf8672f70..e521548cbf 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -703,6 +703,18 @@ func openResolvedUrlImpl( navigationController.setViewControllers(controllers, animated: true) } } + case .phonePrivacy: + let privacySignal = context.engine.privacy.requestAccountPrivacySettings() + let _ = (privacySignal + |> deliverOnMainQueue).start(next: { info in + let current: SelectivePrivacySettings = info.phoneNumber + if let navigationController = navigationController { + let controller = selectivePrivacySettingsController(context: context, kind: .phoneNumber, current: current, phoneDiscoveryEnabled: info.phoneDiscoveryEnabled, updated: { _, _, _, _ in + }) + controller.navigationPresentation = .modal + navigationController.pushViewController(controller) + } + }) } case let .premiumOffer(reference): dismissInput() diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index 71230c9e5a..e54e402009 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -1014,6 +1014,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur section = .twoStepAuth case "enable_log": section = .enableLog + case "phone_privacy": + section = .phonePrivacy default: break } diff --git a/submodules/WebUI/Sources/FullscreenControlsComponent.swift b/submodules/WebUI/Sources/FullscreenControlsComponent.swift index 69dbd1716a..f5ae78231f 100644 --- a/submodules/WebUI/Sources/FullscreenControlsComponent.swift +++ b/submodules/WebUI/Sources/FullscreenControlsComponent.swift @@ -353,6 +353,14 @@ final class FullscreenControlsComponent: Component { return CGSize(width: availableSize.width, height: leftBackgroundSize.height) } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + if result === self { + return nil + } + return result + } } func makeView() -> View { From 18e8ee3357f6c70a79cfb64087a91630191291a9 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 9 Feb 2025 01:17:59 +0400 Subject: [PATCH 02/14] Bump versions --- versions.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/versions.json b/versions.json index 12639de6f5..e84553e625 100644 --- a/versions.json +++ b/versions.json @@ -1,6 +1,6 @@ { "app": "11.7.1", - "xcode": "16.0", + "xcode": "16.2", "bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff", - "macos": "15.0" + "macos": "15" } From 089fb1b1469b41b8593efc258490f8b37046c1d1 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 9 Feb 2025 01:44:47 +0400 Subject: [PATCH 03/14] Bump --- build_number_offset | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_number_offset b/build_number_offset index d0f0d290cd..d1a7e335c7 100644 --- a/build_number_offset +++ b/build_number_offset @@ -1 +1 @@ -2510 +2515 From fd38d2ea9b0e2c6edfc8607e4f019a0091c374be Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 9 Feb 2025 19:38:27 +0400 Subject: [PATCH 04/14] Fix --- .../PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift index 2ccef75a18..87cf88e648 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift @@ -1273,7 +1273,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat self.isReady.set(.single(true)) } } - if let previousCurrentPaneKey, self.currentPaneKey != previousCurrentPaneKey || self.expandOnSwitch { + if let previousCurrentPaneKey, self.currentPaneKey != previousCurrentPaneKey { if self.currentPaneKey == nil && previousCurrentPaneKey == .gifts { } else { self.currentPaneUpdated?(self.expandOnSwitch) From c0184d41145d4b79a22e4adf7682a96effe27702 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 14 Feb 2025 21:44:19 +0400 Subject: [PATCH 05/14] Various fixes --- .../Telegram-iOS/en.lproj/Localizable.strings | 7 + .../Sources/TGMediaEditingContext.m | 6 +- .../StickerPaneSearchContentNode.swift | 20 ++- .../Sources/GiftSetupScreen.swift | 157 +++++++++--------- .../Sources/MediaEditorScreen.swift | 34 +++- .../Sources/PeerInfoGiftsPaneNode.swift | 6 +- .../Sources/StarsTransferScreen.swift | 87 +++++----- 7 files changed, 177 insertions(+), 140 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index fce72fef5b..8b25b9de20 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13801,3 +13801,10 @@ Sorry for the inconvenience."; "Notification.StarsGift.TransferToChannel" = "%@ transferred a unique collectible to %@"; "Notification.StarsGift.TransferToChannelYou" = "You transferred a unique collectible to %@"; + +"Gift.Convert.Success.ChannelText" = "**%1$@** were sent to channel's balance."; +"Gift.Convert.Success.ChannelText.Stars_1" = "%@ Star"; +"Gift.Convert.Success.ChannelText.Stars_any" = "%@ Stars"; + +"Stars.Transfer.Terms" = "By purchasing you agree to the [Terms of Service]()."; +"Stars.Transfer.Terms_URL" = "https://telegram.org/tos/stars"; diff --git a/submodules/LegacyComponents/Sources/TGMediaEditingContext.m b/submodules/LegacyComponents/Sources/TGMediaEditingContext.m index e1f9cfab72..35e9581560 100644 --- a/submodules/LegacyComponents/Sources/TGMediaEditingContext.m +++ b/submodules/LegacyComponents/Sources/TGMediaEditingContext.m @@ -962,7 +962,11 @@ [_coverImageCache setImage:image forKey:itemId attributes:NULL]; _coverImagePipe.sink([TGMediaImageUpdate imageUpdateWithItem:item representation:image]); - [_coverPositions setObject:position forKey:itemId]; + if (position != nil) { + [_coverPositions setObject:position forKey:itemId]; + } else { + [_coverPositions removeObjectForKey:itemId]; + } } - (void)setFullSizeImage:(UIImage *)image forItem:(id)item diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/StickerPaneSearchContentNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/StickerPaneSearchContentNode.swift index 23230d48ba..ef37c0f7d8 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/StickerPaneSearchContentNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/StickerPaneSearchContentNode.swift @@ -355,13 +355,13 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { let signal: Signal<([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?)?, NoError> if !text.isEmpty { let context = self.context - let stickers: Signal<[(String?, FoundStickerItem)], NoError> = Signal { subscriber in - var signals: Signal<[Signal<(String?, [FoundStickerItem]), NoError>], NoError> = .single([]) + let stickers: Signal<([(String?, FoundStickerItem)], Bool), NoError> = Signal { subscriber in + var signals: Signal<[Signal<(String?, [FoundStickerItem], Bool), NoError>], NoError> = .single([]) let query = text.trimmingCharacters(in: .whitespacesAndNewlines) if query.isSingleEmoji { signals = .single([context.engine.stickers.searchStickers(query: nil, emoticon: [text.basicEmoji.0]) - |> map { (nil, $0.items) }]) + |> map { (nil, $0.items, $0.isFinalResult) }]) } else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" { var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3) if !languageCode.lowercased().hasPrefix("en") { @@ -377,10 +377,10 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { } } signals = signal - |> map { keywords -> [Signal<(String?, [FoundStickerItem]), NoError>] in + |> map { keywords -> [Signal<(String?, [FoundStickerItem], Bool), NoError>] in let emoticon = keywords.flatMap { $0.emoticons }.map { $0.basicEmoji.0 } return [context.engine.stickers.searchStickers(query: query, emoticon: emoticon, inputLanguageCode: languageCode) - |> map { (nil, $0.items) }] + |> map { (nil, $0.items, $0.isFinalResult) }] } } @@ -389,12 +389,16 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { return combineLatest(signals) }).start(next: { results in var result: [(String?, FoundStickerItem)] = [] - for (emoji, stickers) in results { + var allAreFinal = true + for (emoji, stickers, isFinal) in results { for sticker in stickers { result.append((emoji, sticker)) } + if !isFinal { + allAreFinal = false + } } - subscriber.putNext(result) + subscriber.putNext((result, allAreFinal)) }, completed: { // subscriber.putCompletion() }) @@ -456,7 +460,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { signal = combineLatest(stickers, packs) |> map { stickers, packs -> ([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?)? in - return (stickers, packs.0, packs.1, packs.2) + return (stickers.0, packs.0, packs.1 && stickers.1, packs.2) } self.updateActivity?(true) } else { diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index 92732d9e47..41e7f07cd2 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -344,98 +344,93 @@ final class GiftSetupScreenComponent: Component { let entities = generateChatInputTextEntities(self.textInputState.text) let source: BotPaymentInvoiceSource = .starGift(hideName: self.hideName, includeUpgrade: self.includeUpgrade, peerId: peerId, giftId: starGift.id, text: self.textInputState.text.string, entities: entities) - let inputData = BotCheckoutController.InputData.fetch(context: component.context, source: source) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } let completion = component.completion - let _ = (inputData - |> deliverOnMainQueue).startStandalone(next: { [weak self] inputData in - guard let inputData else { + let signal = BotCheckoutController.InputData.fetch(context: component.context, source: source) + |> `catch` { _ -> Signal in + return .fail(.generic) + } + |> mapToSignal { inputData -> Signal in + return component.context.engine.payments.sendStarsPaymentForm(formId: inputData.form.id, source: source) + } + |> deliverOnMainQueue + + let _ = signal.start(next: { [weak self] result in + guard let self, let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController else { return } - let _ = (component.context.engine.payments.sendStarsPaymentForm(formId: inputData.form.id, source: source) - |> deliverOnMainQueue).start(next: { [weak self] result in - if let self, peerId.namespace == Namespaces.Peer.CloudChannel, let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController { - var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) } - navigationController.setViewControllers(controllers, animated: true) - - let tooltipController = UndoOverlayController( - presentationData: presentationData, - content: .sticker( - context: context, - file: starGift.file, - loop: true, - title: nil, - text: presentationData.strings.Gift_Send_Success(self.peerMap[peerId]?.compactDisplayTitle ?? "", presentationData.strings.Gift_Send_Success_Stars(Int32(starGift.price))).string, - undoText: nil, - customAction: nil - ), - action: { _ in return true } - ) - (navigationController.viewControllers.last as? ViewController)?.present(tooltipController, in: .current) - - navigationController.view.addSubview(ConfettiView(frame: navigationController.view.bounds)) - } + + if peerId.namespace == Namespaces.Peer.CloudChannel { + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) } + navigationController.setViewControllers(controllers, animated: true) - if let completion { - completion() - - if let self, let controller = self.environment?.controller() { - controller.dismiss() - } - } else { - guard let self, let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController else { - return - } - - if peerId.namespace != Namespaces.Peer.CloudChannel { - var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) } - var foundController = false - for controller in controllers.reversed() { - if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation { - chatController.hintPlayNextOutgoingGift() - foundController = true - break - } - } - if !foundController { - let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil) - chatController.hintPlayNextOutgoingGift() - controllers.append(chatController) - } - navigationController.setViewControllers(controllers, animated: true) + let tooltipController = UndoOverlayController( + presentationData: presentationData, + content: .sticker( + context: context, + file: starGift.file, + loop: true, + title: nil, + text: presentationData.strings.Gift_Send_Success(self.peerMap[peerId]?.compactDisplayTitle ?? "", presentationData.strings.Gift_Send_Success_Stars(Int32(starGift.price))).string, + undoText: nil, + customAction: nil + ), + action: { _ in return true } + ) + (navigationController.viewControllers.last as? ViewController)?.present(tooltipController, in: .current) + + navigationController.view.addSubview(ConfettiView(frame: navigationController.view.bounds)) + } else if peerId.namespace == Namespaces.Peer.CloudUser { + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) } + var foundController = false + for controller in controllers.reversed() { + if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation { + chatController.hintPlayNextOutgoingGift() + foundController = true + break } } - - starsContext.load(force: true) - }, error: { [weak self] error in - guard let self, let controller = self.environment?.controller() else { - return + if !foundController { + let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil) + chatController.hintPlayNextOutgoingGift() + controllers.append(chatController) } + navigationController.setViewControllers(controllers, animated: true) + } + + if let completion { + completion() - self.inProgress = false - self.state?.updated() - - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - var errorText: String? - switch error { - case .starGiftOutOfStock: - errorText = presentationData.strings.Gift_Send_ErrorOutOfStock - default: - errorText = presentationData.strings.Gift_Send_ErrorUnknown + if let controller = self.environment?.controller() { + controller.dismiss() } - - if let errorText = errorText { - let alertController = textAlertController(context: component.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) - controller.present(alertController, in: .window(.root)) - } - }) + } + + starsContext.load(force: true) + }, error: { [weak self] error in + guard let self, let controller = self.environment?.controller() else { + return + } + + self.inProgress = false + self.state?.updated() + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + var errorText: String? + switch error { + case .starGiftOutOfStock: + errorText = presentationData.strings.Gift_Send_ErrorOutOfStock + default: + errorText = presentationData.strings.Gift_Send_ErrorUnknown + } + + if let errorText = errorText { + let alertController = textAlertController(context: component.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + controller.present(alertController, in: .window(.root)) + } }) } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index ed70d21472..62f71b1247 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -3112,10 +3112,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID } self.entitiesView.canInteract = { [weak self] in if let self, let controller = self.controller { - return !controller.node.recording.isActive - } else { - return true + if controller.node.recording.isActive { + return false + } else if case .avatarEditor = controller.mode, self.drawingScreen == nil { + return false + } } + return true } self.availableReactionsDisposable = (allowedStoryReactions(context: controller.context) @@ -3252,11 +3255,12 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID if let mediaEntityView = self.entitiesView.add(mediaEntity, announce: false) as? DrawingMediaEntityView { self.entitiesView.sendSubviewToBack(mediaEntityView) mediaEntityView.updated = { [weak self, weak mediaEntity] in - if let self, let mediaEntity { + if let self, let mediaEditor = self.mediaEditor, let mediaEntity { let rotation = mediaEntity.rotation - initialRotation let position = CGPoint(x: mediaEntity.position.x - initialPosition.x, y: mediaEntity.position.y - initialPosition.y) let scale = mediaEntity.scale / initialScale - self.mediaEditor?.setCrop(offset: position, scale: scale, rotation: rotation, mirroring: false) + let mirroring = mediaEditor.values.cropMirroring + mediaEditor.setCrop(offset: position, scale: scale, rotation: rotation, mirroring: mirroring) self.updateMaskDrawingView(position: position, scale: scale, rotation: rotation) } @@ -3465,6 +3469,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID } return false }) as? DrawingStickerEntityView { + #if DEBUG + if let data = result.dayImage.pngData() { + let path = NSTemporaryDirectory() + "\(Int(Date().timeIntervalSince1970)).png" + try? data.write(to: URL(fileURLWithPath: path)) + } + #endif + existingEntityView.isNightTheme = isNightTheme let messageEntity = existingEntityView.entity as! DrawingStickerEntity messageEntity.renderImage = result.dayImage @@ -5613,6 +5624,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID self.previousDrawingData = self.drawingView.drawingData self.previousDrawingEntities = self.entitiesView.entities + self.cropScrollView?.isUserInteractionEnabled = false + self.interaction?.deactivate() let controller = DrawingScreen( context: self.context, @@ -5668,6 +5681,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID self.previousDrawingData = nil self.previousDrawingEntities = nil + + self.cropScrollView?.isUserInteractionEnabled = true } controller.requestApply = { [weak controller, weak self] in guard let self else { @@ -5690,6 +5705,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID self.interaction?.activate() self.entitiesView.selectEntity(nil) + + self.cropScrollView?.isUserInteractionEnabled = true } self.controller?.present(controller, in: .current) self.animateOutToTool(tool: mode) @@ -7594,6 +7611,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID let values = mediaEditor.values.withUpdatedCoverDimensions(dimensions) makeEditorImageComposition(context: self.node.ciContext, postbox: self.context.account.postbox, inputImage: image, dimensions: storyDimensions, outputDimensions: dimensions.aspectFitted(CGSize(width: 1080, height: 1080)), values: values, time: .zero, textScale: 2.0, completion: { [weak self] resultImage in if let self, let resultImage { + #if DEBUG + if let data = resultImage.jpegData(compressionQuality: 0.7) { + let path = NSTemporaryDirectory() + "\(Int(Date().timeIntervalSince1970)).jpg" + try? data.write(to: URL(fileURLWithPath: path)) + } + #endif + self.completion(MediaEditorScreenImpl.Result(media: .image(image: resultImage, dimensions: PixelDimensions(resultImage.size))), { [weak self] finished in self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in self?.dismiss() diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index 88df209521..60232b1e43 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -141,11 +141,11 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } public func scrollViewDidScroll(_ scrollView: UIScrollView) { - self.updateScrolling(transition: .immediate) + self.updateScrolling(interactive: true, transition: .immediate) } private var notify = false - func updateScrolling(transition: ComponentTransition) { + func updateScrolling(interactive: Bool = false, transition: ComponentTransition) { if let starsProducts = self.starsProducts, let params = self.currentParams { let optionSpacing: CGFloat = 10.0 let itemsSideInset = params.sideInset + 16.0 @@ -644,7 +644,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } let bottomContentOffset = max(0.0, self.scrollNode.view.contentSize.height - self.scrollNode.view.contentOffset.y - self.scrollNode.view.frame.height) - if bottomContentOffset < 200.0 { + if interactive, bottomContentOffset < 200.0 { self.profileGifts.loadMore() } } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift index 3602685b47..49157706af 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift @@ -293,9 +293,11 @@ private final class SheetContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0)) ) + var isExtendedMedia = false let subject: StarsImageComponent.Subject if !component.extendedMedia.isEmpty { subject = .extendedMedia(component.extendedMedia) + isExtendedMedia = true } else if let peer = state.botPeer { if let photo = component.invoice.photo { subject = .photo(photo) @@ -381,7 +383,7 @@ private final class SheetContent: CombinedComponent { contentSize.height += title.size.height contentSize.height += 13.0 - if isBot, let peer = state.botPeer { + if isBot && !isExtendedMedia, let peer = state.botPeer { contentSize.height -= 3.0 let peerShortcut = peerShortcut.update( component: PremiumPeerShortcutComponent( @@ -650,48 +652,49 @@ private final class SheetContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0)) ) contentSize.height += button.size.height - if isSubscription { - contentSize.height += 14.0 - - let termsTextFont = Font.regular(13.0) - let termsTextColor = theme.actionSheet.secondaryTextColor - let termsLinkColor = theme.actionSheet.controlAccentColor - let termsMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: termsTextFont, textColor: termsTextColor), bold: MarkdownAttributeSet(font: termsTextFont, textColor: termsTextColor), link: MarkdownAttributeSet(font: termsTextFont, textColor: termsLinkColor), linkAttribute: { contents in - return (TelegramTextAttributes.URL, contents) - }) - let info = info.update( - component: BalancedTextComponent( - text: .markdown( - text: strings.Stars_Subscription_Terms, - attributes: termsMarkdownAttributes - ), - horizontalAlignment: .center, - maximumNumberOfLines: 0, - lineSpacing: 0.2, - highlightColor: linkColor.withAlphaComponent(0.2), - highlightAction: { attributes in - if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { - return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) - } else { - return nil - } - }, - tapAction: { [weak controller] attributes, _ in - if let controller, let navigationController = controller.navigationController as? NavigationController { - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_Subscription_Terms_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) - } - } + + let termsText = isSubscription ? strings.Stars_Subscription_Terms : strings.Stars_Transfer_Terms + let termsURL = isSubscription ? strings.Stars_Subscription_Terms_URL : strings.Stars_Transfer_Terms_URL + + contentSize.height += 14.0 + + let termsTextFont = Font.regular(13.0) + let termsTextColor = theme.actionSheet.secondaryTextColor + let termsLinkColor = theme.actionSheet.controlAccentColor + let termsMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: termsTextFont, textColor: termsTextColor), bold: MarkdownAttributeSet(font: termsTextFont, textColor: termsTextColor), link: MarkdownAttributeSet(font: termsTextFont, textColor: termsLinkColor), linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + }) + let info = info.update( + component: BalancedTextComponent( + text: .markdown( + text: termsText, + attributes: termsMarkdownAttributes ), - availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height), - transition: .immediate - ) - context.add(info - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + info.size.height / 2.0)) - ) - contentSize.height += info.size.height - - } + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2, + highlightColor: linkColor.withAlphaComponent(0.2), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { [weak controller] attributes, _ in + if let controller, let navigationController = controller.navigationController as? NavigationController { + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: termsURL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) + } + } + ), + availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height), + transition: .immediate + ) + context.add(info + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + info.size.height / 2.0)) + ) + contentSize.height += info.size.height contentSize.height += 48.0 From c6eb97db0220b6a2f57b1326687dd27420d9f59e Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 14 Feb 2025 21:52:15 +0400 Subject: [PATCH 06/14] Bump version --- versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.json b/versions.json index e84553e625..f31d07a316 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "11.7.1", + "app": "11.7.2", "xcode": "16.2", "bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff", "macos": "15" From 5642fc634635c2a8c05b960b8b9217128f96d9f9 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Thu, 13 Feb 2025 18:51:54 +0400 Subject: [PATCH 07/14] Fix memory leak (cherry picked from commit c178023779ed24a143a00dc5e4ecbec06afa5982) --- .../Sources/ChatMessageInteractiveMediaNode.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index c705d9fa6c..5dda9dc5fc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -1952,17 +1952,17 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr videoNode.isUserInteractionEnabled = false var firstTime = true videoNode.ownsContentNodeUpdated = { [weak self] owns in - if let strongSelf = self { + if let strongSelf = self, let videoNode = strongSelf.videoNode { if firstTime { firstTime = false if startFromSavedPosition, let videoTimestamp { videoNode.seek(Double(videoTimestamp)) } } - strongSelf.videoNode?.isHidden = !owns + videoNode.isHidden = !owns if owns { - strongSelf.videoNode?.setBaseRate(1.0) - strongSelf.videoNode?.continuePlayingWithoutSound() + videoNode.setBaseRate(1.0) + videoNode.continuePlayingWithoutSound() } } } From 6a4b0854db4c969e0f41af11fac48c67df4e608b Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 28 Feb 2025 13:43:43 +0100 Subject: [PATCH 08/14] Various improvements --- .../Sources/DebugController.swift | 14 ++++++------- submodules/SemanticStatusNode/BUILD | 3 ++- .../Sources/SemanticStatusNode.swift | 20 +++++++++++++------ .../SemanticStatusNodeIconContext.swift | 11 +++++----- .../Sources/CallControllerNodeV2.swift | 10 +++++++++- .../Sources/BatchVideoRenderingContext.swift | 1 - .../Chat/ChatMessageInteractiveFileNode/BUILD | 1 + .../ChatMessageInteractiveFileNode.swift | 7 +++++++ ...oHeaderNavigationButtonContainerNode.swift | 11 ++++++++-- .../Sources/ExperimentalUISettings.swift | 16 +++++++-------- 10 files changed, 63 insertions(+), 31 deletions(-) diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 76f2848d8f..56398e6f7c 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -100,7 +100,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { case enableReactionOverrides(Bool) case storiesExperiment(Bool) case storiesJpegExperiment(Bool) - case playlistPlayback(Bool) + case conferenceDebug(Bool) case enableQuickReactionSwitch(Bool) case disableReloginTokens(Bool) case liveStreamV2(Bool) @@ -133,7 +133,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.web.rawValue case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure: return DebugControllerSection.experiments.rawValue - case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .fakeAds, .enableLocalTranslation: + case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .conferenceDebug, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .fakeAds, .enableLocalTranslation: return DebugControllerSection.experiments.rawValue case .logTranslationRecognition, .resetTranslationStates: return DebugControllerSection.translation.rawValue @@ -242,7 +242,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 47 case .disableReloginTokens: return 48 - case .playlistPlayback: + case .conferenceDebug: return 49 case .enableQuickReactionSwitch: return 50 @@ -1308,12 +1308,12 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }).start() }) - case let .playlistPlayback(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Playlist Playback", value: value, sectionId: self.section, style: .blocks, updated: { value in + case let .conferenceDebug(value): + return ItemListSwitchItem(presentationData: presentationData, title: "Conference Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings - settings.playlistPlayback = value + settings.conferenceDebug = value return PreferencesEntry(settings) }) }).start() @@ -1540,7 +1540,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present entries.append(.storiesJpegExperiment(experimentalSettings.storiesJpegExperiment)) entries.append(.disableReloginTokens(experimentalSettings.disableReloginTokens)) } - entries.append(.playlistPlayback(experimentalSettings.playlistPlayback)) + entries.append(.conferenceDebug(experimentalSettings.conferenceDebug)) entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction)) entries.append(.liveStreamV2(experimentalSettings.liveStreamV2)) entries.append(.experimentalCallMute(experimentalSettings.experimentalCallMute)) diff --git a/submodules/SemanticStatusNode/BUILD b/submodules/SemanticStatusNode/BUILD index e4a4b8d840..97e68da2c4 100644 --- a/submodules/SemanticStatusNode/BUILD +++ b/submodules/SemanticStatusNode/BUILD @@ -16,7 +16,8 @@ swift_library( "//submodules/GZip:GZip", "//submodules/rlottie:RLottieBinding", "//submodules/AppBundle:AppBundle", - "//submodules/ManagedAnimationNode:ManagedAnimationNode" + "//submodules/ManagedAnimationNode:ManagedAnimationNode", + "//submodules/Components/HierarchyTrackingLayer", ], visibility = [ "//visibility:public", diff --git a/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift b/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift index 7cb36a0453..d6864b7632 100644 --- a/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift +++ b/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift @@ -6,6 +6,7 @@ import SwiftSignalKit import RLottieBinding import GZip import AppBundle +import HierarchyTrackingLayer public enum SemanticStatusNodeState: Equatable { public struct ProgressAppearance: Equatable { @@ -90,7 +91,7 @@ private func svgPath(_ path: StaticString, scale: CGPoint = CGPoint(x: 1.0, y: 1 } private extension SemanticStatusNodeState { - func context(current: SemanticStatusNodeStateContext?) -> SemanticStatusNodeStateContext { + func context(current: SemanticStatusNodeStateContext?, animated: Bool) -> SemanticStatusNodeStateContext { switch self { case .none, .download, .play, .pause, .customIcon: let icon: SemanticStatusNodeIcon @@ -114,7 +115,7 @@ private extension SemanticStatusNodeState { if current.icon == icon { return current } else if (current.icon == .play && icon == .pause) || (current.icon == .pause && icon == .play) { - current.icon = icon + current.setIcon(icon: icon, animated: animated) return current } else { return SemanticStatusNodeIconContext(icon: icon) @@ -376,6 +377,8 @@ public final class SemanticStatusNode: ASControlNode { private var stateContext: SemanticStatusNodeStateContext private var appearanceContext: SemanticStatusNodeAppearanceContext + private let hierarchyTrackingLayer: HierarchyTrackingLayer + private var disposable: Disposable? private var backgroundNodeImage: UIImage? @@ -391,13 +394,16 @@ public final class SemanticStatusNode: ASControlNode { public init(backgroundNodeColor: UIColor, foregroundNodeColor: UIColor, image: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? = nil, overlayForegroundNodeColor: UIColor? = nil, cutout: CGRect? = nil) { self.state = .none - self.stateContext = self.state.context(current: nil) + self.stateContext = self.state.context(current: nil, animated: false) self.appearanceContext = SemanticStatusNodeAppearanceContext(background: backgroundNodeColor, foreground: foregroundNodeColor, backgroundImage: nil, overlayForeground: overlayForegroundNodeColor, cutout: cutout) + self.hierarchyTrackingLayer = HierarchyTrackingLayer() super.init() + self.layer.addSublayer(self.hierarchyTrackingLayer) + self.isOpaque = false - self.displaysAsynchronously = true + self.displaysAsynchronously = false if let image { self.setBackgroundImage(image, size: CGSize(width: 44.0, height: 44.0)) @@ -420,7 +426,6 @@ public final class SemanticStatusNode: ASControlNode { animate = true } } - if self.stateContext.isAnimating { animate = true } @@ -449,12 +454,15 @@ public final class SemanticStatusNode: ASControlNode { self.hasState = true animated = false } + if !self.hierarchyTrackingLayer.isInHierarchy { + animated = false + } if self.state != state || self.appearanceContext.cutout != cutout { self.state = state let previousStateContext = self.stateContext let previousAppearanceContext = updateCutout ? self.appearanceContext : nil - self.stateContext = self.state.context(current: self.stateContext) + self.stateContext = self.state.context(current: self.stateContext, animated: animated) self.stateContext.requestUpdate = { [weak self] in self?.setNeedsDisplay() } diff --git a/submodules/SemanticStatusNode/Sources/SemanticStatusNodeIconContext.swift b/submodules/SemanticStatusNode/Sources/SemanticStatusNodeIconContext.swift index f47b643bf7..bd9586b2d1 100644 --- a/submodules/SemanticStatusNode/Sources/SemanticStatusNodeIconContext.swift +++ b/submodules/SemanticStatusNode/Sources/SemanticStatusNodeIconContext.swift @@ -131,11 +131,7 @@ final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContext { } } - var icon: SemanticStatusNodeIcon { - didSet { - self.animationNode?.enqueueState(self.icon == .play ? .play : .pause, animated: self.iconImage != nil) - } - } + private(set) var icon: SemanticStatusNodeIcon private var animationNode: PlayPauseIconNode? private var iconImage: UIImage? @@ -171,6 +167,11 @@ final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContext { var requestUpdate: () -> Void = {} + func setIcon(icon: SemanticStatusNodeIcon, animated: Bool) { + self.icon = icon + self.animationNode?.enqueueState(self.icon == .play ? .play : .pause, animated: animated) + } + func drawingState(transitionFraction: CGFloat) -> SemanticStatusNodeStateDrawingState { return DrawingState(transitionFraction: transitionFraction, icon: self.icon, iconImage: self.iconImage, iconOffset: self.iconOffset) } diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift index 7ccc395be3..7e25b16b0b 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift @@ -167,6 +167,14 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP self.conferenceAddParticipant?() } + var isConferencePossible = false + if self.call.context.sharedContext.immediateExperimentalUISettings.conferenceDebug { + isConferencePossible = true + } + if let data = self.call.context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_enable_conference"] as? Double { + isConferencePossible = value != 0.0 + } + self.callScreenState = PrivateCallScreen.State( strings: presentationData.strings, lifecycleState: .connecting, @@ -180,7 +188,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP remoteVideo: nil, isRemoteBatteryLow: false, isEnergySavingEnabled: !self.sharedContext.energyUsageSettings.fullTranslucency, - isConferencePossible: true + isConferencePossible: isConferencePossible ) self.isMicrophoneMutedDisposable = (call.isMuted diff --git a/submodules/TelegramUI/Components/BatchVideoRendering/Sources/BatchVideoRenderingContext.swift b/submodules/TelegramUI/Components/BatchVideoRendering/Sources/BatchVideoRenderingContext.swift index ea3af6fad2..3455b29612 100644 --- a/submodules/TelegramUI/Components/BatchVideoRendering/Sources/BatchVideoRenderingContext.swift +++ b/submodules/TelegramUI/Components/BatchVideoRendering/Sources/BatchVideoRenderingContext.swift @@ -153,7 +153,6 @@ public final class BatchVideoRenderingContext { for (id, targetContext) in self.targetContexts { if targetContext.target != nil { if targetContext.fetchDisposable == nil { - //TODO:release pass resource reference targetContext.fetchDisposable = fetchedMediaResource( mediaBox: self.context.account.postbox.mediaBox, userLocation: targetContext.userLocation, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD index ff525a9ace..cf18fe2633 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD @@ -43,6 +43,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/AnimatedCountLabelNode", "//submodules/AudioWaveform", + "//submodules/DeviceProximity", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index 7d9888408e..5357300699 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -34,6 +34,7 @@ import ChatMessageItemCommon import TelegramStringFormatting import AnimatedCountLabelNode import AudioWaveform +import DeviceProximity private struct FetchControls { let fetch: (Bool) -> Void @@ -1561,6 +1562,12 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { guard let arguments = self.arguments else { return } + + var animated = animated + if DeviceProximityManager.shared().currentValue() { + animated = false + } + let incoming = message.effectivelyIncoming(context.account.peerId) let messageTheme = incoming ? presentationData.theme.theme.chat.message.incoming : presentationData.theme.theme.chat.message.outgoing diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift index 77d110f53e..09ee654713 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift @@ -54,7 +54,10 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { } var accumulatedRightButtonOffset: CGFloat = canBeExpanded ? 16.0 : 0.0 - for (_, button) in self.rightButtonNodes { + for spec in self.currentRightButtons.reversed() { + guard let button = self.rightButtonNodes[spec.key] else { + continue + } button.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: canBeExpanded, transition: transition) transition.updateSublayerTransformOffset(layer: button.layer, offset: CGPoint(x: accumulatedRightButtonOffset, y: 0.0)) if self.backgroundContentColor.alpha != 0.0 { @@ -174,6 +177,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { } } + var accumulatedRightButtonOffset: CGFloat = self.canBeExpanded ? 16.0 : 0.0 if self.currentRightButtons != rightButtons || presentationData.strings !== self.presentationData?.strings { self.currentRightButtons = rightButtons @@ -225,7 +229,10 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { buttonNode.alpha = 0.0 transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) - transition.updateSublayerTransformOffset(layer: buttonNode.layer, offset: CGPoint(x: canBeExpanded ? 16.0 : 0.0, y: 0.0)) + transition.updateSublayerTransformOffset(layer: buttonNode.layer, offset: CGPoint(x: accumulatedRightButtonOffset, y: 0.0)) + if self.backgroundContentColor.alpha != 0.0 { + accumulatedRightButtonOffset -= 6.0 + } } else { transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame) transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) diff --git a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift index 586ed5b192..3a2400cd6d 100644 --- a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift @@ -32,7 +32,6 @@ public struct ExperimentalUISettings: Codable, Equatable { public var knockoutWallpaper: Bool public var foldersTabAtBottom: Bool public var playerEmbedding: Bool - public var playlistPlayback: Bool public var preferredVideoCodec: String? public var disableVideoAspectScaling: Bool public var enableVoipTcp: Bool @@ -65,6 +64,7 @@ public struct ExperimentalUISettings: Codable, Equatable { public var playerV2: Bool public var devRequests: Bool public var fakeAds: Bool + public var conferenceDebug: Bool public static var defaultSettings: ExperimentalUISettings { return ExperimentalUISettings( @@ -75,7 +75,6 @@ public struct ExperimentalUISettings: Codable, Equatable { knockoutWallpaper: false, foldersTabAtBottom: false, playerEmbedding: false, - playlistPlayback: false, preferredVideoCodec: nil, disableVideoAspectScaling: false, enableVoipTcp: false, @@ -107,7 +106,8 @@ public struct ExperimentalUISettings: Codable, Equatable { autoBenchmarkReflectors: nil, playerV2: false, devRequests: false, - fakeAds: false + fakeAds: false, + conferenceDebug: false ) } @@ -119,7 +119,6 @@ public struct ExperimentalUISettings: Codable, Equatable { knockoutWallpaper: Bool, foldersTabAtBottom: Bool, playerEmbedding: Bool, - playlistPlayback: Bool, preferredVideoCodec: String?, disableVideoAspectScaling: Bool, enableVoipTcp: Bool, @@ -151,7 +150,8 @@ public struct ExperimentalUISettings: Codable, Equatable { autoBenchmarkReflectors: Bool?, playerV2: Bool, devRequests: Bool, - fakeAds: Bool + fakeAds: Bool, + conferenceDebug: Bool ) { self.keepChatNavigationStack = keepChatNavigationStack self.skipReadHistory = skipReadHistory @@ -160,7 +160,6 @@ public struct ExperimentalUISettings: Codable, Equatable { self.knockoutWallpaper = knockoutWallpaper self.foldersTabAtBottom = foldersTabAtBottom self.playerEmbedding = playerEmbedding - self.playlistPlayback = playlistPlayback self.preferredVideoCodec = preferredVideoCodec self.disableVideoAspectScaling = disableVideoAspectScaling self.enableVoipTcp = enableVoipTcp @@ -193,6 +192,7 @@ public struct ExperimentalUISettings: Codable, Equatable { self.playerV2 = playerV2 self.devRequests = devRequests self.fakeAds = fakeAds + self.conferenceDebug = conferenceDebug } public init(from decoder: Decoder) throws { @@ -205,7 +205,6 @@ public struct ExperimentalUISettings: Codable, Equatable { self.knockoutWallpaper = (try container.decodeIfPresent(Int32.self, forKey: "knockoutWallpaper") ?? 0) != 0 self.foldersTabAtBottom = (try container.decodeIfPresent(Int32.self, forKey: "foldersTabAtBottom") ?? 0) != 0 self.playerEmbedding = (try container.decodeIfPresent(Int32.self, forKey: "playerEmbedding") ?? 0) != 0 - self.playlistPlayback = (try container.decodeIfPresent(Int32.self, forKey: "playlistPlayback") ?? 0) != 0 self.preferredVideoCodec = try container.decodeIfPresent(String.self.self, forKey: "preferredVideoCodec") self.disableVideoAspectScaling = (try container.decodeIfPresent(Int32.self, forKey: "disableVideoAspectScaling") ?? 0) != 0 self.enableVoipTcp = (try container.decodeIfPresent(Int32.self, forKey: "enableVoipTcp") ?? 0) != 0 @@ -238,6 +237,7 @@ public struct ExperimentalUISettings: Codable, Equatable { self.playerV2 = try container.decodeIfPresent(Bool.self, forKey: "playerV2") ?? false self.devRequests = try container.decodeIfPresent(Bool.self, forKey: "devRequests") ?? false self.fakeAds = try container.decodeIfPresent(Bool.self, forKey: "fakeAds") ?? false + self.conferenceDebug = try container.decodeIfPresent(Bool.self, forKey: "conferenceDebug") ?? false } public func encode(to encoder: Encoder) throws { @@ -250,7 +250,6 @@ public struct ExperimentalUISettings: Codable, Equatable { try container.encode((self.knockoutWallpaper ? 1 : 0) as Int32, forKey: "knockoutWallpaper") try container.encode((self.foldersTabAtBottom ? 1 : 0) as Int32, forKey: "foldersTabAtBottom") try container.encode((self.playerEmbedding ? 1 : 0) as Int32, forKey: "playerEmbedding") - try container.encode((self.playlistPlayback ? 1 : 0) as Int32, forKey: "playlistPlayback") try container.encodeIfPresent(self.preferredVideoCodec, forKey: "preferredVideoCodec") try container.encode((self.disableVideoAspectScaling ? 1 : 0) as Int32, forKey: "disableVideoAspectScaling") try container.encode((self.enableVoipTcp ? 1 : 0) as Int32, forKey: "enableVoipTcp") @@ -283,6 +282,7 @@ public struct ExperimentalUISettings: Codable, Equatable { try container.encodeIfPresent(self.playerV2, forKey: "playerV2") try container.encodeIfPresent(self.devRequests, forKey: "devRequests") try container.encodeIfPresent(self.fakeAds, forKey: "fakeAds") + try container.encodeIfPresent(self.conferenceDebug, forKey: "conferenceDebug") } } From 99d210e364c18b7afc54dc4d5b56f30cb1a6b0d8 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 28 Feb 2025 14:40:28 +0100 Subject: [PATCH 09/14] Modernize weak references --- .../TelegramUI/Sources/ChatController.swift | 310 +++++++++--------- 1 file changed, 161 insertions(+), 149 deletions(-) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index e781a376ec..ba2d94f03b 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -887,29 +887,33 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message, params in - guard let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) else { + guard let self, self.isNodeLoaded, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) else { return false } let mode = params.mode - let displayVoiceMessageDiscardAlert: () -> Bool = { - if strongSelf.presentVoiceMessageDiscardAlert(action: { [weak self] in - if let strongSelf = self { - Queue.mainQueue().after(0.1, { - let _ = strongSelf.controllerInteraction?.openMessage(message, params) - }) - } + let displayVoiceMessageDiscardAlert: () -> Bool = { [weak self] in + guard let self else { + return true + } + if self.presentVoiceMessageDiscardAlert(action: { [weak self] in + Queue.mainQueue().after(0.1, { + guard let self else { + return + } + let _ = self.controllerInteraction?.openMessage(message, params) + }) }, performAction: false) { return false } return true } - strongSelf.commitPurposefulAction() - strongSelf.dismissAllTooltips() + self.commitPurposefulAction() + self.dismissAllTooltips() - strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() + self.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() var openMessageByAction = false var isLocation = false @@ -923,9 +927,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let file = media as? TelegramMediaFile { if file.isInstantVideo { - if strongSelf.chatDisplayNode.isInputViewFocused { - strongSelf.returnInputViewFocus = true - strongSelf.chatDisplayNode.dismissInput() + if self.chatDisplayNode.isInputViewFocused { + self.returnInputViewFocus = true + self.chatDisplayNode.dismissInput() } } if file.isMusic || file.isVoice || file.isInstantVideo { @@ -934,7 +938,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if (file.isVoice || file.isInstantVideo) && message.minAutoremoveOrClearTimeout == viewOnceTimeout { - strongSelf.openViewOnceMediaMessage(message) + self.openViewOnceMediaMessage(message) return false } } else if file.isVideo { @@ -947,7 +951,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch extendedMedia { case .preview: if displayVoiceMessageDiscardAlert() { - strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id, params) + self.controllerInteraction?.openCheckoutOrReceipt(message.id, params) return true } else { return false @@ -959,7 +963,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch extendedMedia { case .preview: if displayVoiceMessageDiscardAlert() { - strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id, nil) + self.controllerInteraction?.openCheckoutOrReceipt(message.id, nil) return true } else { return false @@ -969,15 +973,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } else if media is TelegramMediaGiveaway || media is TelegramMediaGiveawayResults { let progress = params.progress - let presentationData = strongSelf.presentationData + let presentationData = self.presentationData - var signal = strongSelf.context.engine.payments.premiumGiveawayInfo(peerId: message.id.peerId, messageId: message.id) + var signal = self.context.engine.payments.premiumGiveawayInfo(peerId: message.id.peerId, messageId: message.id) let disposable: MetaDisposable - if let current = strongSelf.giveawayStatusDisposable { + if let current = self.giveawayStatusDisposable { disposable = current } else { disposable = MetaDisposable() - strongSelf.giveawayStatusDisposable = disposable + self.giveawayStatusDisposable = disposable } let progressSignal = Signal { [weak self] subscriber in @@ -1010,8 +1014,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } disposable.set((signal |> deliverOnMainQueue).startStrict(next: { [weak self] info in - if let strongSelf = self, let info { - strongSelf.displayGiveawayStatusInfo(messageId: message.id, giveawayInfo: info) + if let self, let info { + self.displayGiveawayStatusInfo(messageId: message.id, giveawayInfo: info) } })) @@ -1024,22 +1028,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .pinnedMessageUpdated, .gameScore, .setSameChatWallpaper, .giveawayResults, .customText: for attribute in message.attributes { if let attribute = attribute as? ReplyMessageAttribute { - strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote.flatMap { quote in NavigateToMessageParams.Quote(string: quote.text, offset: quote.offset) } : nil))) + self.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote.flatMap { quote in NavigateToMessageParams.Quote(string: quote.text, offset: quote.offset) } : nil))) break } } case let .photoUpdated(image): openMessageByAction = image != nil case .groupPhoneCall, .inviteToGroupPhoneCall: - if let activeCall = strongSelf.presentationInterfaceState.activeGroupCallInfo?.activeCall { - strongSelf.joinGroupCall(peerId: message.id.peerId, invite: nil, activeCall: EngineGroupCallDescription(id: activeCall.id, accessHash: activeCall.accessHash, title: activeCall.title, scheduleTimestamp: activeCall.scheduleTimestamp, subscribedToScheduled: activeCall.subscribedToScheduled, isStream: activeCall.isStream)) + if let activeCall = self.presentationInterfaceState.activeGroupCallInfo?.activeCall { + self.joinGroupCall(peerId: message.id.peerId, invite: nil, activeCall: EngineGroupCallDescription(id: activeCall.id, accessHash: activeCall.accessHash, title: activeCall.title, scheduleTimestamp: activeCall.scheduleTimestamp, subscribedToScheduled: activeCall.subscribedToScheduled, isStream: activeCall.isStream)) } else { var canManageGroupCalls = false - if let channel = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramChannel { + if let channel = self.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramChannel { if channel.flags.contains(.isCreator) || channel.hasPermission(.manageCalls) { canManageGroupCalls = true } - } else if let group = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramGroup { + } else if let group = self.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramGroup { if case .creator = group.role { canManageGroupCalls = true } else if case let .admin(rights, _) = group.role { @@ -1051,80 +1055,80 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if canManageGroupCalls { let text: String - if let channel = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramChannel, case .broadcast = channel.info { - text = strongSelf.presentationData.strings.LiveStream_CreateNewVoiceChatText + if let channel = self.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramChannel, case .broadcast = channel.info { + text = self.presentationData.strings.LiveStream_CreateNewVoiceChatText } else { - text = strongSelf.presentationData.strings.VoiceChat_CreateNewVoiceChatText + text = self.presentationData.strings.VoiceChat_CreateNewVoiceChatText } - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.VoiceChat_CreateNewVoiceChatStartNow, action: { - if let strongSelf = self { + self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.VoiceChat_CreateNewVoiceChatStartNow, action: { [weak self] in + if let self { var dismissStatus: (() -> Void)? - let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: { + let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: { dismissStatus?() })) dismissStatus = { [weak self, weak statusController] in self?.createVoiceChatDisposable.set(nil) statusController?.dismiss() } - strongSelf.present(statusController, in: .window(.root)) - strongSelf.createVoiceChatDisposable.set((strongSelf.context.engine.calls.createGroupCall(peerId: message.id.peerId, title: nil, scheduleDate: nil, isExternalStream: false) + self.present(statusController, in: .window(.root)) + self.createVoiceChatDisposable.set((self.context.engine.calls.createGroupCall(peerId: message.id.peerId, title: nil, scheduleDate: nil, isExternalStream: false) |> deliverOnMainQueue).startStrict(next: { [weak self] info in - guard let strongSelf = self else { + guard let self else { return } - strongSelf.joinGroupCall(peerId: message.id.peerId, invite: nil, activeCall: EngineGroupCallDescription(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: info.scheduleTimestamp, subscribedToScheduled: info.subscribedToScheduled, isStream: info.isStream)) + self.joinGroupCall(peerId: message.id.peerId, invite: nil, activeCall: EngineGroupCallDescription(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: info.scheduleTimestamp, subscribedToScheduled: info.subscribedToScheduled, isStream: info.isStream)) }, error: { [weak self] error in dismissStatus?() - guard let strongSelf = self else { + guard let self else { return } let text: String switch error { case .generic, .scheduledTooLate: - text = strongSelf.presentationData.strings.Login_UnknownError + text = self.presentationData.strings.Login_UnknownError case .anonymousNotAllowed: if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { - text = strongSelf.presentationData.strings.LiveStream_AnonymousDisabledAlertText + text = self.presentationData.strings.LiveStream_AnonymousDisabledAlertText } else { - text = strongSelf.presentationData.strings.VoiceChat_AnonymousDisabledAlertText + text = self.presentationData.strings.VoiceChat_AnonymousDisabledAlertText } } - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }, completed: { dismissStatus?() })) } - }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.VoiceChat_CreateNewVoiceChatSchedule, action: { - if let strongSelf = self { - strongSelf.context.scheduleGroupCall(peerId: message.id.peerId, parentController: strongSelf) + }), TextAlertAction(type: .genericAction, title: self.presentationData.strings.VoiceChat_CreateNewVoiceChatSchedule, action: { [weak self] in + if let self { + self.context.scheduleGroupCall(peerId: message.id.peerId, parentController: self) } - }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})], actionLayout: .vertical), in: .window(.root)) + }), TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {})], actionLayout: .vertical), in: .window(.root)) } } return true case .messageAutoremoveTimeoutUpdated: var canSetupAutoremoveTimeout = false - if let _ = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat { + if let _ = self.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat { canSetupAutoremoveTimeout = false - } else if let group = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramGroup { + } else if let group = self.presentationInterfaceState.renderedPeer?.peer as? TelegramGroup { if !group.hasBannedPermission(.banChangeInfo) { canSetupAutoremoveTimeout = true } - } else if let user = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramUser { - if user.id != strongSelf.context.account.peerId && user.botInfo == nil { + } else if let user = self.presentationInterfaceState.renderedPeer?.peer as? TelegramUser { + if user.id != self.context.account.peerId && user.botInfo == nil { canSetupAutoremoveTimeout = true } - } else if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel { + } else if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel { if channel.hasPermission(.changeInfo) { canSetupAutoremoveTimeout = true } } if canSetupAutoremoveTimeout { - strongSelf.presentAutoremoveSetup() + self.presentAutoremoveSetup() } case let .paymentSent(currency, _, _, _, _): if currency == "XTR" { @@ -1136,14 +1140,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.push(self.context.sharedContext.makeStarsReceiptScreen(context: self.context, receipt: receipt)) }) } else { - strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: message.id), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + self.present(BotReceiptController(context: self.context, messageId: message.id), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } return true case .setChatTheme: - strongSelf.presentThemeSelection() + self.presentThemeSelection() return true case let .setChatWallpaper(wallpaper, _): - guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { + guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { return true } if let peer = peer as? TelegramChannel { @@ -1158,11 +1162,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } return true } - guard message.effectivelyIncoming(strongSelf.context.account.peerId), let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { - strongSelf.presentThemeSelection() + guard message.effectivelyIncoming(self.context.account.peerId), let peer = self.presentationInterfaceState.renderedPeer?.peer else { + self.presentThemeSelection() return true } - strongSelf.chatDisplayNode.dismissInput() + self.chatDisplayNode.dismissInput() var options = WallpaperPresentationOptions() var intensity: Int32? if let settings = wallpaper.settings { @@ -1176,7 +1180,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G intensity = settings.intensity } } - let wallpaperPreviewController = WallpaperGalleryController(context: strongSelf.context, source: .wallpaper(wallpaper, options, [], intensity, nil, nil), mode: .peer(EnginePeer(peer), true)) + let wallpaperPreviewController = WallpaperGalleryController(context: self.context, source: .wallpaper(wallpaper, options, [], intensity, nil, nil), mode: .peer(EnginePeer(peer), true)) wallpaperPreviewController.apply = { [weak wallpaperPreviewController] entry, options, _, _, brightness, forBoth in var settings: WallpaperSettings? if case let .wallpaper(wallpaper, _) = entry { @@ -1189,69 +1193,69 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } settings = WallpaperSettings(blur: options.contains(.blur), motion: options.contains(.motion), colors: baseSettings?.colors ?? [], intensity: intensity, rotation: baseSettings?.rotation) } - let _ = (strongSelf.context.engine.themes.setExistingChatWallpaper(messageId: message.id, settings: settings, forBoth: forBoth) + let _ = (self.context.engine.themes.setExistingChatWallpaper(messageId: message.id, settings: settings, forBoth: forBoth) |> deliverOnMainQueue).startStandalone() Queue.mainQueue().after(0.1) { wallpaperPreviewController?.dismiss() } } - strongSelf.push(wallpaperPreviewController) + self.push(wallpaperPreviewController) return true case let .giftPremium(_, _, duration, _, _, _, _): - strongSelf.chatDisplayNode.dismissInput() - let fromPeerId: PeerId = message.author?.id == strongSelf.context.account.peerId ? strongSelf.context.account.peerId : message.id.peerId - let toPeerId: PeerId = message.author?.id == strongSelf.context.account.peerId ? message.id.peerId : strongSelf.context.account.peerId - let controller = PremiumIntroScreen(context: strongSelf.context, source: .gift(from: fromPeerId, to: toPeerId, duration: duration, giftCode: nil)) - strongSelf.push(controller) + self.chatDisplayNode.dismissInput() + let fromPeerId: PeerId = message.author?.id == self.context.account.peerId ? self.context.account.peerId : message.id.peerId + let toPeerId: PeerId = message.author?.id == self.context.account.peerId ? message.id.peerId : self.context.account.peerId + let controller = PremiumIntroScreen(context: self.context, source: .gift(from: fromPeerId, to: toPeerId, duration: duration, giftCode: nil)) + self.push(controller) return true case .starGift, .starGiftUnique: - let controller = strongSelf.context.sharedContext.makeGiftViewScreen(context: strongSelf.context, message: EngineMessage(message), shareStory: { [weak self] uniqueGift in - if let self { - Queue.mainQueue().after(0.15) { + let controller = self.context.sharedContext.makeGiftViewScreen(context: self.context, message: EngineMessage(message), shareStory: { [weak self] uniqueGift in + Queue.mainQueue().after(0.15) { + if let self { let controller = self.context.sharedContext.makeStorySharingScreen(context: self.context, subject: .gift(uniqueGift), parentController: self) self.push(controller) } } }) - strongSelf.push(controller) + self.push(controller) return true case .giftStars: - let controller = strongSelf.context.sharedContext.makeStarsGiftScreen(context: strongSelf.context, message: EngineMessage(message)) - strongSelf.push(controller) + let controller = self.context.sharedContext.makeStarsGiftScreen(context: self.context, message: EngineMessage(message)) + self.push(controller) return true case let .giftCode(slug, _, _, _, _, _, _, _, _, _, _): - strongSelf.openResolved(result: .premiumGiftCode(slug: slug), sourceMessageId: message.id, progress: params.progress) + self.openResolved(result: .premiumGiftCode(slug: slug), sourceMessageId: message.id, progress: params.progress) return true case .prizeStars: - let controller = strongSelf.context.sharedContext.makeStarsGiftScreen(context: strongSelf.context, message: EngineMessage(message)) - strongSelf.push(controller) + let controller = self.context.sharedContext.makeStarsGiftScreen(context: self.context, message: EngineMessage(message)) + self.push(controller) return true case let .suggestedProfilePhoto(image): - strongSelf.chatDisplayNode.dismissInput() + self.chatDisplayNode.dismissInput() if let image = image { - if message.effectivelyIncoming(strongSelf.context.account.peerId) { + if message.effectivelyIncoming(self.context.account.peerId) { if let emojiMarkup = image.emojiMarkup { - let controller = AvatarEditorScreen(context: strongSelf.context, inputData: AvatarEditorScreen.inputData(context: strongSelf.context, isGroup: false), peerType: .user, markup: emojiMarkup) + let controller = AvatarEditorScreen(context: self.context, inputData: AvatarEditorScreen.inputData(context: self.context, isGroup: false), peerType: .user, markup: emojiMarkup) controller.imageCompletion = { [weak self] image, commit in - if let strongSelf = self { - if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl { + if let self { + if let rootController = self.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl { settingsController.updateProfilePhoto(image, mode: .accept, uploadStatus: nil) commit() } } } controller.videoCompletion = { [weak self] image, url, values, markup, commit in - if let strongSelf = self { - if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl { + if let self { + if let rootController = self.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl { settingsController.updateProfileVideo(image, video: nil, values: nil, markup: markup, mode: .accept, uploadStatus: nil) commit() } } } - strongSelf.push(controller) + self.push(controller) } else { var selectedNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? - strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in + self.chatDisplayNode.historyNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemView { if let result = itemNode.transitionNode(id: message.id, media: image, adjustRect: false) { selectedNode = result @@ -1267,17 +1271,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G senderName = nil } - legacyAvatarEditor(context: strongSelf.context, media: .message(message: MessageReference(message), media: image), transitionView: transitionView, senderName: senderName, present: { [weak self] c, a in + legacyAvatarEditor(context: self.context, media: .message(message: MessageReference(message), media: image), transitionView: transitionView, senderName: senderName, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) }, imageCompletion: { [weak self] image in - if let strongSelf = self { - if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl { + if let self { + if let rootController = self.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl { settingsController.updateProfilePhoto(image, mode: .accept, uploadStatus: nil) } } }, videoCompletion: { [weak self] image, url, adjustments in - if let strongSelf = self { - if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl { + if let self { + if let rootController = self.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl { settingsController.oldUpdateProfileVideo(image, asset: AVURLAsset(url: url), adjustments: adjustments, mode: .accept) } } @@ -1288,7 +1292,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } case .boostsApplied: - strongSelf.controllerInteraction?.openGroupBoostInfo(nil, 0) + self.controllerInteraction?.openGroupBoostInfo(nil, 0) return true default: break @@ -1299,29 +1303,33 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - let openChatLocation = strongSelf.chatLocation + let openChatLocation = self.chatLocation var chatFilterTag: MemoryBuffer? - if case let .customTag(value, _) = strongSelf.chatDisplayNode.historyNode.tag { + if case let .customTag(value, _) = self.chatDisplayNode.historyNode.tag { chatFilterTag = value } var standalone = false - if case .customChatContents = strongSelf.chatLocation { + if case .customChatContents = self.chatLocation { standalone = true } if let adAttribute = message.attributes.first(where: { $0 is AdMessageAttribute }) as? AdMessageAttribute { if let file = message.media.first(where: { $0 is TelegramMediaFile}) as? TelegramMediaFile, file.isVideo && !file.isAnimated { - strongSelf.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId, media: true, fullscreen: false) + self.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId, media: true, fullscreen: false) } else { - strongSelf.controllerInteraction?.activateAdAction(message.id, nil, true, false) + self.controllerInteraction?.activateAdAction(message.id, nil, true, false) return true } } - return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, updatedPresentationData: strongSelf.updatedPresentationData, chatLocation: openChatLocation, chatFilterTag: chatFilterTag, chatLocationContextHolder: strongSelf.chatLocationContextHolder, message: message, mediaIndex: params.mediaIndex, standalone: standalone, reverseMessageGalleryOrder: false, mode: mode, navigationController: strongSelf.effectiveNavigationController, dismissInput: { + let openChatMessageParams = OpenChatMessageParams(context: context, updatedPresentationData: self.updatedPresentationData, chatLocation: openChatLocation, chatFilterTag: chatFilterTag, chatLocationContextHolder: self.chatLocationContextHolder, message: message, mediaIndex: params.mediaIndex, standalone: standalone, reverseMessageGalleryOrder: false, mode: mode, navigationController: self.effectiveNavigationController, dismissInput: { [weak self] in self?.chatDisplayNode.dismissInput() - }, present: { c, a, i in + }, present: { [weak self] c, a, i in + guard let self else { + return + } + if case .current = i { c.presentationArguments = a c.statusBar.alphaUpdated = { [weak self] transition in @@ -1330,14 +1338,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } self.updateStatusBarPresentation(animated: transition.isAnimated) } - self?.galleryPresentationContext.present(c, on: PresentationSurfaceLevel(rawValue: 0), blockInteraction: true, completion: {}) + self.galleryPresentationContext.present(c, on: PresentationSurfaceLevel(rawValue: 0), blockInteraction: true, completion: {}) } else { - self?.present(c, in: .window(.root), with: a, blockInteraction: true) + self.present(c, in: .window(.root), with: a, blockInteraction: true) } - }, transitionNode: { messageId, media, adjustRect in + }, transitionNode: { [weak self] messageId, media, adjustRect in var selectedNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? - if let strongSelf = self { - strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in + if let self { + self.chatDisplayNode.historyNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemView { if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: adjustRect) { selectedNode = result @@ -1346,27 +1354,27 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } return selectedNode - }, addToTransitionSurface: { view in - guard let strongSelf = self else { + }, addToTransitionSurface: { [weak self] view in + guard let self else { return } - strongSelf.chatDisplayNode.historyNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.chatDisplayNode.historyNode.view) - }, openUrl: { url in + self.chatDisplayNode.historyNode.view.superview?.insertSubview(view, aboveSubview: self.chatDisplayNode.historyNode.view) + }, openUrl: { [weak self] url in self?.openUrl(url, concealed: false, skipConcealedAlert: isLocation, message: nil) - }, openPeer: { peer, navigation in + }, openPeer: { [weak self] peer, navigation in self?.openPeer(peer: EnginePeer(peer), navigation: navigation, fromMessage: nil) - }, callPeer: { peerId, isVideo in + }, callPeer: { [weak self] peerId, isVideo in self?.controllerInteraction?.callPeer(peerId, isVideo) - }, enqueueMessage: { message in + }, enqueueMessage: { [weak self] message in self?.sendMessages([message]) - }, sendSticker: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { fileReference, sourceNode, sourceRect in + }, sendSticker: canSendMessagesToChat(self.presentationInterfaceState) ? { [weak self] fileReference, sourceNode, sourceRect in return self?.controllerInteraction?.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil, []) ?? false - } : nil, sendEmoji: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { text, attribute in + } : nil, sendEmoji: canSendMessagesToChat(self.presentationInterfaceState) ? { [weak self] text, attribute in self?.controllerInteraction?.sendEmoji(text, attribute, false) - } : nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in - if let strongSelf = self { - strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { entry in - if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { + } : nil, setupTemporaryHiddenMedia: { [weak self] signal, centralIndex, galleryMedia in + if let self { + self.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { [weak self] entry in + if let self, let controllerInteraction = self.controllerInteraction { var messageIdAndMedia: [MessageId: [Media]] = [:] if let entry = entry as? InstantPageGalleryEntry, entry.index == centralIndex { @@ -1375,7 +1383,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G controllerInteraction.hiddenMedia = messageIdAndMedia - strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in + self.chatDisplayNode.historyNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemView { itemNode.updateHiddenMedia() } @@ -1383,10 +1391,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } })) } - }, chatAvatarHiddenMedia: { signal, media in - if let strongSelf = self { - strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { messageId in - if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { + }, chatAvatarHiddenMedia: { [weak self] signal, media in + if let self { + self.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { [weak self] messageId in + if let self, let controllerInteraction = self.controllerInteraction { var messageIdAndMedia: [MessageId: [Media]] = [:] if let messageId = messageId { @@ -1395,7 +1403,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G controllerInteraction.hiddenMedia = messageIdAndMedia - strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in + self.chatDisplayNode.historyNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemView { itemNode.updateHiddenMedia() } @@ -1405,54 +1413,54 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, actionInteraction: GalleryControllerActionInteraction( openUrl: { [weak self] url, concealed in - if let strongSelf = self { - strongSelf.openUrl(url, concealed: concealed, message: nil) + if let self { + self.openUrl(url, concealed: concealed, message: nil) } }, openUrlIn: { [weak self] url in - if let strongSelf = self { - strongSelf.openUrlIn(url) + if let self { + self.openUrlIn(url) } }, openPeerMention: { [weak self] mention in - if let strongSelf = self { - strongSelf.controllerInteraction?.openPeerMention(mention, nil) + if let self { + self.controllerInteraction?.openPeerMention(mention, nil) } }, openPeer: { [weak self] peer in - if let strongSelf = self { - strongSelf.controllerInteraction?.openPeer(peer, .default, nil, .default) + if let self { + self.controllerInteraction?.openPeer(peer, .default, nil, .default) } }, openHashtag: { [weak self] peerName, hashtag in - if let strongSelf = self { - strongSelf.controllerInteraction?.openHashtag(peerName, hashtag) + if let self { + self.controllerInteraction?.openHashtag(peerName, hashtag) } }, openBotCommand: { [weak self] command in - if let strongSelf = self { - strongSelf.controllerInteraction?.sendBotCommand(nil, command) + if let self { + self.controllerInteraction?.sendBotCommand(nil, command) } }, openAd: { [weak self] messageId in - if let strongSelf = self { - strongSelf.controllerInteraction?.activateAdAction(messageId, nil, true, true) + if let self { + self.controllerInteraction?.activateAdAction(messageId, nil, true, true) } }, addContact: { [weak self] phoneNumber in - if let strongSelf = self { - strongSelf.controllerInteraction?.addContact(phoneNumber) + if let self { + self.controllerInteraction?.addContact(phoneNumber) } }, storeMediaPlaybackState: { [weak self] messageId, timestamp, playbackRate in - guard let strongSelf = self else { + guard let self else { return } var storedState: MediaPlaybackStoredState? if let timestamp = timestamp { storedState = MediaPlaybackStoredState(timestamp: timestamp, playbackRate: AudioPlaybackRate(playbackRate)) } - let _ = updateMediaPlaybackStoredStateInteractively(engine: strongSelf.context.engine, messageId: messageId, state: storedState).startStandalone() + let _ = updateMediaPlaybackStoredStateInteractively(engine: self.context.engine, messageId: messageId, state: storedState).startStandalone() }, editMedia: { [weak self] messageId, snapshots, transitionCompletion in - guard let strongSelf = self else { + guard let self else { return } - let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId)) + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId)) |> deliverOnMainQueue).startStandalone(next: { [weak self] message in - guard let strongSelf = self, let message = message else { + guard let self, let message = message else { return } @@ -1472,17 +1480,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] { - legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: { + legacyMediaEditor(context: self.context, peer: peer, threadTitle: self.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: { transitionCompletion() }, getCaptionPanelView: { [weak self] in return self?.getCaptionPanelView(isFile: false) }, sendMessagesWithSignals: { [weak self] signals, _, _, isCaptionAbove in - if let strongSelf = self { + if let self { var parameters: ChatSendMessageActionSheetController.SendParameters? if isCaptionAbove { parameters = ChatSendMessageActionSheetController.SendParameters(effect: nil, textIsAboveMedia: true) } - strongSelf.enqueueMediaMessages(signals: signals, silentPosting: false, parameters: parameters) + self.enqueueMediaMessages(signals: signals, silentPosting: false, parameters: parameters) } }, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) @@ -1493,18 +1501,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self?.canReadHistory.set(canReadHistory) }), getSourceRect: { [weak self] in - guard let strongSelf = self else { + guard let self else { return nil } var rect: CGRect? - strongSelf.chatDisplayNode.historyNode.forEachVisibleMessageItemNode({ itemNode in + self.chatDisplayNode.historyNode.forEachVisibleMessageItemNode({ itemNode in if itemNode.item?.message.id == message.id { rect = itemNode.view.convert(itemNode.contentFrame(), to: nil) } }) return rect } - )) + ) + + self.controllerInteraction?.isOpeningMediaSignal = openChatMessageParams.blockInteraction.get() + + return context.sharedContext.openChatMessage(openChatMessageParams) }, openPeer: { [weak self] peer, navigation, fromMessage, source in var expandAvatar = false if case let .groupParticipant(storyStats, avatarHeaderNode) = source { From 16768ffb10b3e32c8aca1822807d6246bf132314 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 28 Feb 2025 14:52:13 +0100 Subject: [PATCH 10/14] Prevent media gallery from opening multiple times --- .../Sources/OpenChatMessage.swift | 2 ++ .../Items/UniversalVideoGalleryItem.swift | 33 +++++++++++-------- .../ChatMessageMediaBubbleContentNode.swift | 5 ++- .../Sources/ChatControllerInteraction.swift | 27 +++++++++++++++ .../TelegramUI/Sources/OpenChatMessage.swift | 4 +++ 5 files changed, 56 insertions(+), 15 deletions(-) diff --git a/submodules/AccountContext/Sources/OpenChatMessage.swift b/submodules/AccountContext/Sources/OpenChatMessage.swift index 25c58ae4dc..ae43e03132 100644 --- a/submodules/AccountContext/Sources/OpenChatMessage.swift +++ b/submodules/AccountContext/Sources/OpenChatMessage.swift @@ -48,6 +48,7 @@ public final class OpenChatMessageParams { public let gallerySource: GalleryControllerItemSource? public let centralItemUpdated: ((MessageId) -> Void)? public let getSourceRect: (() -> CGRect?)? + public let blockInteraction: Promise public init( context: AccountContext, @@ -109,5 +110,6 @@ public final class OpenChatMessageParams { self.gallerySource = gallerySource self.centralItemUpdated = centralItemUpdated self.getSourceRect = getSourceRect + self.blockInteraction = Promise() } } diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 0ea471dc67..5e2b000949 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -1076,7 +1076,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { private var customUnembedWhenPortrait: ((OverlayMediaItemNode) -> Bool)? - private var pictureInPictureContent: AnyObject? private var nativePictureInPictureContent: AnyObject? private var activePictureInPictureNavigationController: NavigationController? @@ -1544,10 +1543,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { strongSelf.videoNode?.setBaseRate(playbackRate) } } - - if strongSelf.nativePictureInPictureContent == nil { - strongSelf.setupNativePictureInPicture() - } } } self.videoNode = videoNode @@ -2963,19 +2958,29 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } @objc func pictureInPictureButtonPressed() { - if let currentPictureInPictureNode = self.context.sharedContext.mediaManager.currentPictureInPictureNode as? UniversalVideoGalleryItemNode, let currentItem = currentPictureInPictureNode.item, case let .message(currentMessage, _) = currentItem.contentInfo, case let .message(message, _) = self.item?.contentInfo, currentMessage.id == message.id { - if let controller = self.galleryController() as? GalleryController { - controller.dismiss(forceAway: true) - } - return + if self.nativePictureInPictureContent == nil { + self.setupNativePictureInPicture() } - if #available(iOS 15.0, *) { - if let nativePictureInPictureContent = self.nativePictureInPictureContent as? NativePictureInPictureContentImpl { - addAppLogEvent(postbox: self.context.account.postbox, type: "pip_btn", peerId: self.context.account.peerId) - nativePictureInPictureContent.beginPictureInPicture() + DispatchQueue.main.async { [weak self] in + guard let self else { return } + + if let currentPictureInPictureNode = self.context.sharedContext.mediaManager.currentPictureInPictureNode as? UniversalVideoGalleryItemNode, let currentItem = currentPictureInPictureNode.item, case let .message(currentMessage, _) = currentItem.contentInfo, case let .message(message, _) = self.item?.contentInfo, currentMessage.id == message.id { + if let controller = self.galleryController() as? GalleryController { + controller.dismiss(forceAway: true) + } + return + } + + if #available(iOS 15.0, *) { + if let nativePictureInPictureContent = self.nativePictureInPictureContent as? NativePictureInPictureContentImpl { + addAppLogEvent(postbox: self.context.account.postbox, type: "pip_btn", peerId: self.context.account.peerId) + nativePictureInPictureContent.beginPictureInPicture() + return + } + } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift index 6450e44497..49392be769 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift @@ -58,7 +58,10 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { openChatMessageMode = .automaticPlayback } - let _ = item.controllerInteraction.openMessage(item.message, OpenMessageParams(mode: openChatMessageMode, mediaIndex: self.mediaIndex, progress: self.itemNode?.makeProgress())) + if !item.controllerInteraction.isOpeningMedia { + let params = OpenMessageParams(mode: openChatMessageMode, mediaIndex: self.mediaIndex, progress: self.itemNode?.makeProgress()) + let _ = item.controllerInteraction.openMessage(item.message, params) + } } self.interactiveImageNode.activateAgeRestrictedMedia = { [weak self] in diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 93368bc6e6..b34776baba 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -303,6 +303,29 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public var chatIsRotated: Bool = true public var canReadHistory: Bool = false + private var isOpeningMediaValue: Bool = false + public var isOpeningMedia: Bool { + return self.isOpeningMediaValue + } + private var isOpeningMediaDisposable: Disposable? + public var isOpeningMediaSignal: Signal? { + didSet { + self.isOpeningMediaDisposable?.dispose() + self.isOpeningMediaDisposable = nil + self.isOpeningMediaValue = false + + if let isOpeningMediaSignal = self.isOpeningMediaSignal { + self.isOpeningMediaValue = true + self.isOpeningMediaDisposable = (isOpeningMediaSignal |> filter { !$0 } |> take(1) |> timeout(1.0, queue: .mainQueue(), alternate: .single(false)) |> deliverOnMainQueue).startStrict(next: { [weak self] _ in + guard let self else { + return + } + self.isOpeningMediaValue = false + }) + } + } + } + public init( openMessage: @escaping (Message, OpenMessageParams) -> Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void, @@ -538,4 +561,8 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol self.presentationContext = presentationContext } + + deinit { + self.isOpeningMediaDisposable?.dispose() + } } diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index e19daaeab9..ac593bd233 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -323,8 +323,12 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { return true } + params.blockInteraction.set(.single(true)) + let _ = (gallery |> deliverOnMainQueue).startStandalone(next: { gallery in + params.blockInteraction.set(.single(false)) + gallery.centralItemUpdated = { messageId in params.centralItemUpdated?(messageId) } From 66ea2cd0c3ee7f94896b70ff58e6bf7a407184dc Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 28 Feb 2025 15:39:15 +0100 Subject: [PATCH 11/14] Update localization --- Telegram/Telegram-iOS/en.lproj/Localizable.strings | 4 ++++ .../GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift | 3 +-- submodules/ShareController/Sources/ShareControllerNode.swift | 3 +-- .../Sources/AvatarUploadToastScreen.swift | 1 - .../ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift | 3 +-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 3f867105ee..0838850213 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13975,3 +13975,7 @@ Sorry for the inconvenience."; "Privacy.Review.Invite.Title" = "Invitation Settings"; "Privacy.Review.Invite.Text" = "You've restricted who can message you, but anyone can still invite you to groups and channels. Would you like to review these settings?"; + +"Conversation.VideoTimeLinkCopied" = "Link with start time at %@ copied to clipboard."; +"Share.VideoStartAt" = "Start at %@"; +"SendStarReactions.SubtitleFrom" = "from %@"; diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index c608ff148d..1a7f37f6f3 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -1755,7 +1755,6 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let text: String if let timestamp { - //TODO:localize let startTimeString: String let hours = timestamp / (60 * 60) let minutes = timestamp % (60 * 60) / 60 @@ -1765,7 +1764,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll } else { startTimeString = String(format: "%d:%02d", minutes, seconds) } - text = "Link with start time at \(startTimeString) copied to clipboard." + text = presentationData.strings.Conversation_VideoTimeLinkCopied(startTimeString).string } else { text = presentationData.strings.Conversation_LinkCopied } diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 3c71ca02d1..17b083c6eb 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -472,8 +472,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate self.actionButtonNode.setBackgroundImage(highlightedHalfRoundedBackground, for: .highlighted) if let startAtTimestamp = mediaParameters?.startAtTimestamp { - //TODO:localize - self.startAtTimestampNode = ShareStartAtTimestampNode(titleText: "Start at \(textForTimeout(value: startAtTimestamp))", titleTextColor: self.presentationData.theme.actionSheet.secondaryTextColor, checkNodeTheme: CheckNodeTheme(backgroundColor: presentationData.theme.list.itemCheckColors.fillColor, strokeColor: presentationData.theme.list.itemCheckColors.foregroundColor, borderColor: presentationData.theme.list.itemCheckColors.strokeColor, overlayBorder: false, hasInset: false, hasShadow: false)) + self.startAtTimestampNode = ShareStartAtTimestampNode(titleText: self.presentationData.strings.Share_VideoStartAt(textForTimeout(value: startAtTimestamp)).string, titleTextColor: self.presentationData.theme.actionSheet.secondaryTextColor, checkNodeTheme: CheckNodeTheme(backgroundColor: presentationData.theme.list.itemCheckColors.fillColor, strokeColor: presentationData.theme.list.itemCheckColors.foregroundColor, borderColor: presentationData.theme.list.itemCheckColors.strokeColor, overlayBorder: false, hasInset: false, hasShadow: false)) } else { self.startAtTimestampNode = nil } diff --git a/submodules/TelegramUI/Components/AvatarUploadToastScreen/Sources/AvatarUploadToastScreen.swift b/submodules/TelegramUI/Components/AvatarUploadToastScreen/Sources/AvatarUploadToastScreen.swift index 00c1588cc6..b5cf3a95ab 100644 --- a/submodules/TelegramUI/Components/AvatarUploadToastScreen/Sources/AvatarUploadToastScreen.swift +++ b/submodules/TelegramUI/Components/AvatarUploadToastScreen/Sources/AvatarUploadToastScreen.swift @@ -264,7 +264,6 @@ private final class AvatarUploadToastScreenComponent: Component { containerSize: CGSize(width: availableContentSize.width - contentInsets.left - contentInsets.right - spacing - iconSize.width, height: availableContentSize.height) ) - //TODO:localize let contentSize = self.content.update( transition: transition, component: AnyComponent(AnimatedTextComponent( diff --git a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift index 33a46dbca8..3f3e47ca50 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift @@ -1794,11 +1794,10 @@ private final class ChatSendStarsScreenComponent: Component { let titleSubtitleSpacing: CGFloat = 1.0 - //TODO:localize let subtitleSize = self.subtitle.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "from \(currentMyPeer.compactDisplayTitle)", font: Font.regular(12.0), textColor: environment.theme.list.itemSecondaryTextColor)) + text: .plain(NSAttributedString(string: environment.strings.SendStarReactions_SubtitleFrom(currentMyPeer.compactDisplayTitle).string, font: Font.regular(12.0), textColor: environment.theme.list.itemSecondaryTextColor)) )), environment: {}, containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0) From dc4ea3ea9d0d05034b6c20b6a759c1304b08c054 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 28 Feb 2025 15:39:37 +0100 Subject: [PATCH 12/14] Use large video thumbnail if available --- .../Sources/PhotoResources.swift | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index 26535420d0..46a8a8e5a8 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -524,15 +524,34 @@ private func chatMessageVideoDatas(postbox: Postbox, userLocation: MediaResource thumbnail = .single(decodedThumbnailData) } } else if let thumbnailResource = thumbnailResource { - thumbnail = Signal { subscriber in - let fetchedDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: userLocation, userContentType: customUserContentType ?? MediaResourceUserContentType(file: fileReference.media), reference: fileReference.resourceReference(thumbnailResource), statsCategory: .video).start() - let thumbnailDisposable = postbox.mediaBox.resourceData(thumbnailResource, attemptSynchronously: synchronousLoad).start(next: { next in - subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) - }, error: subscriber.putError, completed: subscriber.putCompletion) - - return ActionDisposable { - fetchedDisposable.dispose() - thumbnailDisposable.dispose() + if autoFetchFullSizeThumbnail, let thumbnailRepresentation = thumbnailRepresentation, (thumbnailRepresentation.dimensions.width > 200 || thumbnailRepresentation.dimensions.height > 200) { + thumbnail = Signal { subscriber in + let fetchedDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: userLocation, userContentType: customUserContentType ?? MediaResourceUserContentType(file: fileReference.media), reference: fileReference.resourceReference(thumbnailRepresentation.resource), statsCategory: .video).start() + let thumbnailDisposable = postbox.mediaBox.resourceData(thumbnailRepresentation.resource, attemptSynchronously: synchronousLoad).start(next: { next in + let data: Data? = next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []) + if let data { + subscriber.putNext(data) + } else { + subscriber.putNext(nil) + } + }, error: subscriber.putError, completed: subscriber.putCompletion) + + return ActionDisposable { + fetchedDisposable.dispose() + thumbnailDisposable.dispose() + } + } + } else { + thumbnail = Signal { subscriber in + let fetchedDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: userLocation, userContentType: customUserContentType ?? MediaResourceUserContentType(file: fileReference.media), reference: fileReference.resourceReference(thumbnailResource), statsCategory: .video).start() + let thumbnailDisposable = postbox.mediaBox.resourceData(thumbnailResource, attemptSynchronously: synchronousLoad).start(next: { next in + subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) + }, error: subscriber.putError, completed: subscriber.putCompletion) + + return ActionDisposable { + fetchedDisposable.dispose() + thumbnailDisposable.dispose() + } } } } else { From 9efef8d64ef556be50954a411942572ffe1d882c Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 28 Feb 2025 15:40:25 +0100 Subject: [PATCH 13/14] Call improvements --- .../Sources/CallControllerNodeV2.swift | 6 +++ .../Sources/PresentationCall.swift | 42 +++++++++++++++++-- .../Sources/PrivateCallScreen.swift | 2 +- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift index 7e25b16b0b..5641bf8f89 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift @@ -528,6 +528,12 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP } if var callScreenState = self.callScreenState { + if callScreenState.remoteVideo == nil && self.remoteVideo != nil { + if let call = self.call as? PresentationCallImpl, let sharedAudioContext = call.sharedAudioContext, case .builtin = sharedAudioContext.currentAudioOutputValue { + call.playRemoteCameraTone() + } + } + callScreenState.lifecycleState = mappedLifecycleState callScreenState.remoteVideo = self.remoteVideo callScreenState.localVideo = self.localVideo diff --git a/submodules/TelegramCallsUI/Sources/PresentationCall.swift b/submodules/TelegramCallsUI/Sources/PresentationCall.swift index a4532da6f5..382f61463e 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCall.swift @@ -15,13 +15,14 @@ import AccountContext import DeviceProximity import PhoneNumberFormat -final class SharedCallAudioContext { +public final class SharedCallAudioContext { let audioDevice: OngoingCallContext.AudioDevice? let callKitIntegration: CallKitIntegration? private var audioSessionDisposable: Disposable? private var audioSessionShouldBeActiveDisposable: Disposable? private var isAudioSessionActiveDisposable: Disposable? + private var audioOutputStateDisposable: Disposable? private(set) var audioSessionControl: ManagedAudioSessionControl? @@ -32,7 +33,7 @@ final class SharedCallAudioContext { private let audioOutputStatePromise = Promise<([AudioSessionOutput], AudioSessionOutput?)>(([], nil)) private var audioOutputStateValue: ([AudioSessionOutput], AudioSessionOutput?) = ([], nil) - private var currentAudioOutputValue: AudioSessionOutput = .builtin + public private(set) var currentAudioOutputValue: AudioSessionOutput = .builtin private var didSetCurrentAudioOutputValue: Bool = false var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { return self.audioOutputStatePromise.get() @@ -141,12 +142,24 @@ final class SharedCallAudioContext { } self.audioDevice?.setIsAudioSessionActive(value) }) + + self.audioOutputStateDisposable = (self.audioOutputStatePromise.get() + |> deliverOnMainQueue).start(next: { [weak self] value in + guard let self else { + return + } + self.audioOutputStateValue = value + if let currentOutput = value.1 { + self.currentAudioOutputValue = currentOutput + } + }) } deinit { self.audioSessionDisposable?.dispose() self.audioSessionShouldBeActiveDisposable?.dispose() self.isAudioSessionActiveDisposable?.dispose() + self.audioOutputStateDisposable?.dispose() } func setCurrentAudioOutput(_ output: AudioSessionOutput) { @@ -201,7 +214,7 @@ public final class PresentationCallImpl: PresentationCall { private let currentNetworkType: NetworkType private let updatedNetworkType: Signal - private var sharedAudioContext: SharedCallAudioContext? + public private(set) var sharedAudioContext: SharedCallAudioContext? private var sessionState: CallSession? private var callContextState: OngoingCallContextState? @@ -1610,6 +1623,29 @@ public final class PresentationCallImpl: PresentationCall { self.useFrontCamera = !self.useFrontCamera self.videoCapturer?.switchVideoInput(isFront: self.useFrontCamera) } + + public func playRemoteCameraTone() { + let name: String + name = "voip_group_recording_started.mp3" + + self.beginTone(tone: .custom(name: name, loopCount: 1)) + } + + private func beginTone(tone: PresentationCallTone?) { + if let tone, let toneData = presentationCallToneData(tone) { + if let sharedAudioContext = self.sharedAudioContext { + sharedAudioContext.audioDevice?.setTone(tone: OngoingCallContext.Tone( + samples: toneData, + sampleRate: 48000, + loopCount: tone.loopCount ?? 100000 + )) + } + } else { + if let sharedAudioContext = self.sharedAudioContext { + sharedAudioContext.audioDevice?.setTone(tone: nil) + } + } + } } func sampleBufferFromPixelBuffer(pixelBuffer: CVPixelBuffer) -> CMSampleBuffer? { diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift index 8681522eed..3927200df2 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift @@ -613,7 +613,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu if let previousParams = self.params, case .active = params.state.lifecycleState { switch previousParams.state.lifecycleState { case .requesting, .ringing, .connecting, .reconnecting: - if self.hideEmojiTooltipTimer == nil && !self.areControlsHidden { + if self.hideEmojiTooltipTimer == nil && !self.areControlsHidden && self.activeRemoteVideoSource == nil && self.activeLocalVideoSource == nil { self.displayEmojiTooltip = true self.hideEmojiTooltipTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self] _ in From b51d1a2de8c0e47a7e60e7c88a56f209c25f1efb Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 28 Feb 2025 17:01:27 +0100 Subject: [PATCH 14/14] Fix chat list search reference cycle --- .../Sources/ChatListSearchListPaneNode.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index b943abbd31..b3c32c75b6 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -3566,12 +3566,24 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } }) }, openStories: { peerId, avatarNode in + guard let strongSelf = self else { + return + } strongSelf.interaction.openStories?(peerId, avatarNode) }, openPublicPosts: { + guard let strongSelf = self else { + return + } strongSelf.interaction.switchToFilter(.publicPosts) }, openMessagesFilter: { sourceNode in + guard let strongSelf = self else { + return + } strongSelf.openMessagesFilter(sourceNode: sourceNode) }, switchMessagesFilter: { filter in + guard let strongSelf = self else { + return + } strongSelf.searchScopePromise.set(.everywhere) }) strongSelf.currentEntries = newEntries