Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2025-02-28 20:09:41 +04:00
commit d62c37adf1
26 changed files with 381 additions and 216 deletions

View File

@ -13805,6 +13805,13 @@ Sorry for the inconvenience.";
"Notification.StarsGift.TransferToChannel" = "%1$@ transferred a unique collectible to %2$@"; "Notification.StarsGift.TransferToChannel" = "%1$@ transferred a unique collectible to %2$@";
"Notification.StarsGift.TransferToChannelYou" = "You 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";
"AvatarEditor.PremiumNeeded.Background" = "Subscribe to Telegram Premium to choose this background."; "AvatarEditor.PremiumNeeded.Background" = "Subscribe to Telegram Premium to choose this background.";
"AvatarEditor.PremiumNeeded.Emoji" = "Subscribe to Telegram Premium to choose this emoji."; "AvatarEditor.PremiumNeeded.Emoji" = "Subscribe to Telegram Premium to choose this emoji.";
"AvatarEditor.PremiumNeeded.Sticker" = "Subscribe to Telegram Premium to choose this sticker."; "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.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?"; "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 %@";

View File

@ -1 +1 @@
2510 2515

View File

@ -48,6 +48,7 @@ public final class OpenChatMessageParams {
public let gallerySource: GalleryControllerItemSource? public let gallerySource: GalleryControllerItemSource?
public let centralItemUpdated: ((MessageId) -> Void)? public let centralItemUpdated: ((MessageId) -> Void)?
public let getSourceRect: (() -> CGRect?)? public let getSourceRect: (() -> CGRect?)?
public let blockInteraction: Promise<Bool>
public init( public init(
context: AccountContext, context: AccountContext,
@ -109,5 +110,6 @@ public final class OpenChatMessageParams {
self.gallerySource = gallerySource self.gallerySource = gallerySource
self.centralItemUpdated = centralItemUpdated self.centralItemUpdated = centralItemUpdated
self.getSourceRect = getSourceRect self.getSourceRect = getSourceRect
self.blockInteraction = Promise()
} }
} }

View File

@ -3566,12 +3566,24 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} }
}) })
}, openStories: { peerId, avatarNode in }, openStories: { peerId, avatarNode in
guard let strongSelf = self else {
return
}
strongSelf.interaction.openStories?(peerId, avatarNode) strongSelf.interaction.openStories?(peerId, avatarNode)
}, openPublicPosts: { }, openPublicPosts: {
guard let strongSelf = self else {
return
}
strongSelf.interaction.switchToFilter(.publicPosts) strongSelf.interaction.switchToFilter(.publicPosts)
}, openMessagesFilter: { sourceNode in }, openMessagesFilter: { sourceNode in
guard let strongSelf = self else {
return
}
strongSelf.openMessagesFilter(sourceNode: sourceNode) strongSelf.openMessagesFilter(sourceNode: sourceNode)
}, switchMessagesFilter: { filter in }, switchMessagesFilter: { filter in
guard let strongSelf = self else {
return
}
strongSelf.searchScopePromise.set(.everywhere) strongSelf.searchScopePromise.set(.everywhere)
}) })
strongSelf.currentEntries = newEntries strongSelf.currentEntries = newEntries

View File

@ -100,7 +100,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case enableReactionOverrides(Bool) case enableReactionOverrides(Bool)
case storiesExperiment(Bool) case storiesExperiment(Bool)
case storiesJpegExperiment(Bool) case storiesJpegExperiment(Bool)
case playlistPlayback(Bool) case conferenceDebug(Bool)
case enableQuickReactionSwitch(Bool) case enableQuickReactionSwitch(Bool)
case disableReloginTokens(Bool) case disableReloginTokens(Bool)
case liveStreamV2(Bool) case liveStreamV2(Bool)
@ -133,7 +133,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.web.rawValue return DebugControllerSection.web.rawValue
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure: case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
return DebugControllerSection.experiments.rawValue 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 return DebugControllerSection.experiments.rawValue
case .logTranslationRecognition, .resetTranslationStates: case .logTranslationRecognition, .resetTranslationStates:
return DebugControllerSection.translation.rawValue return DebugControllerSection.translation.rawValue
@ -242,7 +242,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 47 return 47
case .disableReloginTokens: case .disableReloginTokens:
return 48 return 48
case .playlistPlayback: case .conferenceDebug:
return 49 return 49
case .enableQuickReactionSwitch: case .enableQuickReactionSwitch:
return 50 return 50
@ -1308,12 +1308,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}).start() }).start()
}) })
case let .playlistPlayback(value): case let .conferenceDebug(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Playlist Playback", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, title: "Conference Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.playlistPlayback = value settings.conferenceDebug = value
return PreferencesEntry(settings) return PreferencesEntry(settings)
}) })
}).start() }).start()
@ -1540,7 +1540,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.storiesJpegExperiment(experimentalSettings.storiesJpegExperiment)) entries.append(.storiesJpegExperiment(experimentalSettings.storiesJpegExperiment))
entries.append(.disableReloginTokens(experimentalSettings.disableReloginTokens)) entries.append(.disableReloginTokens(experimentalSettings.disableReloginTokens))
} }
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback)) entries.append(.conferenceDebug(experimentalSettings.conferenceDebug))
entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction)) entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction))
entries.append(.liveStreamV2(experimentalSettings.liveStreamV2)) entries.append(.liveStreamV2(experimentalSettings.liveStreamV2))
entries.append(.experimentalCallMute(experimentalSettings.experimentalCallMute)) entries.append(.experimentalCallMute(experimentalSettings.experimentalCallMute))

View File

@ -1755,7 +1755,6 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let text: String let text: String
if let timestamp { if let timestamp {
//TODO:localize
let startTimeString: String let startTimeString: String
let hours = timestamp / (60 * 60) let hours = timestamp / (60 * 60)
let minutes = timestamp % (60 * 60) / 60 let minutes = timestamp % (60 * 60) / 60
@ -1765,7 +1764,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
} else { } else {
startTimeString = String(format: "%d:%02d", minutes, seconds) 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 { } else {
text = presentationData.strings.Conversation_LinkCopied text = presentationData.strings.Conversation_LinkCopied
} }

View File

@ -1076,7 +1076,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
private var customUnembedWhenPortrait: ((OverlayMediaItemNode) -> Bool)? private var customUnembedWhenPortrait: ((OverlayMediaItemNode) -> Bool)?
private var pictureInPictureContent: AnyObject?
private var nativePictureInPictureContent: AnyObject? private var nativePictureInPictureContent: AnyObject?
private var activePictureInPictureNavigationController: NavigationController? private var activePictureInPictureNavigationController: NavigationController?
@ -1544,10 +1543,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
strongSelf.videoNode?.setBaseRate(playbackRate) strongSelf.videoNode?.setBaseRate(playbackRate)
} }
} }
if strongSelf.nativePictureInPictureContent == nil {
strongSelf.setupNativePictureInPicture()
}
} }
} }
self.videoNode = videoNode self.videoNode = videoNode
@ -2963,6 +2958,15 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} }
@objc func pictureInPictureButtonPressed() { @objc func pictureInPictureButtonPressed() {
if self.nativePictureInPictureContent == nil {
self.setupNativePictureInPicture()
}
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 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 { if let controller = self.galleryController() as? GalleryController {
controller.dismiss(forceAway: true) controller.dismiss(forceAway: true)
@ -2978,6 +2982,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} }
} }
} }
}
func expandPIP() { func expandPIP() {
if #available(iOS 15.0, *) { if #available(iOS 15.0, *) {

View File

@ -524,6 +524,24 @@ private func chatMessageVideoDatas(postbox: Postbox, userLocation: MediaResource
thumbnail = .single(decodedThumbnailData) thumbnail = .single(decodedThumbnailData)
} }
} else if let thumbnailResource = thumbnailResource { } else if let thumbnailResource = thumbnailResource {
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 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 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 let thumbnailDisposable = postbox.mediaBox.resourceData(thumbnailResource, attemptSynchronously: synchronousLoad).start(next: { next in
@ -535,6 +553,7 @@ private func chatMessageVideoDatas(postbox: Postbox, userLocation: MediaResource
thumbnailDisposable.dispose() thumbnailDisposable.dispose()
} }
} }
}
} else { } else {
thumbnail = .single(nil) thumbnail = .single(nil)
} }

View File

@ -16,7 +16,8 @@ swift_library(
"//submodules/GZip:GZip", "//submodules/GZip:GZip",
"//submodules/rlottie:RLottieBinding", "//submodules/rlottie:RLottieBinding",
"//submodules/AppBundle:AppBundle", "//submodules/AppBundle:AppBundle",
"//submodules/ManagedAnimationNode:ManagedAnimationNode" "//submodules/ManagedAnimationNode:ManagedAnimationNode",
"//submodules/Components/HierarchyTrackingLayer",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -6,6 +6,7 @@ import SwiftSignalKit
import RLottieBinding import RLottieBinding
import GZip import GZip
import AppBundle import AppBundle
import HierarchyTrackingLayer
public enum SemanticStatusNodeState: Equatable { public enum SemanticStatusNodeState: Equatable {
public struct ProgressAppearance: 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 { private extension SemanticStatusNodeState {
func context(current: SemanticStatusNodeStateContext?) -> SemanticStatusNodeStateContext { func context(current: SemanticStatusNodeStateContext?, animated: Bool) -> SemanticStatusNodeStateContext {
switch self { switch self {
case .none, .download, .play, .pause, .customIcon: case .none, .download, .play, .pause, .customIcon:
let icon: SemanticStatusNodeIcon let icon: SemanticStatusNodeIcon
@ -114,7 +115,7 @@ private extension SemanticStatusNodeState {
if current.icon == icon { if current.icon == icon {
return current return current
} else if (current.icon == .play && icon == .pause) || (current.icon == .pause && icon == .play) { } else if (current.icon == .play && icon == .pause) || (current.icon == .pause && icon == .play) {
current.icon = icon current.setIcon(icon: icon, animated: animated)
return current return current
} else { } else {
return SemanticStatusNodeIconContext(icon: icon) return SemanticStatusNodeIconContext(icon: icon)
@ -376,6 +377,8 @@ public final class SemanticStatusNode: ASControlNode {
private var stateContext: SemanticStatusNodeStateContext private var stateContext: SemanticStatusNodeStateContext
private var appearanceContext: SemanticStatusNodeAppearanceContext private var appearanceContext: SemanticStatusNodeAppearanceContext
private let hierarchyTrackingLayer: HierarchyTrackingLayer
private var disposable: Disposable? private var disposable: Disposable?
private var backgroundNodeImage: UIImage? 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) { public init(backgroundNodeColor: UIColor, foregroundNodeColor: UIColor, image: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? = nil, overlayForegroundNodeColor: UIColor? = nil, cutout: CGRect? = nil) {
self.state = .none 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.appearanceContext = SemanticStatusNodeAppearanceContext(background: backgroundNodeColor, foreground: foregroundNodeColor, backgroundImage: nil, overlayForeground: overlayForegroundNodeColor, cutout: cutout)
self.hierarchyTrackingLayer = HierarchyTrackingLayer()
super.init() super.init()
self.layer.addSublayer(self.hierarchyTrackingLayer)
self.isOpaque = false self.isOpaque = false
self.displaysAsynchronously = true self.displaysAsynchronously = false
if let image { if let image {
self.setBackgroundImage(image, size: CGSize(width: 44.0, height: 44.0)) self.setBackgroundImage(image, size: CGSize(width: 44.0, height: 44.0))
@ -420,7 +426,6 @@ public final class SemanticStatusNode: ASControlNode {
animate = true animate = true
} }
} }
if self.stateContext.isAnimating { if self.stateContext.isAnimating {
animate = true animate = true
} }
@ -449,12 +454,15 @@ public final class SemanticStatusNode: ASControlNode {
self.hasState = true self.hasState = true
animated = false animated = false
} }
if !self.hierarchyTrackingLayer.isInHierarchy {
animated = false
}
if self.state != state || self.appearanceContext.cutout != cutout { if self.state != state || self.appearanceContext.cutout != cutout {
self.state = state self.state = state
let previousStateContext = self.stateContext let previousStateContext = self.stateContext
let previousAppearanceContext = updateCutout ? self.appearanceContext : nil 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.stateContext.requestUpdate = { [weak self] in
self?.setNeedsDisplay() self?.setNeedsDisplay()
} }

View File

@ -131,11 +131,7 @@ final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContext {
} }
} }
var icon: SemanticStatusNodeIcon { private(set) var icon: SemanticStatusNodeIcon
didSet {
self.animationNode?.enqueueState(self.icon == .play ? .play : .pause, animated: self.iconImage != nil)
}
}
private var animationNode: PlayPauseIconNode? private var animationNode: PlayPauseIconNode?
private var iconImage: UIImage? private var iconImage: UIImage?
@ -171,6 +167,11 @@ final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContext {
var requestUpdate: () -> Void = {} 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 { func drawingState(transitionFraction: CGFloat) -> SemanticStatusNodeStateDrawingState {
return DrawingState(transitionFraction: transitionFraction, icon: self.icon, iconImage: self.iconImage, iconOffset: self.iconOffset) return DrawingState(transitionFraction: transitionFraction, icon: self.icon, iconImage: self.iconImage, iconOffset: self.iconOffset)
} }

View File

@ -472,8 +472,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
self.actionButtonNode.setBackgroundImage(highlightedHalfRoundedBackground, for: .highlighted) self.actionButtonNode.setBackgroundImage(highlightedHalfRoundedBackground, for: .highlighted)
if let startAtTimestamp = mediaParameters?.startAtTimestamp { if let startAtTimestamp = mediaParameters?.startAtTimestamp {
//TODO:localize 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))
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))
} else { } else {
self.startAtTimestampNode = nil self.startAtTimestampNode = nil
} }

View File

@ -167,6 +167,14 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
self.conferenceAddParticipant?() 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( self.callScreenState = PrivateCallScreen.State(
strings: presentationData.strings, strings: presentationData.strings,
lifecycleState: .connecting, lifecycleState: .connecting,
@ -180,7 +188,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
remoteVideo: nil, remoteVideo: nil,
isRemoteBatteryLow: false, isRemoteBatteryLow: false,
isEnergySavingEnabled: !self.sharedContext.energyUsageSettings.fullTranslucency, isEnergySavingEnabled: !self.sharedContext.energyUsageSettings.fullTranslucency,
isConferencePossible: true isConferencePossible: isConferencePossible
) )
self.isMicrophoneMutedDisposable = (call.isMuted self.isMicrophoneMutedDisposable = (call.isMuted
@ -520,6 +528,12 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
} }
if var callScreenState = self.callScreenState { 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.lifecycleState = mappedLifecycleState
callScreenState.remoteVideo = self.remoteVideo callScreenState.remoteVideo = self.remoteVideo
callScreenState.localVideo = self.localVideo callScreenState.localVideo = self.localVideo

View File

@ -15,13 +15,14 @@ import AccountContext
import DeviceProximity import DeviceProximity
import PhoneNumberFormat import PhoneNumberFormat
final class SharedCallAudioContext { public final class SharedCallAudioContext {
let audioDevice: OngoingCallContext.AudioDevice? let audioDevice: OngoingCallContext.AudioDevice?
let callKitIntegration: CallKitIntegration? let callKitIntegration: CallKitIntegration?
private var audioSessionDisposable: Disposable? private var audioSessionDisposable: Disposable?
private var audioSessionShouldBeActiveDisposable: Disposable? private var audioSessionShouldBeActiveDisposable: Disposable?
private var isAudioSessionActiveDisposable: Disposable? private var isAudioSessionActiveDisposable: Disposable?
private var audioOutputStateDisposable: Disposable?
private(set) var audioSessionControl: ManagedAudioSessionControl? private(set) var audioSessionControl: ManagedAudioSessionControl?
@ -32,7 +33,7 @@ final class SharedCallAudioContext {
private let audioOutputStatePromise = Promise<([AudioSessionOutput], AudioSessionOutput?)>(([], nil)) private let audioOutputStatePromise = Promise<([AudioSessionOutput], AudioSessionOutput?)>(([], nil))
private var audioOutputStateValue: ([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 private var didSetCurrentAudioOutputValue: Bool = false
var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> {
return self.audioOutputStatePromise.get() return self.audioOutputStatePromise.get()
@ -141,12 +142,24 @@ final class SharedCallAudioContext {
} }
self.audioDevice?.setIsAudioSessionActive(value) 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 { deinit {
self.audioSessionDisposable?.dispose() self.audioSessionDisposable?.dispose()
self.audioSessionShouldBeActiveDisposable?.dispose() self.audioSessionShouldBeActiveDisposable?.dispose()
self.isAudioSessionActiveDisposable?.dispose() self.isAudioSessionActiveDisposable?.dispose()
self.audioOutputStateDisposable?.dispose()
} }
func setCurrentAudioOutput(_ output: AudioSessionOutput) { func setCurrentAudioOutput(_ output: AudioSessionOutput) {
@ -201,7 +214,7 @@ public final class PresentationCallImpl: PresentationCall {
private let currentNetworkType: NetworkType private let currentNetworkType: NetworkType
private let updatedNetworkType: Signal<NetworkType, NoError> private let updatedNetworkType: Signal<NetworkType, NoError>
private var sharedAudioContext: SharedCallAudioContext? public private(set) var sharedAudioContext: SharedCallAudioContext?
private var sessionState: CallSession? private var sessionState: CallSession?
private var callContextState: OngoingCallContextState? private var callContextState: OngoingCallContextState?
@ -1610,6 +1623,29 @@ public final class PresentationCallImpl: PresentationCall {
self.useFrontCamera = !self.useFrontCamera self.useFrontCamera = !self.useFrontCamera
self.videoCapturer?.switchVideoInput(isFront: 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? { func sampleBufferFromPixelBuffer(pixelBuffer: CVPixelBuffer) -> CMSampleBuffer? {

View File

@ -264,7 +264,6 @@ private final class AvatarUploadToastScreenComponent: Component {
containerSize: CGSize(width: availableContentSize.width - contentInsets.left - contentInsets.right - spacing - iconSize.width, height: availableContentSize.height) containerSize: CGSize(width: availableContentSize.width - contentInsets.left - contentInsets.right - spacing - iconSize.width, height: availableContentSize.height)
) )
//TODO:localize
let contentSize = self.content.update( let contentSize = self.content.update(
transition: transition, transition: transition,
component: AnyComponent(AnimatedTextComponent( component: AnyComponent(AnimatedTextComponent(

View File

@ -153,7 +153,6 @@ public final class BatchVideoRenderingContext {
for (id, targetContext) in self.targetContexts { for (id, targetContext) in self.targetContexts {
if targetContext.target != nil { if targetContext.target != nil {
if targetContext.fetchDisposable == nil { if targetContext.fetchDisposable == nil {
//TODO:release pass resource reference
targetContext.fetchDisposable = fetchedMediaResource( targetContext.fetchDisposable = fetchedMediaResource(
mediaBox: self.context.account.postbox.mediaBox, mediaBox: self.context.account.postbox.mediaBox,
userLocation: targetContext.userLocation, userLocation: targetContext.userLocation,

View File

@ -613,7 +613,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
if let previousParams = self.params, case .active = params.state.lifecycleState { if let previousParams = self.params, case .active = params.state.lifecycleState {
switch previousParams.state.lifecycleState { switch previousParams.state.lifecycleState {
case .requesting, .ringing, .connecting, .reconnecting: 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.displayEmojiTooltip = true
self.hideEmojiTooltipTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self] _ in self.hideEmojiTooltipTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self] _ in

View File

@ -43,6 +43,7 @@ swift_library(
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
"//submodules/AnimatedCountLabelNode", "//submodules/AnimatedCountLabelNode",
"//submodules/AudioWaveform", "//submodules/AudioWaveform",
"//submodules/DeviceProximity",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -34,6 +34,7 @@ import ChatMessageItemCommon
import TelegramStringFormatting import TelegramStringFormatting
import AnimatedCountLabelNode import AnimatedCountLabelNode
import AudioWaveform import AudioWaveform
import DeviceProximity
private struct FetchControls { private struct FetchControls {
let fetch: (Bool) -> Void let fetch: (Bool) -> Void
@ -1561,6 +1562,12 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
guard let arguments = self.arguments else { guard let arguments = self.arguments else {
return return
} }
var animated = animated
if DeviceProximityManager.shared().currentValue() {
animated = false
}
let incoming = message.effectivelyIncoming(context.account.peerId) let incoming = message.effectivelyIncoming(context.account.peerId)
let messageTheme = incoming ? presentationData.theme.theme.chat.message.incoming : presentationData.theme.theme.chat.message.outgoing let messageTheme = incoming ? presentationData.theme.theme.chat.message.incoming : presentationData.theme.theme.chat.message.outgoing

View File

@ -58,7 +58,10 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
openChatMessageMode = .automaticPlayback 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 self.interactiveImageNode.activateAgeRestrictedMedia = { [weak self] in

View File

@ -1794,11 +1794,10 @@ private final class ChatSendStarsScreenComponent: Component {
let titleSubtitleSpacing: CGFloat = 1.0 let titleSubtitleSpacing: CGFloat = 1.0
//TODO:localize
let subtitleSize = self.subtitle.update( let subtitleSize = self.subtitle.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextComponent( 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: {}, environment: {},
containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0) containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0)

View File

@ -303,6 +303,29 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
public var chatIsRotated: Bool = true public var chatIsRotated: Bool = true
public var canReadHistory: Bool = false 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<Bool, NoError>? {
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( public init(
openMessage: @escaping (Message, OpenMessageParams) -> Bool, openMessage: @escaping (Message, OpenMessageParams) -> Bool,
openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void,
@ -538,4 +561,8 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
self.presentationContext = presentationContext self.presentationContext = presentationContext
} }
deinit {
self.isOpeningMediaDisposable?.dispose()
}
} }

View File

@ -54,7 +54,10 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
} }
var accumulatedRightButtonOffset: CGFloat = canBeExpanded ? 16.0 : 0.0 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) button.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: canBeExpanded, transition: transition)
transition.updateSublayerTransformOffset(layer: button.layer, offset: CGPoint(x: accumulatedRightButtonOffset, y: 0.0)) transition.updateSublayerTransformOffset(layer: button.layer, offset: CGPoint(x: accumulatedRightButtonOffset, y: 0.0))
if self.backgroundContentColor.alpha != 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 { if self.currentRightButtons != rightButtons || presentationData.strings !== self.presentationData?.strings {
self.currentRightButtons = rightButtons self.currentRightButtons = rightButtons
@ -225,7 +229,10 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
buttonNode.alpha = 0.0 buttonNode.alpha = 0.0
transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) 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 { } else {
transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame) transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame)
transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor)

View File

@ -887,29 +887,33 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message, params in 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 return false
} }
let mode = params.mode let mode = params.mode
let displayVoiceMessageDiscardAlert: () -> Bool = { let displayVoiceMessageDiscardAlert: () -> Bool = { [weak self] in
if strongSelf.presentVoiceMessageDiscardAlert(action: { [weak self] in guard let self else {
if let strongSelf = self { return true
Queue.mainQueue().after(0.1, {
let _ = strongSelf.controllerInteraction?.openMessage(message, params)
})
} }
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) { }, performAction: false) {
return false return false
} }
return true return true
} }
strongSelf.commitPurposefulAction() self.commitPurposefulAction()
strongSelf.dismissAllTooltips() self.dismissAllTooltips()
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() self.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
var openMessageByAction = false var openMessageByAction = false
var isLocation = false var isLocation = false
@ -923,9 +927,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
if let file = media as? TelegramMediaFile { if let file = media as? TelegramMediaFile {
if file.isInstantVideo { if file.isInstantVideo {
if strongSelf.chatDisplayNode.isInputViewFocused { if self.chatDisplayNode.isInputViewFocused {
strongSelf.returnInputViewFocus = true self.returnInputViewFocus = true
strongSelf.chatDisplayNode.dismissInput() self.chatDisplayNode.dismissInput()
} }
} }
if file.isMusic || file.isVoice || file.isInstantVideo { 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 { if (file.isVoice || file.isInstantVideo) && message.minAutoremoveOrClearTimeout == viewOnceTimeout {
strongSelf.openViewOnceMediaMessage(message) self.openViewOnceMediaMessage(message)
return false return false
} }
} else if file.isVideo { } else if file.isVideo {
@ -947,7 +951,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch extendedMedia { switch extendedMedia {
case .preview: case .preview:
if displayVoiceMessageDiscardAlert() { if displayVoiceMessageDiscardAlert() {
strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id, params) self.controllerInteraction?.openCheckoutOrReceipt(message.id, params)
return true return true
} else { } else {
return false return false
@ -959,7 +963,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch extendedMedia { switch extendedMedia {
case .preview: case .preview:
if displayVoiceMessageDiscardAlert() { if displayVoiceMessageDiscardAlert() {
strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id, nil) self.controllerInteraction?.openCheckoutOrReceipt(message.id, nil)
return true return true
} else { } else {
return false return false
@ -969,15 +973,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} else if media is TelegramMediaGiveaway || media is TelegramMediaGiveawayResults { } else if media is TelegramMediaGiveaway || media is TelegramMediaGiveawayResults {
let progress = params.progress 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 let disposable: MetaDisposable
if let current = strongSelf.giveawayStatusDisposable { if let current = self.giveawayStatusDisposable {
disposable = current disposable = current
} else { } else {
disposable = MetaDisposable() disposable = MetaDisposable()
strongSelf.giveawayStatusDisposable = disposable self.giveawayStatusDisposable = disposable
} }
let progressSignal = Signal<Never, NoError> { [weak self] subscriber in let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
@ -1010,8 +1014,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
disposable.set((signal disposable.set((signal
|> deliverOnMainQueue).startStrict(next: { [weak self] info in |> deliverOnMainQueue).startStrict(next: { [weak self] info in
if let strongSelf = self, let info { if let self, let info {
strongSelf.displayGiveawayStatusInfo(messageId: message.id, giveawayInfo: 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: case .pinnedMessageUpdated, .gameScore, .setSameChatWallpaper, .giveawayResults, .customText:
for attribute in message.attributes { for attribute in message.attributes {
if let attribute = attribute as? ReplyMessageAttribute { 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 break
} }
} }
case let .photoUpdated(image): case let .photoUpdated(image):
openMessageByAction = image != nil openMessageByAction = image != nil
case .groupPhoneCall, .inviteToGroupPhoneCall: case .groupPhoneCall, .inviteToGroupPhoneCall:
if let activeCall = strongSelf.presentationInterfaceState.activeGroupCallInfo?.activeCall { if let activeCall = self.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)) 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 { } else {
var canManageGroupCalls = false 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) { if channel.flags.contains(.isCreator) || channel.hasPermission(.manageCalls) {
canManageGroupCalls = true 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 { if case .creator = group.role {
canManageGroupCalls = true canManageGroupCalls = true
} else if case let .admin(rights, _) = group.role { } else if case let .admin(rights, _) = group.role {
@ -1051,80 +1055,80 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if canManageGroupCalls { if canManageGroupCalls {
let text: String let text: String
if let channel = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramChannel, case .broadcast = channel.info { if let channel = self.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramChannel, case .broadcast = channel.info {
text = strongSelf.presentationData.strings.LiveStream_CreateNewVoiceChatText text = self.presentationData.strings.LiveStream_CreateNewVoiceChatText
} else { } 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: { 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 strongSelf = self { if let self {
var dismissStatus: (() -> Void)? 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?()
})) }))
dismissStatus = { [weak self, weak statusController] in dismissStatus = { [weak self, weak statusController] in
self?.createVoiceChatDisposable.set(nil) self?.createVoiceChatDisposable.set(nil)
statusController?.dismiss() statusController?.dismiss()
} }
strongSelf.present(statusController, in: .window(.root)) self.present(statusController, in: .window(.root))
strongSelf.createVoiceChatDisposable.set((strongSelf.context.engine.calls.createGroupCall(peerId: message.id.peerId, title: nil, scheduleDate: nil, isExternalStream: false) 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 |> deliverOnMainQueue).startStrict(next: { [weak self] info in
guard let strongSelf = self else { guard let self else {
return 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 }, error: { [weak self] error in
dismissStatus?() dismissStatus?()
guard let strongSelf = self else { guard let self else {
return return
} }
let text: String let text: String
switch error { switch error {
case .generic, .scheduledTooLate: case .generic, .scheduledTooLate:
text = strongSelf.presentationData.strings.Login_UnknownError text = self.presentationData.strings.Login_UnknownError
case .anonymousNotAllowed: case .anonymousNotAllowed:
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { 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 { } 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: { }, completed: {
dismissStatus?() dismissStatus?()
})) }))
} }
}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.VoiceChat_CreateNewVoiceChatSchedule, action: { }), TextAlertAction(type: .genericAction, title: self.presentationData.strings.VoiceChat_CreateNewVoiceChatSchedule, action: { [weak self] in
if let strongSelf = self { if let self {
strongSelf.context.scheduleGroupCall(peerId: message.id.peerId, parentController: strongSelf) 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 return true
case .messageAutoremoveTimeoutUpdated: case .messageAutoremoveTimeoutUpdated:
var canSetupAutoremoveTimeout = false var canSetupAutoremoveTimeout = false
if let _ = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat { if let _ = self.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat {
canSetupAutoremoveTimeout = false 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) { if !group.hasBannedPermission(.banChangeInfo) {
canSetupAutoremoveTimeout = true canSetupAutoremoveTimeout = true
} }
} else if let user = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramUser { } else if let user = self.presentationInterfaceState.renderedPeer?.peer as? TelegramUser {
if user.id != strongSelf.context.account.peerId && user.botInfo == nil { if user.id != self.context.account.peerId && user.botInfo == nil {
canSetupAutoremoveTimeout = true 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) { if channel.hasPermission(.changeInfo) {
canSetupAutoremoveTimeout = true canSetupAutoremoveTimeout = true
} }
} }
if canSetupAutoremoveTimeout { if canSetupAutoremoveTimeout {
strongSelf.presentAutoremoveSetup() self.presentAutoremoveSetup()
} }
case let .paymentSent(currency, _, _, _, _): case let .paymentSent(currency, _, _, _, _):
if currency == "XTR" { 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)) self.push(self.context.sharedContext.makeStarsReceiptScreen(context: self.context, receipt: receipt))
}) })
} else { } 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 return true
case .setChatTheme: case .setChatTheme:
strongSelf.presentThemeSelection() self.presentThemeSelection()
return true return true
case let .setChatWallpaper(wallpaper, _): case let .setChatWallpaper(wallpaper, _):
guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
return true return true
} }
if let peer = peer as? TelegramChannel { if let peer = peer as? TelegramChannel {
@ -1158,11 +1162,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
return true return true
} }
guard message.effectivelyIncoming(strongSelf.context.account.peerId), let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { guard message.effectivelyIncoming(self.context.account.peerId), let peer = self.presentationInterfaceState.renderedPeer?.peer else {
strongSelf.presentThemeSelection() self.presentThemeSelection()
return true return true
} }
strongSelf.chatDisplayNode.dismissInput() self.chatDisplayNode.dismissInput()
var options = WallpaperPresentationOptions() var options = WallpaperPresentationOptions()
var intensity: Int32? var intensity: Int32?
if let settings = wallpaper.settings { if let settings = wallpaper.settings {
@ -1176,7 +1180,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
intensity = settings.intensity 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 wallpaperPreviewController.apply = { [weak wallpaperPreviewController] entry, options, _, _, brightness, forBoth in
var settings: WallpaperSettings? var settings: WallpaperSettings?
if case let .wallpaper(wallpaper, _) = entry { 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) 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() |> deliverOnMainQueue).startStandalone()
Queue.mainQueue().after(0.1) { Queue.mainQueue().after(0.1) {
wallpaperPreviewController?.dismiss() wallpaperPreviewController?.dismiss()
} }
} }
strongSelf.push(wallpaperPreviewController) self.push(wallpaperPreviewController)
return true return true
case let .giftPremium(_, _, duration, _, _, _, _): case let .giftPremium(_, _, duration, _, _, _, _):
strongSelf.chatDisplayNode.dismissInput() self.chatDisplayNode.dismissInput()
let fromPeerId: PeerId = message.author?.id == strongSelf.context.account.peerId ? strongSelf.context.account.peerId : message.id.peerId let fromPeerId: PeerId = message.author?.id == self.context.account.peerId ? self.context.account.peerId : message.id.peerId
let toPeerId: PeerId = message.author?.id == strongSelf.context.account.peerId ? message.id.peerId : strongSelf.context.account.peerId let toPeerId: PeerId = message.author?.id == self.context.account.peerId ? message.id.peerId : self.context.account.peerId
let controller = PremiumIntroScreen(context: strongSelf.context, source: .gift(from: fromPeerId, to: toPeerId, duration: duration, giftCode: nil)) let controller = PremiumIntroScreen(context: self.context, source: .gift(from: fromPeerId, to: toPeerId, duration: duration, giftCode: nil))
strongSelf.push(controller) self.push(controller)
return true return true
case .starGift, .starGiftUnique: case .starGift, .starGiftUnique:
let controller = strongSelf.context.sharedContext.makeGiftViewScreen(context: strongSelf.context, message: EngineMessage(message), shareStory: { [weak self] uniqueGift in let controller = self.context.sharedContext.makeGiftViewScreen(context: self.context, message: EngineMessage(message), shareStory: { [weak self] uniqueGift in
if let self {
Queue.mainQueue().after(0.15) { Queue.mainQueue().after(0.15) {
if let self {
let controller = self.context.sharedContext.makeStorySharingScreen(context: self.context, subject: .gift(uniqueGift), parentController: self) let controller = self.context.sharedContext.makeStorySharingScreen(context: self.context, subject: .gift(uniqueGift), parentController: self)
self.push(controller) self.push(controller)
} }
} }
}) })
strongSelf.push(controller) self.push(controller)
return true return true
case .giftStars: case .giftStars:
let controller = strongSelf.context.sharedContext.makeStarsGiftScreen(context: strongSelf.context, message: EngineMessage(message)) let controller = self.context.sharedContext.makeStarsGiftScreen(context: self.context, message: EngineMessage(message))
strongSelf.push(controller) self.push(controller)
return true return true
case let .giftCode(slug, _, _, _, _, _, _, _, _, _, _): 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 return true
case .prizeStars: case .prizeStars:
let controller = strongSelf.context.sharedContext.makeStarsGiftScreen(context: strongSelf.context, message: EngineMessage(message)) let controller = self.context.sharedContext.makeStarsGiftScreen(context: self.context, message: EngineMessage(message))
strongSelf.push(controller) self.push(controller)
return true return true
case let .suggestedProfilePhoto(image): case let .suggestedProfilePhoto(image):
strongSelf.chatDisplayNode.dismissInput() self.chatDisplayNode.dismissInput()
if let image = image { if let image = image {
if message.effectivelyIncoming(strongSelf.context.account.peerId) { if message.effectivelyIncoming(self.context.account.peerId) {
if let emojiMarkup = image.emojiMarkup { 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 controller.imageCompletion = { [weak self] image, commit in
if let strongSelf = self { if let self {
if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl { if let rootController = self.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl {
settingsController.updateProfilePhoto(image, mode: .accept, uploadStatus: nil) settingsController.updateProfilePhoto(image, mode: .accept, uploadStatus: nil)
commit() commit()
} }
} }
} }
controller.videoCompletion = { [weak self] image, url, values, markup, commit in controller.videoCompletion = { [weak self] image, url, values, markup, commit in
if let strongSelf = self { if let self {
if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl { 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) settingsController.updateProfileVideo(image, video: nil, values: nil, markup: markup, mode: .accept, uploadStatus: nil)
commit() commit()
} }
} }
} }
strongSelf.push(controller) self.push(controller)
} else { } else {
var selectedNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? 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 itemNode = itemNode as? ChatMessageItemView {
if let result = itemNode.transitionNode(id: message.id, media: image, adjustRect: false) { if let result = itemNode.transitionNode(id: message.id, media: image, adjustRect: false) {
selectedNode = result selectedNode = result
@ -1267,17 +1271,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
senderName = nil 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) self?.present(c, in: .window(.root), with: a)
}, imageCompletion: { [weak self] image in }, imageCompletion: { [weak self] image in
if let strongSelf = self { if let self {
if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl { if let rootController = self.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl {
settingsController.updateProfilePhoto(image, mode: .accept, uploadStatus: nil) settingsController.updateProfilePhoto(image, mode: .accept, uploadStatus: nil)
} }
} }
}, videoCompletion: { [weak self] image, url, adjustments in }, videoCompletion: { [weak self] image, url, adjustments in
if let strongSelf = self { if let self {
if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl { if let rootController = self.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl {
settingsController.oldUpdateProfileVideo(image, asset: AVURLAsset(url: url), adjustments: adjustments, mode: .accept) settingsController.oldUpdateProfileVideo(image, asset: AVURLAsset(url: url), adjustments: adjustments, mode: .accept)
} }
} }
@ -1288,7 +1292,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
case .boostsApplied: case .boostsApplied:
strongSelf.controllerInteraction?.openGroupBoostInfo(nil, 0) self.controllerInteraction?.openGroupBoostInfo(nil, 0)
return true return true
default: default:
break break
@ -1299,29 +1303,33 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
let openChatLocation = strongSelf.chatLocation let openChatLocation = self.chatLocation
var chatFilterTag: MemoryBuffer? var chatFilterTag: MemoryBuffer?
if case let .customTag(value, _) = strongSelf.chatDisplayNode.historyNode.tag { if case let .customTag(value, _) = self.chatDisplayNode.historyNode.tag {
chatFilterTag = value chatFilterTag = value
} }
var standalone = false var standalone = false
if case .customChatContents = strongSelf.chatLocation { if case .customChatContents = self.chatLocation {
standalone = true standalone = true
} }
if let adAttribute = message.attributes.first(where: { $0 is AdMessageAttribute }) as? AdMessageAttribute { 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 { 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 { } else {
strongSelf.controllerInteraction?.activateAdAction(message.id, nil, true, false) self.controllerInteraction?.activateAdAction(message.id, nil, true, false)
return true 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() self?.chatDisplayNode.dismissInput()
}, present: { c, a, i in }, present: { [weak self] c, a, i in
guard let self else {
return
}
if case .current = i { if case .current = i {
c.presentationArguments = a c.presentationArguments = a
c.statusBar.alphaUpdated = { [weak self] transition in c.statusBar.alphaUpdated = { [weak self] transition in
@ -1330,14 +1338,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
self.updateStatusBarPresentation(animated: transition.isAnimated) 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 { } 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?))? var selectedNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
if let strongSelf = self { if let self {
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView { if let itemNode = itemNode as? ChatMessageItemView {
if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: adjustRect) { if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: adjustRect) {
selectedNode = result selectedNode = result
@ -1346,27 +1354,27 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
return selectedNode return selectedNode
}, addToTransitionSurface: { view in }, addToTransitionSurface: { [weak self] view in
guard let strongSelf = self else { guard let self else {
return return
} }
strongSelf.chatDisplayNode.historyNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.chatDisplayNode.historyNode.view) self.chatDisplayNode.historyNode.view.superview?.insertSubview(view, aboveSubview: self.chatDisplayNode.historyNode.view)
}, openUrl: { url in }, openUrl: { [weak self] url in
self?.openUrl(url, concealed: false, skipConcealedAlert: isLocation, message: nil) 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) self?.openPeer(peer: EnginePeer(peer), navigation: navigation, fromMessage: nil)
}, callPeer: { peerId, isVideo in }, callPeer: { [weak self] peerId, isVideo in
self?.controllerInteraction?.callPeer(peerId, isVideo) self?.controllerInteraction?.callPeer(peerId, isVideo)
}, enqueueMessage: { message in }, enqueueMessage: { [weak self] message in
self?.sendMessages([message]) 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 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) self?.controllerInteraction?.sendEmoji(text, attribute, false)
} : nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in } : nil, setupTemporaryHiddenMedia: { [weak self] signal, centralIndex, galleryMedia in
if let strongSelf = self { if let self {
strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { entry in self.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { [weak self] entry in
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { if let self, let controllerInteraction = self.controllerInteraction {
var messageIdAndMedia: [MessageId: [Media]] = [:] var messageIdAndMedia: [MessageId: [Media]] = [:]
if let entry = entry as? InstantPageGalleryEntry, entry.index == centralIndex { if let entry = entry as? InstantPageGalleryEntry, entry.index == centralIndex {
@ -1375,7 +1383,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
controllerInteraction.hiddenMedia = messageIdAndMedia controllerInteraction.hiddenMedia = messageIdAndMedia
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView { if let itemNode = itemNode as? ChatMessageItemView {
itemNode.updateHiddenMedia() itemNode.updateHiddenMedia()
} }
@ -1383,10 +1391,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
})) }))
} }
}, chatAvatarHiddenMedia: { signal, media in }, chatAvatarHiddenMedia: { [weak self] signal, media in
if let strongSelf = self { if let self {
strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { messageId in self.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { [weak self] messageId in
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { if let self, let controllerInteraction = self.controllerInteraction {
var messageIdAndMedia: [MessageId: [Media]] = [:] var messageIdAndMedia: [MessageId: [Media]] = [:]
if let messageId = messageId { if let messageId = messageId {
@ -1395,7 +1403,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
controllerInteraction.hiddenMedia = messageIdAndMedia controllerInteraction.hiddenMedia = messageIdAndMedia
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView { if let itemNode = itemNode as? ChatMessageItemView {
itemNode.updateHiddenMedia() itemNode.updateHiddenMedia()
} }
@ -1405,54 +1413,54 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
}, actionInteraction: GalleryControllerActionInteraction( }, actionInteraction: GalleryControllerActionInteraction(
openUrl: { [weak self] url, concealed in openUrl: { [weak self] url, concealed in
if let strongSelf = self { if let self {
strongSelf.openUrl(url, concealed: concealed, message: nil) self.openUrl(url, concealed: concealed, message: nil)
} }
}, openUrlIn: { [weak self] url in }, openUrlIn: { [weak self] url in
if let strongSelf = self { if let self {
strongSelf.openUrlIn(url) self.openUrlIn(url)
} }
}, openPeerMention: { [weak self] mention in }, openPeerMention: { [weak self] mention in
if let strongSelf = self { if let self {
strongSelf.controllerInteraction?.openPeerMention(mention, nil) self.controllerInteraction?.openPeerMention(mention, nil)
} }
}, openPeer: { [weak self] peer in }, openPeer: { [weak self] peer in
if let strongSelf = self { if let self {
strongSelf.controllerInteraction?.openPeer(peer, .default, nil, .default) self.controllerInteraction?.openPeer(peer, .default, nil, .default)
} }
}, openHashtag: { [weak self] peerName, hashtag in }, openHashtag: { [weak self] peerName, hashtag in
if let strongSelf = self { if let self {
strongSelf.controllerInteraction?.openHashtag(peerName, hashtag) self.controllerInteraction?.openHashtag(peerName, hashtag)
} }
}, openBotCommand: { [weak self] command in }, openBotCommand: { [weak self] command in
if let strongSelf = self { if let self {
strongSelf.controllerInteraction?.sendBotCommand(nil, command) self.controllerInteraction?.sendBotCommand(nil, command)
} }
}, openAd: { [weak self] messageId in }, openAd: { [weak self] messageId in
if let strongSelf = self { if let self {
strongSelf.controllerInteraction?.activateAdAction(messageId, nil, true, true) self.controllerInteraction?.activateAdAction(messageId, nil, true, true)
} }
}, addContact: { [weak self] phoneNumber in }, addContact: { [weak self] phoneNumber in
if let strongSelf = self { if let self {
strongSelf.controllerInteraction?.addContact(phoneNumber) self.controllerInteraction?.addContact(phoneNumber)
} }
}, storeMediaPlaybackState: { [weak self] messageId, timestamp, playbackRate in }, storeMediaPlaybackState: { [weak self] messageId, timestamp, playbackRate in
guard let strongSelf = self else { guard let self else {
return return
} }
var storedState: MediaPlaybackStoredState? var storedState: MediaPlaybackStoredState?
if let timestamp = timestamp { if let timestamp = timestamp {
storedState = MediaPlaybackStoredState(timestamp: timestamp, playbackRate: AudioPlaybackRate(playbackRate)) 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 }, editMedia: { [weak self] messageId, snapshots, transitionCompletion in
guard let strongSelf = self else { guard let self else {
return 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 |> deliverOnMainQueue).startStandalone(next: { [weak self] message in
guard let strongSelf = self, let message = message else { guard let self, let message = message else {
return return
} }
@ -1472,17 +1480,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] { 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() transitionCompletion()
}, getCaptionPanelView: { [weak self] in }, getCaptionPanelView: { [weak self] in
return self?.getCaptionPanelView(isFile: false) return self?.getCaptionPanelView(isFile: false)
}, sendMessagesWithSignals: { [weak self] signals, _, _, isCaptionAbove in }, sendMessagesWithSignals: { [weak self] signals, _, _, isCaptionAbove in
if let strongSelf = self { if let self {
var parameters: ChatSendMessageActionSheetController.SendParameters? var parameters: ChatSendMessageActionSheetController.SendParameters?
if isCaptionAbove { if isCaptionAbove {
parameters = ChatSendMessageActionSheetController.SendParameters(effect: nil, textIsAboveMedia: true) 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 }, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a) self?.present(c, in: .window(.root), with: a)
@ -1493,18 +1501,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self?.canReadHistory.set(canReadHistory) self?.canReadHistory.set(canReadHistory)
}), }),
getSourceRect: { [weak self] in getSourceRect: { [weak self] in
guard let strongSelf = self else { guard let self else {
return nil return nil
} }
var rect: CGRect? var rect: CGRect?
strongSelf.chatDisplayNode.historyNode.forEachVisibleMessageItemNode({ itemNode in self.chatDisplayNode.historyNode.forEachVisibleMessageItemNode({ itemNode in
if itemNode.item?.message.id == message.id { if itemNode.item?.message.id == message.id {
rect = itemNode.view.convert(itemNode.contentFrame(), to: nil) rect = itemNode.view.convert(itemNode.contentFrame(), to: nil)
} }
}) })
return rect return rect
} }
)) )
self.controllerInteraction?.isOpeningMediaSignal = openChatMessageParams.blockInteraction.get()
return context.sharedContext.openChatMessage(openChatMessageParams)
}, openPeer: { [weak self] peer, navigation, fromMessage, source in }, openPeer: { [weak self] peer, navigation, fromMessage, source in
var expandAvatar = false var expandAvatar = false
if case let .groupParticipant(storyStats, avatarHeaderNode) = source { if case let .groupParticipant(storyStats, avatarHeaderNode) = source {

View File

@ -323,8 +323,12 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
return true return true
} }
params.blockInteraction.set(.single(true))
let _ = (gallery let _ = (gallery
|> deliverOnMainQueue).startStandalone(next: { gallery in |> deliverOnMainQueue).startStandalone(next: { gallery in
params.blockInteraction.set(.single(false))
gallery.centralItemUpdated = { messageId in gallery.centralItemUpdated = { messageId in
params.centralItemUpdated?(messageId) params.centralItemUpdated?(messageId)
} }

View File

@ -32,7 +32,6 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var knockoutWallpaper: Bool public var knockoutWallpaper: Bool
public var foldersTabAtBottom: Bool public var foldersTabAtBottom: Bool
public var playerEmbedding: Bool public var playerEmbedding: Bool
public var playlistPlayback: Bool
public var preferredVideoCodec: String? public var preferredVideoCodec: String?
public var disableVideoAspectScaling: Bool public var disableVideoAspectScaling: Bool
public var enableVoipTcp: Bool public var enableVoipTcp: Bool
@ -65,6 +64,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var playerV2: Bool public var playerV2: Bool
public var devRequests: Bool public var devRequests: Bool
public var fakeAds: Bool public var fakeAds: Bool
public var conferenceDebug: Bool
public static var defaultSettings: ExperimentalUISettings { public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings( return ExperimentalUISettings(
@ -75,7 +75,6 @@ public struct ExperimentalUISettings: Codable, Equatable {
knockoutWallpaper: false, knockoutWallpaper: false,
foldersTabAtBottom: false, foldersTabAtBottom: false,
playerEmbedding: false, playerEmbedding: false,
playlistPlayback: false,
preferredVideoCodec: nil, preferredVideoCodec: nil,
disableVideoAspectScaling: false, disableVideoAspectScaling: false,
enableVoipTcp: false, enableVoipTcp: false,
@ -107,7 +106,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
autoBenchmarkReflectors: nil, autoBenchmarkReflectors: nil,
playerV2: false, playerV2: false,
devRequests: false, devRequests: false,
fakeAds: false fakeAds: false,
conferenceDebug: false
) )
} }
@ -119,7 +119,6 @@ public struct ExperimentalUISettings: Codable, Equatable {
knockoutWallpaper: Bool, knockoutWallpaper: Bool,
foldersTabAtBottom: Bool, foldersTabAtBottom: Bool,
playerEmbedding: Bool, playerEmbedding: Bool,
playlistPlayback: Bool,
preferredVideoCodec: String?, preferredVideoCodec: String?,
disableVideoAspectScaling: Bool, disableVideoAspectScaling: Bool,
enableVoipTcp: Bool, enableVoipTcp: Bool,
@ -151,7 +150,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
autoBenchmarkReflectors: Bool?, autoBenchmarkReflectors: Bool?,
playerV2: Bool, playerV2: Bool,
devRequests: Bool, devRequests: Bool,
fakeAds: Bool fakeAds: Bool,
conferenceDebug: Bool
) { ) {
self.keepChatNavigationStack = keepChatNavigationStack self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory self.skipReadHistory = skipReadHistory
@ -160,7 +160,6 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.knockoutWallpaper = knockoutWallpaper self.knockoutWallpaper = knockoutWallpaper
self.foldersTabAtBottom = foldersTabAtBottom self.foldersTabAtBottom = foldersTabAtBottom
self.playerEmbedding = playerEmbedding self.playerEmbedding = playerEmbedding
self.playlistPlayback = playlistPlayback
self.preferredVideoCodec = preferredVideoCodec self.preferredVideoCodec = preferredVideoCodec
self.disableVideoAspectScaling = disableVideoAspectScaling self.disableVideoAspectScaling = disableVideoAspectScaling
self.enableVoipTcp = enableVoipTcp self.enableVoipTcp = enableVoipTcp
@ -193,6 +192,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.playerV2 = playerV2 self.playerV2 = playerV2
self.devRequests = devRequests self.devRequests = devRequests
self.fakeAds = fakeAds self.fakeAds = fakeAds
self.conferenceDebug = conferenceDebug
} }
public init(from decoder: Decoder) throws { 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.knockoutWallpaper = (try container.decodeIfPresent(Int32.self, forKey: "knockoutWallpaper") ?? 0) != 0
self.foldersTabAtBottom = (try container.decodeIfPresent(Int32.self, forKey: "foldersTabAtBottom") ?? 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.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.preferredVideoCodec = try container.decodeIfPresent(String.self.self, forKey: "preferredVideoCodec")
self.disableVideoAspectScaling = (try container.decodeIfPresent(Int32.self, forKey: "disableVideoAspectScaling") ?? 0) != 0 self.disableVideoAspectScaling = (try container.decodeIfPresent(Int32.self, forKey: "disableVideoAspectScaling") ?? 0) != 0
self.enableVoipTcp = (try container.decodeIfPresent(Int32.self, forKey: "enableVoipTcp") ?? 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.playerV2 = try container.decodeIfPresent(Bool.self, forKey: "playerV2") ?? false
self.devRequests = try container.decodeIfPresent(Bool.self, forKey: "devRequests") ?? false self.devRequests = try container.decodeIfPresent(Bool.self, forKey: "devRequests") ?? false
self.fakeAds = try container.decodeIfPresent(Bool.self, forKey: "fakeAds") ?? 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 { 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.knockoutWallpaper ? 1 : 0) as Int32, forKey: "knockoutWallpaper")
try container.encode((self.foldersTabAtBottom ? 1 : 0) as Int32, forKey: "foldersTabAtBottom") 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.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.encodeIfPresent(self.preferredVideoCodec, forKey: "preferredVideoCodec")
try container.encode((self.disableVideoAspectScaling ? 1 : 0) as Int32, forKey: "disableVideoAspectScaling") try container.encode((self.disableVideoAspectScaling ? 1 : 0) as Int32, forKey: "disableVideoAspectScaling")
try container.encode((self.enableVoipTcp ? 1 : 0) as Int32, forKey: "enableVoipTcp") 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.playerV2, forKey: "playerV2")
try container.encodeIfPresent(self.devRequests, forKey: "devRequests") try container.encodeIfPresent(self.devRequests, forKey: "devRequests")
try container.encodeIfPresent(self.fakeAds, forKey: "fakeAds") try container.encodeIfPresent(self.fakeAds, forKey: "fakeAds")
try container.encodeIfPresent(self.conferenceDebug, forKey: "conferenceDebug")
} }
} }