mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-04-10 15:02:50 +00:00
Fixes
fix localeWithStrings globally (#30)
Fix badge on zoomed devices. closes #9
Hide channel bottom panel closes #27
Another attempt to fix badge on some Zoomed devices
Force System Share sheet tg://sg/debug
fixes for device badge
New Crowdin updates (#34)
* New translations sglocalizable.strings (Chinese Traditional)
* New translations sglocalizable.strings (Chinese Simplified)
* New translations sglocalizable.strings (Chinese Traditional)
Fix input panel hidden on selection (#31)
* added if check for selectionState != nil
* same order of subnodes
Revert "Fix input panel hidden on selection (#31)"
This reverts commit e8a8bb1496.
Fix input panel for channels Closes #37
Quickly share links with system's share menu
force tabbar when editing
increase height for correct animation
New translations sglocalizable.strings (Ukrainian) (#38)
Hide Post Story button
Fix 10.15.1
Fix archive option for long-tap
Enable in-app Safari
Disable some unsupported purchases
disableDeleteChatSwipeOption + refactor restart alert
Hide bot in suggestions list
Fix merge v11.0
Fix exceptions for safari webview controller
New Crowdin updates (#47)
* New translations sglocalizable.strings (Romanian)
* New translations sglocalizable.strings (French)
* New translations sglocalizable.strings (Spanish)
* New translations sglocalizable.strings (Afrikaans)
* New translations sglocalizable.strings (Arabic)
* New translations sglocalizable.strings (Catalan)
* New translations sglocalizable.strings (Czech)
* New translations sglocalizable.strings (Danish)
* New translations sglocalizable.strings (German)
* New translations sglocalizable.strings (Greek)
* New translations sglocalizable.strings (Finnish)
* New translations sglocalizable.strings (Hebrew)
* New translations sglocalizable.strings (Hungarian)
* New translations sglocalizable.strings (Italian)
* New translations sglocalizable.strings (Japanese)
* New translations sglocalizable.strings (Korean)
* New translations sglocalizable.strings (Dutch)
* New translations sglocalizable.strings (Norwegian)
* New translations sglocalizable.strings (Polish)
* New translations sglocalizable.strings (Portuguese)
* New translations sglocalizable.strings (Serbian (Cyrillic))
* New translations sglocalizable.strings (Swedish)
* New translations sglocalizable.strings (Turkish)
* New translations sglocalizable.strings (Vietnamese)
* New translations sglocalizable.strings (Indonesian)
* New translations sglocalizable.strings (Hindi)
* New translations sglocalizable.strings (Uzbek)
New Crowdin updates (#49)
* New translations sglocalizable.strings (Arabic)
* New translations sglocalizable.strings (Arabic)
New translations sglocalizable.strings (Russian) (#51)
Call confirmation
WIP Settings search
Settings Search
Localize placeholder
Update AccountUtils.swift
mark mutual contact
Align back context action to left
New Crowdin updates (#54)
* New translations sglocalizable.strings (Chinese Simplified)
* New translations sglocalizable.strings (Chinese Traditional)
* New translations sglocalizable.strings (Ukrainian)
Independent Playground app for simulator
New translations sglocalizable.strings (Ukrainian) (#55)
Playground UIKit base and controllers
Inject SwiftUI view with overflow to AsyncDisplayKit
Launch Playgound project on simulator
Create .swiftformat
Move Playground to example
Update .swiftformat
Init SwiftUIViewController
wip
New translations sglocalizable.strings (Chinese Traditional) (#57)
Xcode 16 fixes
Fix
New translations sglocalizable.strings (Italian) (#59)
New translations sglocalizable.strings (Chinese Simplified) (#63)
Force disable CallKit integration due to missing NSE Entitlement
Fix merge
Fix whole chat translator
Sweetpad config
Bump version
11.3.1 fixes
Mutual contact placement fix
Disable Video PIP swipe
Update versions.json
Fix PIP crash
553 lines
27 KiB
Swift
553 lines
27 KiB
Swift
import SGSimpleSettings
|
|
import Foundation
|
|
import UIKit
|
|
import Postbox
|
|
import SwiftSignalKit
|
|
import Display
|
|
import AsyncDisplayKit
|
|
import TelegramCore
|
|
import SafariServices
|
|
import MobileCoreServices
|
|
import Intents
|
|
import LegacyComponents
|
|
import TelegramPresentationData
|
|
import TelegramUIPreferences
|
|
import DeviceAccess
|
|
import TextFormat
|
|
import TelegramBaseController
|
|
import AccountContext
|
|
import TelegramStringFormatting
|
|
import OverlayStatusController
|
|
import DeviceLocationManager
|
|
import ShareController
|
|
import UrlEscaping
|
|
import ContextUI
|
|
import ComposePollUI
|
|
import AlertUI
|
|
import PresentationDataUtils
|
|
import UndoUI
|
|
import TelegramCallsUI
|
|
import TelegramNotices
|
|
import GameUI
|
|
import ScreenCaptureDetection
|
|
import GalleryUI
|
|
import OpenInExternalAppUI
|
|
import LegacyUI
|
|
import InstantPageUI
|
|
import LocationUI
|
|
import BotPaymentsUI
|
|
import DeleteChatPeerActionSheetItem
|
|
import HashtagSearchUI
|
|
import LegacyMediaPickerUI
|
|
import Emoji
|
|
import PeerAvatarGalleryUI
|
|
import PeerInfoUI
|
|
import RaiseToListen
|
|
import UrlHandling
|
|
import AvatarNode
|
|
import AppBundle
|
|
import LocalizedPeerData
|
|
import PhoneNumberFormat
|
|
import SettingsUI
|
|
import UrlWhitelist
|
|
import TelegramIntents
|
|
import TooltipUI
|
|
import StatisticsUI
|
|
import MediaResources
|
|
import GalleryData
|
|
import ChatInterfaceState
|
|
import InviteLinksUI
|
|
import Markdown
|
|
import TelegramPermissionsUI
|
|
import Speak
|
|
import TranslateUI
|
|
import UniversalMediaPlayer
|
|
import WallpaperBackgroundNode
|
|
import ChatListUI
|
|
import CalendarMessageScreen
|
|
import ReactionSelectionNode
|
|
import ReactionListContextMenuContent
|
|
import AttachmentUI
|
|
import AttachmentTextInputPanelNode
|
|
import MediaPickerUI
|
|
import ChatPresentationInterfaceState
|
|
import Pasteboard
|
|
import ChatSendMessageActionUI
|
|
import ChatTextLinkEditUI
|
|
import WebUI
|
|
import PremiumUI
|
|
import ImageTransparency
|
|
import StickerPackPreviewUI
|
|
import TextNodeWithEntities
|
|
import EntityKeyboard
|
|
import ChatTitleView
|
|
import EmojiStatusComponent
|
|
import ChatTimerScreen
|
|
import MediaPasteboardUI
|
|
import ChatListHeaderComponent
|
|
import ChatControllerInteraction
|
|
import FeaturedStickersScreen
|
|
import ChatEntityKeyboardInputNode
|
|
import StorageUsageScreen
|
|
import AvatarEditorScreen
|
|
import ChatScheduleTimeController
|
|
import ICloudResources
|
|
import StoryContainerScreen
|
|
import MoreHeaderButton
|
|
import VolumeButtons
|
|
import ChatAvatarNavigationNode
|
|
import ChatContextQuery
|
|
import PeerReportScreen
|
|
import PeerSelectionController
|
|
import SaveToCameraRoll
|
|
import ChatMessageDateAndStatusNode
|
|
import ReplyAccessoryPanelNode
|
|
import TextSelectionNode
|
|
import ChatMessagePollBubbleContentNode
|
|
import ChatMessageItem
|
|
import ChatMessageItemImpl
|
|
import ChatMessageItemView
|
|
import ChatMessageItemCommon
|
|
import ChatMessageAnimatedStickerItemNode
|
|
import ChatMessageBubbleItemNode
|
|
import ChatNavigationButton
|
|
import WebsiteType
|
|
import ChatQrCodeScreen
|
|
import PeerInfoScreen
|
|
import MediaEditorScreen
|
|
import WallpaperGalleryScreen
|
|
import WallpaperGridScreen
|
|
import VideoMessageCameraScreen
|
|
import TopMessageReactions
|
|
import AudioWaveform
|
|
import PeerNameColorScreen
|
|
import ChatEmptyNode
|
|
import ChatMediaInputStickerGridItem
|
|
import AdsInfoScreen
|
|
|
|
extension ChatControllerImpl {
|
|
func requestAudioRecorder(beginWithTone: Bool) {
|
|
if self.audioRecorderValue == nil {
|
|
if self.recorderFeedback == nil {
|
|
self.recorderFeedback = HapticFeedback()
|
|
self.recorderFeedback?.prepareImpact(.light)
|
|
}
|
|
|
|
self.audioRecorder.set(self.context.sharedContext.mediaManager.audioRecorder(beginWithTone: beginWithTone, applicationBindings: self.context.sharedContext.applicationBindings, beganWithTone: { _ in
|
|
}))
|
|
}
|
|
}
|
|
|
|
func requestVideoRecorder() {
|
|
if self.videoRecorderValue == nil {
|
|
if let currentInputPanelFrame = self.chatDisplayNode.currentInputPanelFrame() {
|
|
if self.recorderFeedback == nil {
|
|
self.recorderFeedback = HapticFeedback()
|
|
self.recorderFeedback?.prepareImpact(.light)
|
|
}
|
|
|
|
var isScheduledMessages = false
|
|
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
|
isScheduledMessages = true
|
|
}
|
|
|
|
var isBot = false
|
|
|
|
var allowLiveUpload = false
|
|
var viewOnceAvailable = false
|
|
if let peerId = self.chatLocation.peerId {
|
|
allowLiveUpload = peerId.namespace != Namespaces.Peer.SecretChat
|
|
viewOnceAvailable = !isScheduledMessages && peerId.namespace == Namespaces.Peer.CloudUser && peerId != self.context.account.peerId && !isBot
|
|
} else if case .customChatContents = self.chatLocation {
|
|
allowLiveUpload = true
|
|
}
|
|
|
|
if let user = self.presentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil {
|
|
isBot = true
|
|
}
|
|
|
|
let controller = VideoMessageCameraScreen(
|
|
context: self.context,
|
|
updatedPresentationData: self.updatedPresentationData,
|
|
allowLiveUpload: allowLiveUpload,
|
|
viewOnceAvailable: viewOnceAvailable,
|
|
inputPanelFrame: (currentInputPanelFrame, self.chatDisplayNode.inputNode != nil),
|
|
chatNode: self.chatDisplayNode.historyNode,
|
|
completion: { [weak self] message, silentPosting, scheduleTime in
|
|
guard let self, let videoController = self.videoRecorderValue else {
|
|
return
|
|
}
|
|
|
|
guard var message else {
|
|
self.recorderFeedback?.error()
|
|
self.recorderFeedback = nil
|
|
self.videoRecorder.set(.single(nil))
|
|
return
|
|
}
|
|
|
|
let replyMessageSubject = self.presentationInterfaceState.interfaceState.replyMessageSubject
|
|
let correlationId = Int64.random(in: 0 ..< Int64.max)
|
|
message = message
|
|
.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel)
|
|
.withUpdatedCorrelationId(correlationId)
|
|
|
|
var usedCorrelationId = false
|
|
if scheduleTime == nil, self.chatDisplayNode.shouldAnimateMessageTransition, let extractedView = videoController.extractVideoSnapshot() {
|
|
usedCorrelationId = true
|
|
self.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .videoMessage(ChatMessageTransitionNodeImpl.Source.VideoMessage(view: extractedView)), initiated: { [weak videoController, weak self] in
|
|
videoController?.hideVideoSnapshot()
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.videoRecorder.set(.single(nil))
|
|
})
|
|
} else {
|
|
self.videoRecorder.set(.single(nil))
|
|
}
|
|
|
|
self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in
|
|
if let self {
|
|
self.chatDisplayNode.collapseInput()
|
|
|
|
self.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
|
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedMediaDraftState(nil) }
|
|
})
|
|
}
|
|
}, usedCorrelationId ? correlationId : nil)
|
|
|
|
let messages = [message]
|
|
let transformedMessages: [EnqueueMessage]
|
|
if let silentPosting {
|
|
transformedMessages = self.transformEnqueueMessages(messages, silentPosting: silentPosting)
|
|
} else if let scheduleTime {
|
|
transformedMessages = self.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime)
|
|
} else {
|
|
transformedMessages = self.transformEnqueueMessages(messages)
|
|
}
|
|
|
|
self.sendMessages(transformedMessages)
|
|
}
|
|
)
|
|
controller.onResume = { [weak self] in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.resumeMediaRecorder()
|
|
}
|
|
self.videoRecorder.set(.single(controller))
|
|
}
|
|
}
|
|
}
|
|
|
|
func dismissMediaRecorder(_ action: ChatFinishMediaRecordingAction) {
|
|
var updatedAction = action
|
|
var isScheduledMessages = false
|
|
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
|
isScheduledMessages = true
|
|
}
|
|
|
|
if let _ = self.presentationInterfaceState.slowmodeState, !isScheduledMessages {
|
|
updatedAction = .preview
|
|
}
|
|
|
|
if let audioRecorderValue = self.audioRecorderValue {
|
|
switch action {
|
|
case .pause:
|
|
audioRecorderValue.pause()
|
|
default:
|
|
audioRecorderValue.stop()
|
|
}
|
|
|
|
switch updatedAction {
|
|
case .dismiss:
|
|
self.recorderDataDisposable.set(nil)
|
|
self.chatDisplayNode.updateRecordedMediaDeleted(true)
|
|
self.audioRecorder.set(.single(nil))
|
|
case .preview, .pause:
|
|
if case .preview = updatedAction {
|
|
self.audioRecorder.set(.single(nil))
|
|
}
|
|
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
|
$0.updatedInputTextPanelState { panelState in
|
|
return panelState.withUpdatedMediaRecordingState(.waitingForPreview)
|
|
}
|
|
})
|
|
self.recorderDataDisposable.set((audioRecorderValue.takenRecordedData()
|
|
|> deliverOnMainQueue).startStrict(next: { [weak self] data in
|
|
if let strongSelf = self, let data = data {
|
|
if data.duration < 0.5 {
|
|
strongSelf.recorderFeedback?.error()
|
|
strongSelf.recorderFeedback = nil
|
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
|
$0.updatedInputTextPanelState { panelState in
|
|
return panelState.withUpdatedMediaRecordingState(nil)
|
|
}
|
|
})
|
|
strongSelf.recorderDataDisposable.set(nil)
|
|
} else if let waveform = data.waveform {
|
|
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max), size: Int64(data.compressedData.count))
|
|
|
|
strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data.compressedData)
|
|
|
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
|
$0.updatedInterfaceState { $0.withUpdatedMediaDraftState(.audio(ChatInterfaceMediaDraftState.Audio(resource: resource, fileSize: Int32(data.compressedData.count), duration: Int32(data.duration), waveform: AudioWaveform(bitstream: waveform, bitsPerSample: 5)))) }.updatedInputTextPanelState { panelState in
|
|
return panelState.withUpdatedMediaRecordingState(nil)
|
|
}
|
|
})
|
|
strongSelf.recorderFeedback = nil
|
|
strongSelf.updateDownButtonVisibility()
|
|
strongSelf.recorderDataDisposable.set(nil)
|
|
}
|
|
}
|
|
}))
|
|
case let .send(viewOnce):
|
|
self.chatDisplayNode.updateRecordedMediaDeleted(false)
|
|
self.recorderDataDisposable.set((audioRecorderValue.takenRecordedData()
|
|
|> deliverOnMainQueue).startStrict(next: { [weak self] data in
|
|
if let strongSelf = self, let data = data {
|
|
if data.duration < 0.5 {
|
|
strongSelf.recorderFeedback?.error()
|
|
strongSelf.recorderFeedback = nil
|
|
strongSelf.audioRecorder.set(.single(nil))
|
|
} else {
|
|
let randomId = Int64.random(in: Int64.min ... Int64.max)
|
|
|
|
let resource = LocalFileMediaResource(fileId: randomId)
|
|
strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data.compressedData)
|
|
|
|
let waveformBuffer: Data? = data.waveform
|
|
|
|
let correlationId = Int64.random(in: 0 ..< Int64.max)
|
|
var usedCorrelationId = false
|
|
|
|
if strongSelf.chatDisplayNode.shouldAnimateMessageTransition, let textInputPanelNode = strongSelf.chatDisplayNode.textInputPanelNode, let micButton = textInputPanelNode.micButton {
|
|
usedCorrelationId = true
|
|
strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .audioMicInput(ChatMessageTransitionNodeImpl.Source.AudioMicInput(micButton: micButton)), initiated: {
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
strongSelf.audioRecorder.set(.single(nil))
|
|
})
|
|
} else {
|
|
strongSelf.audioRecorder.set(.single(nil))
|
|
}
|
|
|
|
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
|
if let strongSelf = self {
|
|
strongSelf.chatDisplayNode.collapseInput()
|
|
|
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
|
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) }
|
|
})
|
|
}
|
|
}, usedCorrelationId ? correlationId : nil)
|
|
|
|
var attributes: [MessageAttribute] = []
|
|
if viewOnce {
|
|
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: viewOnceTimeout, countdownBeginTime: nil))
|
|
}
|
|
|
|
strongSelf.sendMessages([.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(data.compressedData.count), attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: [])])
|
|
|
|
strongSelf.recorderFeedback?.tap()
|
|
strongSelf.recorderFeedback = nil
|
|
strongSelf.recorderDataDisposable.set(nil)
|
|
}
|
|
}
|
|
}))
|
|
}
|
|
} else if let videoRecorderValue = self.videoRecorderValue {
|
|
if case .send = updatedAction {
|
|
self.chatDisplayNode.updateRecordedMediaDeleted(false)
|
|
videoRecorderValue.sendVideoRecording()
|
|
self.recorderDataDisposable.set(nil)
|
|
} else {
|
|
if case .dismiss = updatedAction {
|
|
self.chatDisplayNode.updateRecordedMediaDeleted(true)
|
|
self.recorderDataDisposable.set(nil)
|
|
}
|
|
|
|
switch updatedAction {
|
|
case .preview, .pause:
|
|
if videoRecorderValue.stopVideoRecording() {
|
|
self.recorderDataDisposable.set((videoRecorderValue.takenRecordedData()
|
|
|> deliverOnMainQueue).startStrict(next: { [weak self] data in
|
|
if let strongSelf = self, let data = data {
|
|
if data.duration < 1.0 {
|
|
strongSelf.recorderFeedback?.error()
|
|
strongSelf.recorderFeedback = nil
|
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
|
$0.updatedInputTextPanelState { panelState in
|
|
return panelState.withUpdatedMediaRecordingState(nil)
|
|
}
|
|
})
|
|
strongSelf.recorderDataDisposable.set(nil)
|
|
strongSelf.videoRecorder.set(.single(nil))
|
|
} else {
|
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
|
$0.updatedInterfaceState {
|
|
$0.withUpdatedMediaDraftState(.video(
|
|
ChatInterfaceMediaDraftState.Video(
|
|
duration: Int32(data.duration),
|
|
frames: data.frames,
|
|
framesUpdateTimestamp: data.framesUpdateTimestamp,
|
|
trimRange: data.trimRange
|
|
)
|
|
))
|
|
}.updatedInputTextPanelState { panelState in
|
|
return panelState.withUpdatedMediaRecordingState(nil)
|
|
}
|
|
})
|
|
strongSelf.recorderFeedback = nil
|
|
strongSelf.updateDownButtonVisibility()
|
|
}
|
|
}
|
|
}))
|
|
}
|
|
default:
|
|
self.recorderDataDisposable.set(nil)
|
|
self.videoRecorder.set(.single(nil))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func stopMediaRecorder(pause: Bool = false) {
|
|
if let audioRecorderValue = self.audioRecorderValue {
|
|
if let _ = self.presentationInterfaceState.inputTextPanelState.mediaRecordingState {
|
|
self.dismissMediaRecorder(pause ? .pause : .preview)
|
|
} else {
|
|
audioRecorderValue.stop()
|
|
self.audioRecorder.set(.single(nil))
|
|
}
|
|
} else if let _ = self.videoRecorderValue {
|
|
if let _ = self.presentationInterfaceState.inputTextPanelState.mediaRecordingState {
|
|
self.dismissMediaRecorder(pause ? .pause : .preview)
|
|
} else {
|
|
self.videoRecorder.set(.single(nil))
|
|
}
|
|
}
|
|
}
|
|
|
|
func resumeMediaRecorder() {
|
|
self.context.sharedContext.mediaManager.playlistControl(.playback(.pause), type: nil)
|
|
|
|
if let audioRecorderValue = self.audioRecorderValue {
|
|
audioRecorderValue.resume()
|
|
|
|
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
|
$0.updatedInputTextPanelState { panelState in
|
|
return panelState.withUpdatedMediaRecordingState(.audio(recorder: audioRecorderValue, isLocked: true))
|
|
}.updatedInterfaceState { $0.withUpdatedMediaDraftState(nil) }
|
|
})
|
|
} else if let videoRecorderValue = self.videoRecorderValue {
|
|
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
|
$0.updatedInputTextPanelState { panelState in
|
|
let recordingStatus = videoRecorderValue.recordingStatus
|
|
return panelState.withUpdatedMediaRecordingState(.video(status: .recording(InstantVideoControllerRecordingStatus(micLevel: recordingStatus.micLevel, duration: recordingStatus.duration)), isLocked: true))
|
|
}.updatedInterfaceState { $0.withUpdatedMediaDraftState(nil) }
|
|
})
|
|
}
|
|
}
|
|
|
|
func lockMediaRecorder() {
|
|
if self.presentationInterfaceState.inputTextPanelState.mediaRecordingState != nil {
|
|
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
|
return $0.updatedInputTextPanelState { panelState in
|
|
return panelState.withUpdatedMediaRecordingState(panelState.mediaRecordingState?.withLocked(true))
|
|
}
|
|
})
|
|
}
|
|
|
|
self.videoRecorderValue?.lockVideoRecording()
|
|
}
|
|
|
|
func deleteMediaRecording() {
|
|
if let _ = self.audioRecorderValue {
|
|
self.audioRecorder.set(.single(nil))
|
|
} else if let _ = self.videoRecorderValue {
|
|
self.videoRecorder.set(.single(nil))
|
|
}
|
|
|
|
self.recorderDataDisposable.set(nil)
|
|
self.chatDisplayNode.updateRecordedMediaDeleted(true)
|
|
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
|
$0.updatedInterfaceState { $0.withUpdatedMediaDraftState(nil) }
|
|
})
|
|
self.updateDownButtonVisibility()
|
|
}
|
|
|
|
func sendMediaRecording(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, viewOnce: Bool = false, messageEffect: ChatSendMessageEffect? = nil) {
|
|
self.chatDisplayNode.updateRecordedMediaDeleted(false)
|
|
|
|
guard let recordedMediaPreview = self.presentationInterfaceState.interfaceState.mediaDraftState else {
|
|
return
|
|
}
|
|
|
|
switch recordedMediaPreview {
|
|
case let .audio(audio):
|
|
self.audioRecorder.set(.single(nil))
|
|
|
|
var isScheduledMessages = false
|
|
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
|
isScheduledMessages = true
|
|
}
|
|
|
|
if let _ = self.presentationInterfaceState.slowmodeState, !isScheduledMessages {
|
|
if let rect = self.chatDisplayNode.frameForInputActionButton() {
|
|
self.interfaceInteraction?.displaySlowmodeTooltip(self.chatDisplayNode.view, rect)
|
|
}
|
|
return
|
|
}
|
|
|
|
let waveformBuffer = audio.waveform.makeBitstream()
|
|
|
|
self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in
|
|
if let strongSelf = self {
|
|
strongSelf.chatDisplayNode.collapseInput()
|
|
|
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
|
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedMediaDraftState(nil).withUpdatedSendMessageEffect(nil) }
|
|
})
|
|
|
|
strongSelf.updateDownButtonVisibility()
|
|
}
|
|
}, nil)
|
|
|
|
var attributes: [MessageAttribute] = []
|
|
if viewOnce {
|
|
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: viewOnceTimeout, countdownBeginTime: nil))
|
|
}
|
|
if let messageEffect {
|
|
attributes.append(EffectMessageAttribute(id: messageEffect.id))
|
|
}
|
|
|
|
let messages: [EnqueueMessage] = [.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: audio.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(audio.fileSize), attributes: [.Audio(isVoice: true, duration: Int(audio.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: self.chatLocation.threadId, replyToMessageId: self.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]
|
|
|
|
let transformedMessages: [EnqueueMessage]
|
|
if let silentPosting = silentPosting {
|
|
transformedMessages = self.transformEnqueueMessages(messages, silentPosting: silentPosting)
|
|
} else if let scheduleTime = scheduleTime {
|
|
transformedMessages = self.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime)
|
|
} else {
|
|
transformedMessages = self.transformEnqueueMessages(messages)
|
|
}
|
|
|
|
guard let peerId = self.chatLocation.peerId else {
|
|
return
|
|
}
|
|
|
|
let _ = (enqueueMessages(account: self.context.account, peerId: peerId, messages: transformedMessages)
|
|
|> deliverOnMainQueue).startStandalone(next: { [weak self] _ in
|
|
if let strongSelf = self, strongSelf.presentationInterfaceState.subject != .scheduledMessages {
|
|
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
|
}
|
|
})
|
|
|
|
donateSendMessageIntent(account: self.context.account, sharedContext: self.context.sharedContext, intentContext: .chat, peerIds: [peerId])
|
|
case .video:
|
|
self.videoRecorderValue?.sendVideoRecording(silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect)
|
|
}
|
|
}
|
|
}
|