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.TransferToChannelYou" = "You transferred a unique collectible to %@";
"Gift.Convert.Success.ChannelText" = "**%1$@** were sent to channel's balance.";
"Gift.Convert.Success.ChannelText.Stars_1" = "%@ Star";
"Gift.Convert.Success.ChannelText.Stars_any" = "%@ Stars";
"Stars.Transfer.Terms" = "By purchasing you agree to the [Terms of Service]().";
"Stars.Transfer.Terms_URL" = "https://telegram.org/tos/stars";
"AvatarEditor.PremiumNeeded.Background" = "Subscribe to Telegram Premium to choose this background.";
"AvatarEditor.PremiumNeeded.Emoji" = "Subscribe to Telegram Premium to choose this emoji.";
"AvatarEditor.PremiumNeeded.Sticker" = "Subscribe to Telegram Premium to choose this sticker.";
@ -13981,3 +13988,7 @@ Sorry for the inconvenience.";
"Privacy.Review.Invite.Title" = "Invitation Settings";
"Privacy.Review.Invite.Text" = "You've restricted who can message you, but anyone can still invite you to groups and channels. Would you like to review these settings?";
"Conversation.VideoTimeLinkCopied" = "Link with start time at %@ copied to clipboard.";
"Share.VideoStartAt" = "Start at %@";
"SendStarReactions.SubtitleFrom" = "from %@";

View File

@ -1 +1 @@
2510
2515

View File

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

View File

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

View File

@ -100,7 +100,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case enableReactionOverrides(Bool)
case storiesExperiment(Bool)
case storiesJpegExperiment(Bool)
case playlistPlayback(Bool)
case conferenceDebug(Bool)
case enableQuickReactionSwitch(Bool)
case disableReloginTokens(Bool)
case liveStreamV2(Bool)
@ -133,7 +133,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.web.rawValue
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
return DebugControllerSection.experiments.rawValue
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .fakeAds, .enableLocalTranslation:
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .conferenceDebug, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .fakeAds, .enableLocalTranslation:
return DebugControllerSection.experiments.rawValue
case .logTranslationRecognition, .resetTranslationStates:
return DebugControllerSection.translation.rawValue
@ -242,7 +242,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 47
case .disableReloginTokens:
return 48
case .playlistPlayback:
case .conferenceDebug:
return 49
case .enableQuickReactionSwitch:
return 50
@ -1308,12 +1308,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .playlistPlayback(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Playlist Playback", value: value, sectionId: self.section, style: .blocks, updated: { value in
case let .conferenceDebug(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Conference Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.playlistPlayback = value
settings.conferenceDebug = value
return PreferencesEntry(settings)
})
}).start()
@ -1540,7 +1540,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.storiesJpegExperiment(experimentalSettings.storiesJpegExperiment))
entries.append(.disableReloginTokens(experimentalSettings.disableReloginTokens))
}
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
entries.append(.conferenceDebug(experimentalSettings.conferenceDebug))
entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction))
entries.append(.liveStreamV2(experimentalSettings.liveStreamV2))
entries.append(.experimentalCallMute(experimentalSettings.experimentalCallMute))

View File

@ -1755,7 +1755,6 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let text: String
if let timestamp {
//TODO:localize
let startTimeString: String
let hours = timestamp / (60 * 60)
let minutes = timestamp % (60 * 60) / 60
@ -1765,7 +1764,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
} else {
startTimeString = String(format: "%d:%02d", minutes, seconds)
}
text = "Link with start time at \(startTimeString) copied to clipboard."
text = presentationData.strings.Conversation_VideoTimeLinkCopied(startTimeString).string
} else {
text = presentationData.strings.Conversation_LinkCopied
}

View File

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

View File

@ -524,6 +524,24 @@ private func chatMessageVideoDatas(postbox: Postbox, userLocation: MediaResource
thumbnail = .single(decodedThumbnailData)
}
} 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
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
@ -535,6 +553,7 @@ private func chatMessageVideoDatas(postbox: Postbox, userLocation: MediaResource
thumbnailDisposable.dispose()
}
}
}
} else {
thumbnail = .single(nil)
}

View File

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

View File

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

View File

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

View File

@ -472,8 +472,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
self.actionButtonNode.setBackgroundImage(highlightedHalfRoundedBackground, for: .highlighted)
if let startAtTimestamp = mediaParameters?.startAtTimestamp {
//TODO:localize
self.startAtTimestampNode = ShareStartAtTimestampNode(titleText: "Start at \(textForTimeout(value: startAtTimestamp))", titleTextColor: self.presentationData.theme.actionSheet.secondaryTextColor, checkNodeTheme: CheckNodeTheme(backgroundColor: presentationData.theme.list.itemCheckColors.fillColor, strokeColor: presentationData.theme.list.itemCheckColors.foregroundColor, borderColor: presentationData.theme.list.itemCheckColors.strokeColor, overlayBorder: false, hasInset: false, hasShadow: false))
self.startAtTimestampNode = ShareStartAtTimestampNode(titleText: self.presentationData.strings.Share_VideoStartAt(textForTimeout(value: startAtTimestamp)).string, titleTextColor: self.presentationData.theme.actionSheet.secondaryTextColor, checkNodeTheme: CheckNodeTheme(backgroundColor: presentationData.theme.list.itemCheckColors.fillColor, strokeColor: presentationData.theme.list.itemCheckColors.foregroundColor, borderColor: presentationData.theme.list.itemCheckColors.strokeColor, overlayBorder: false, hasInset: false, hasShadow: false))
} else {
self.startAtTimestampNode = nil
}

View File

@ -167,6 +167,14 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
self.conferenceAddParticipant?()
}
var isConferencePossible = false
if self.call.context.sharedContext.immediateExperimentalUISettings.conferenceDebug {
isConferencePossible = true
}
if let data = self.call.context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_enable_conference"] as? Double {
isConferencePossible = value != 0.0
}
self.callScreenState = PrivateCallScreen.State(
strings: presentationData.strings,
lifecycleState: .connecting,
@ -180,7 +188,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
remoteVideo: nil,
isRemoteBatteryLow: false,
isEnergySavingEnabled: !self.sharedContext.energyUsageSettings.fullTranslucency,
isConferencePossible: true
isConferencePossible: isConferencePossible
)
self.isMicrophoneMutedDisposable = (call.isMuted
@ -520,6 +528,12 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
}
if var callScreenState = self.callScreenState {
if callScreenState.remoteVideo == nil && self.remoteVideo != nil {
if let call = self.call as? PresentationCallImpl, let sharedAudioContext = call.sharedAudioContext, case .builtin = sharedAudioContext.currentAudioOutputValue {
call.playRemoteCameraTone()
}
}
callScreenState.lifecycleState = mappedLifecycleState
callScreenState.remoteVideo = self.remoteVideo
callScreenState.localVideo = self.localVideo

View File

@ -15,13 +15,14 @@ import AccountContext
import DeviceProximity
import PhoneNumberFormat
final class SharedCallAudioContext {
public final class SharedCallAudioContext {
let audioDevice: OngoingCallContext.AudioDevice?
let callKitIntegration: CallKitIntegration?
private var audioSessionDisposable: Disposable?
private var audioSessionShouldBeActiveDisposable: Disposable?
private var isAudioSessionActiveDisposable: Disposable?
private var audioOutputStateDisposable: Disposable?
private(set) var audioSessionControl: ManagedAudioSessionControl?
@ -32,7 +33,7 @@ final class SharedCallAudioContext {
private let audioOutputStatePromise = Promise<([AudioSessionOutput], AudioSessionOutput?)>(([], nil))
private var audioOutputStateValue: ([AudioSessionOutput], AudioSessionOutput?) = ([], nil)
private var currentAudioOutputValue: AudioSessionOutput = .builtin
public private(set) var currentAudioOutputValue: AudioSessionOutput = .builtin
private var didSetCurrentAudioOutputValue: Bool = false
var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> {
return self.audioOutputStatePromise.get()
@ -141,12 +142,24 @@ final class SharedCallAudioContext {
}
self.audioDevice?.setIsAudioSessionActive(value)
})
self.audioOutputStateDisposable = (self.audioOutputStatePromise.get()
|> deliverOnMainQueue).start(next: { [weak self] value in
guard let self else {
return
}
self.audioOutputStateValue = value
if let currentOutput = value.1 {
self.currentAudioOutputValue = currentOutput
}
})
}
deinit {
self.audioSessionDisposable?.dispose()
self.audioSessionShouldBeActiveDisposable?.dispose()
self.isAudioSessionActiveDisposable?.dispose()
self.audioOutputStateDisposable?.dispose()
}
func setCurrentAudioOutput(_ output: AudioSessionOutput) {
@ -201,7 +214,7 @@ public final class PresentationCallImpl: PresentationCall {
private let currentNetworkType: NetworkType
private let updatedNetworkType: Signal<NetworkType, NoError>
private var sharedAudioContext: SharedCallAudioContext?
public private(set) var sharedAudioContext: SharedCallAudioContext?
private var sessionState: CallSession?
private var callContextState: OngoingCallContextState?
@ -1610,6 +1623,29 @@ public final class PresentationCallImpl: PresentationCall {
self.useFrontCamera = !self.useFrontCamera
self.videoCapturer?.switchVideoInput(isFront: self.useFrontCamera)
}
public func playRemoteCameraTone() {
let name: String
name = "voip_group_recording_started.mp3"
self.beginTone(tone: .custom(name: name, loopCount: 1))
}
private func beginTone(tone: PresentationCallTone?) {
if let tone, let toneData = presentationCallToneData(tone) {
if let sharedAudioContext = self.sharedAudioContext {
sharedAudioContext.audioDevice?.setTone(tone: OngoingCallContext.Tone(
samples: toneData,
sampleRate: 48000,
loopCount: tone.loopCount ?? 100000
))
}
} else {
if let sharedAudioContext = self.sharedAudioContext {
sharedAudioContext.audioDevice?.setTone(tone: nil)
}
}
}
}
func sampleBufferFromPixelBuffer(pixelBuffer: CVPixelBuffer) -> CMSampleBuffer? {

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)
)
//TODO:localize
let contentSize = self.content.update(
transition: transition,
component: AnyComponent(AnimatedTextComponent(

View File

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

View File

@ -613,7 +613,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
if let previousParams = self.params, case .active = params.state.lifecycleState {
switch previousParams.state.lifecycleState {
case .requesting, .ringing, .connecting, .reconnecting:
if self.hideEmojiTooltipTimer == nil && !self.areControlsHidden {
if self.hideEmojiTooltipTimer == nil && !self.areControlsHidden && self.activeRemoteVideoSource == nil && self.activeLocalVideoSource == nil {
self.displayEmojiTooltip = true
self.hideEmojiTooltipTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self] _ in

View File

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

View File

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

View File

@ -58,7 +58,10 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
openChatMessageMode = .automaticPlayback
}
let _ = item.controllerInteraction.openMessage(item.message, OpenMessageParams(mode: openChatMessageMode, mediaIndex: self.mediaIndex, progress: self.itemNode?.makeProgress()))
if !item.controllerInteraction.isOpeningMedia {
let params = OpenMessageParams(mode: openChatMessageMode, mediaIndex: self.mediaIndex, progress: self.itemNode?.makeProgress())
let _ = item.controllerInteraction.openMessage(item.message, params)
}
}
self.interactiveImageNode.activateAgeRestrictedMedia = { [weak self] in

View File

@ -1794,11 +1794,10 @@ private final class ChatSendStarsScreenComponent: Component {
let titleSubtitleSpacing: CGFloat = 1.0
//TODO:localize
let subtitleSize = self.subtitle.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "from \(currentMyPeer.compactDisplayTitle)", font: Font.regular(12.0), textColor: environment.theme.list.itemSecondaryTextColor))
text: .plain(NSAttributedString(string: environment.strings.SendStarReactions_SubtitleFrom(currentMyPeer.compactDisplayTitle).string, font: Font.regular(12.0), textColor: environment.theme.list.itemSecondaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0)

View File

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

View File

@ -54,7 +54,10 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
}
var accumulatedRightButtonOffset: CGFloat = canBeExpanded ? 16.0 : 0.0
for (_, button) in self.rightButtonNodes {
for spec in self.currentRightButtons.reversed() {
guard let button = self.rightButtonNodes[spec.key] else {
continue
}
button.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: canBeExpanded, transition: transition)
transition.updateSublayerTransformOffset(layer: button.layer, offset: CGPoint(x: accumulatedRightButtonOffset, y: 0.0))
if self.backgroundContentColor.alpha != 0.0 {
@ -174,6 +177,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
}
}
var accumulatedRightButtonOffset: CGFloat = self.canBeExpanded ? 16.0 : 0.0
if self.currentRightButtons != rightButtons || presentationData.strings !== self.presentationData?.strings {
self.currentRightButtons = rightButtons
@ -225,7 +229,10 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
buttonNode.alpha = 0.0
transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor)
transition.updateSublayerTransformOffset(layer: buttonNode.layer, offset: CGPoint(x: canBeExpanded ? 16.0 : 0.0, y: 0.0))
transition.updateSublayerTransformOffset(layer: buttonNode.layer, offset: CGPoint(x: accumulatedRightButtonOffset, y: 0.0))
if self.backgroundContentColor.alpha != 0.0 {
accumulatedRightButtonOffset -= 6.0
}
} else {
transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame)
transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor)

View File

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

View File

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

View File

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