diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 0ec0e83f61..3df82e28b0 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13805,6 +13805,13 @@ Sorry for the inconvenience."; "Notification.StarsGift.TransferToChannel" = "%1$@ transferred a unique collectible to %2$@"; "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"; + "AvatarEditor.PremiumNeeded.Background" = "Subscribe to Telegram Premium to choose this background."; "AvatarEditor.PremiumNeeded.Emoji" = "Subscribe to Telegram Premium to choose this emoji."; "AvatarEditor.PremiumNeeded.Sticker" = "Subscribe to Telegram Premium to choose this sticker."; @@ -13981,3 +13988,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/build_number_offset b/build_number_offset index d0f0d290cd..d1a7e335c7 100644 --- a/build_number_offset +++ b/build_number_offset @@ -1 +1 @@ -2510 +2515 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/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 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/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/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/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 { 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/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index d50cc584f2..b3046db256 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/TelegramCallsUI/Sources/CallControllerNodeV2.swift b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift index 7ccc395be3..5641bf8f89 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 @@ -520,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/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/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/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 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/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/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) 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/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/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 { 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) } 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") } }