diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 0a480132c6..c780283d14 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10017,3 +10017,5 @@ Sorry for the inconvenience."; "Stats.Boosts.OverviewHeader" = "OVERVIEW"; "Stats.Boosts.LinkHeader" = "LINK FOR BOOSTING"; "Stats.Boosts.LinkInfo" = "Share this link with your subscribers to get more boosts."; + +"Conversation.SaveToFiles" = "Save to Files"; diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift index b95965ed83..2286cc04ce 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift @@ -353,6 +353,9 @@ public final class DrawingStickerEntityView: DrawingEntityView { self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() self.imageNode.frame = imageFrame if let animationNode = self.animationNode { + if self.isReaction { + animationNode.cornerRadius = floor(imageSize.width * 0.03) + } animationNode.frame = imageFrame animationNode.updateLayout(size: imageSize) @@ -363,10 +366,9 @@ public final class DrawingStickerEntityView: DrawingEntityView { } if let videoNode = self.videoNode { - let videoSize = self.dimensions.aspectFitted(boundingSize) - videoNode.cornerRadius = floor(videoSize.width * 0.03) - videoNode.frame = CGRect(origin: CGPoint(x: floor((size.width - videoSize.width) * 0.5), y: floor((size.height - videoSize.height) * 0.5)), size: videoSize) - videoNode.updateLayout(size: videoSize, transition: .immediate) + videoNode.cornerRadius = floor(imageSize.width * 0.03) + videoNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) * 0.5), y: floor((size.height - imageSize.height) * 0.5)), size: imageSize) + videoNode.updateLayout(size: imageSize, transition: .immediate) } self.update(animated: false) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index 547804ef92..3d1b99f597 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -652,7 +652,7 @@ public final class MediaEditor { if self.sourceIsVideo { start = self.values.videoTrimRange?.lowerBound ?? 0.0 } else { - start = self.values.audioTrackTrimRange?.lowerBound ?? 0.0 + start = (self.values.audioTrackOffset ?? 0.0) + (self.values.audioTrackTrimRange?.lowerBound ?? 0.0) } let targetTime = CMTime(seconds: start, preferredTimescale: CMTimeScale(1000)) self.player?.seek(to: targetTime) @@ -846,11 +846,7 @@ public final class MediaEditor { self.audioPlayer?.seek(to: self.audioTime(for: targetPosition), toleranceBefore: .zero, toleranceAfter: .zero) } } - - private func setupAudioPlayback() { - } - private var audioDelayTimer: SwiftSignalKit.Timer? private func audioDelay(for time: CMTime) -> Double? { var time = time @@ -1052,6 +1048,17 @@ public final class MediaEditor { return values.withUpdatedAudioTrack(audioTrack).withUpdatedAudioTrackSamples(nil).withUpdatedAudioTrackTrimRange(nil).withUpdatedAudioTrackVolume(nil).withUpdatedAudioTrackOffset(nil) } + if let audioPlayer = self.audioPlayer { + audioPlayer.pause() + + self.destroyTimeObservers() + self.audioPlayer = nil + + if !self.sourceIsVideo { + self.playerPromise.set(.single(nil)) + } + } + if let audioTrack { let path = fullDraftPath(peerId: self.context.account.peerId, path: audioTrack.path) let audioAsset = AVURLAsset(url: URL(fileURLWithPath: path)) @@ -1066,16 +1073,6 @@ public final class MediaEditor { if !self.sourceIsVideo { self.playerPromise.set(.single(player)) } - } else if let audioPlayer = self.audioPlayer { - audioPlayer.pause() - - self.destroyTimeObservers() - - self.audioPlayer = nil - - if !self.sourceIsVideo { - self.playerPromise.set(.single(nil)) - } } } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 29d7590a14..7af447db1d 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -760,9 +760,11 @@ final class MediaEditorScreenComponent: Component { if controller.isEditingStory { controller.requestCompletion(animated: true) } else { - controller.openPrivacySettings(completion: { [weak controller] in - controller?.requestCompletion(animated: true) - }) + if controller.checkIfCompletionIsAllowed() { + controller.openPrivacySettings(completion: { [weak controller] in + controller?.requestCompletion(animated: true) + }) + } } } )), @@ -3101,7 +3103,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate func presentAudioPicker() { var isSettingTrack = false - self.controller?.present(legacyICloudFilePicker(theme: self.presentationData.theme, mode: .import, documentTypes: ["public.mp3"], forceDarkTheme: true, dismissed: { [weak self] in + self.controller?.present(legacyICloudFilePicker(theme: self.presentationData.theme, mode: .import, documentTypes: ["public.mp3", "public.mpeg-4-audio", "public.aac-audio"], forceDarkTheme: true, dismissed: { [weak self] in if let self { Queue.mainQueue().after(0.1) { if !isSettingTrack { @@ -4188,6 +4190,24 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.present(controller, in: .window(.root)) }) } + + fileprivate func presentUnavailableReactionPremiumSuggestion(file: TelegramMediaFile) { + self.hapticFeedback.error() + + self.dismissAllTooltips() + + let context = self.context + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + let controller = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: presentationData.strings.Story_Editor_TooltipPremiumReaction, undoText: nil, customAction: nil), elevatedLayout: true, position: .top, animateInAsReplacement: false, blurred: true, action: { [weak self] action in + if case .info = action, let self { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesExpirationDurations, forceDark: true, dismissed: nil) + self.push(controller) + } + return false + }) + self.present(controller, in: .window(.root)) + } fileprivate func presentCaptionLimitPremiumSuggestion(isPremium: Bool) { self.dismissAllTooltips() @@ -4442,6 +4462,19 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } return true } + + func checkIfCompletionIsAllowed() -> Bool { + if !self.context.isPremium { + let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) } + for entity in entities { + if let stickerEntity = entity as? DrawingStickerEntity, case let .file(file, type) = stickerEntity.content, case let .reaction(reaction, _) = type, case .custom = reaction { + self.presentUnavailableReactionPremiumSuggestion(file: file) + return false + } + } + } + return true + } private var didComplete = false func requestCompletion(animated: Bool, skipSendCheck: Bool = false) { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index a6821c2e3a..625f2e12b8 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4536,9 +4536,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } var file: TelegramMediaFile? + var title: String? + var performer: String? for media in message.media { if let mediaFile = media as? TelegramMediaFile, mediaFile.isMusic { file = mediaFile + for attribute in mediaFile.attributes { + if case let .Audio(_, _, titleValue, performerValue, _) = attribute { + if let titleValue, !titleValue.isEmpty { + title = titleValue + } + if let performerValue, !performerValue.isEmpty { + performer = performerValue + } + } + } } } guard let file else { @@ -4602,28 +4614,49 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let audioUrl = URL(fileURLWithPath: symlinkPath) let audioAsset = AVURLAsset(url: audioUrl) + + var fileExtension = "mp3" + if let filename = file.fileName { + if let dotIndex = filename.lastIndex(of: ".") { + fileExtension = String(filename[filename.index(after: dotIndex)...]) + } + } + var nameComponents: [String] = [] - var artist: String? - var title: String? - for data in audioAsset.commonMetadata { - if data.commonKey == .commonKeyArtist { - artist = data.stringValue + if let title { + if let performer { + nameComponents.append(performer) } - if data.commonKey == .commonKeyTitle { - title = data.stringValue - } - } - if let artist, !artist.isEmpty { - nameComponents.append(artist) - } - if let title, !title.isEmpty { nameComponents.append(title) + } else { + var artist: String? + var title: String? + for data in audioAsset.commonMetadata { + if data.commonKey == .commonKeyArtist { + artist = data.stringValue + } + if data.commonKey == .commonKeyTitle { + title = data.stringValue + } + } + if let artist, !artist.isEmpty { + nameComponents.append(artist) + } + if let title, !title.isEmpty { + nameComponents.append(title) + } + if nameComponents.isEmpty, var filename = file.fileName { + if let dotIndex = filename.lastIndex(of: ".") { + filename = String(filename[..