From 2b22d175d804c6a9fb1678bb2bd07795bfb8f5b2 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 30 May 2023 15:57:09 +0400 Subject: [PATCH] Camera and editor improvements --- .../Sources/AccountContext.swift | 2 +- submodules/Camera/Sources/CameraOutput.swift | 25 ++++- .../Sources/ChatListController.swift | 3 + .../Sources/ChatListControllerNode.swift | 12 ++- .../ChatListFilterTabContainerNode.swift | 38 ++++++-- .../Sources/MediaGroupsAlbumItem.swift | 2 - .../Sources/MediaGroupsScreen.swift | 91 +++++++++++++++++-- .../Sources/MediaPickerScreen.swift | 81 ++++++++++++----- .../Sources/Themes/ThemeGridController.swift | 2 +- .../CameraScreen/Sources/CameraScreen.swift | 49 ++++++---- .../MediaEditor/Sources/MediaEditor.swift | 2 + .../Sources/MediaEditorValues.swift | 37 ++++++-- .../Sources/MediaEditorScreen.swift | 4 +- .../Sources/MediaToolsScreen.swift | 2 +- .../TelegramUI/Sources/ChatController.swift | 2 +- .../Sources/SharedAccountContext.swift | 2 +- .../Sources/TelegramRootController.swift | 5 +- 17 files changed, 278 insertions(+), 81 deletions(-) diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 16080fa8ed..83cf05cc27 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -891,7 +891,7 @@ public protocol SharedAccountContext: AnyObject { func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController - func makeMediaPickerScreen(context: AccountContext, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping () -> (UIView, CGRect)?) -> Void, dismissed: @escaping () -> Void) -> ViewController + func makeMediaPickerScreen(context: AccountContext, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping () -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index 6f3b2d0e58..ea51c2702c 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -1,6 +1,7 @@ import AVFoundation import SwiftSignalKit import Vision +import VideoToolbox public struct CameraCode: Equatable { public enum CodeType { @@ -139,10 +140,6 @@ final class CameraOutput: NSObject { connection.videoOrientation = orientation } -// var settings = AVCapturePhotoSettings() -// if self.photoOutput.availablePhotoCodecTypes.contains(.hevc) { -// settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.hevc]) -// } let settings = AVCapturePhotoSettings(format: [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)]) settings.flashMode = flashMode if let previewPhotoPixelFormatType = settings.availablePreviewPhotoPixelFormatTypes.first { @@ -169,7 +166,14 @@ final class CameraOutput: NSObject { return .complete() } - guard let videoSettings = self.videoOutput.recommendedVideoSettings(forVideoCodecType: .h264, assetWriterOutputFileType: .mp4) else { + let codecType: AVVideoCodecType + if hasHEVCHardwareEncoder { + codecType = .hevc + } else { + codecType = .h264 + } + + guard let videoSettings = self.videoOutput.recommendedVideoSettings(forVideoCodecType: codecType, assetWriterOutputFileType: .mp4) else { return .complete() } guard let audioSettings = self.audioOutput.recommendedAudioSettingsForAssetWriter(writingTo: .mp4) else { @@ -272,3 +276,14 @@ extension CameraOutput: AVCaptureMetadataOutputObjectsDelegate { self.processCodes?(codes) } } + +private let hasHEVCHardwareEncoder: Bool = { + let spec: [CFString: Any] = [:] + var outID: CFString? + var properties: CFDictionary? + let result = VTCopySupportedPropertyDictionaryForEncoder(width: 1920, height: 1080, codecType: kCMVideoCodecType_HEVC, encoderSpecification: spec as CFDictionary, encoderIDOut: &outID, supportedPropertiesOut: &properties) + if result == kVTCouldNotFindVideoEncoderErr { + return false + } + return result == noErr +}() diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 43ed04b506..57b209e05b 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -4942,6 +4942,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } private var storyCameraTransitionInCoordinator: StoryCameraTransitionInCoordinator? + var hasStoryCameraTransition: Bool { + return self.storyCameraTransitionInCoordinator != nil + } func storyCameraPanGestureChanged(transitionFraction: CGFloat) { guard let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface else { return diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 84714e2c25..9195596041 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -1080,13 +1080,19 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele let coefficient: CGFloat = 0.4 return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range } - + + let cameraIsAlreadyOpened = self.controller?.hasStoryCameraTransition ?? false if selectedIndex <= 0 && translation.x > 0.0 { - //let overscroll = translation.x - //transitionFraction = rubberBandingOffset(offset: overscroll, bandingStart: 0.0) / layout.size.width transitionFraction = 0.0 self.controller?.storyCameraPanGestureChanged(transitionFraction: translation.x / layout.size.width) + } else if translation.x <= 0.0 && cameraIsAlreadyOpened { + self.controller?.storyCameraPanGestureChanged(transitionFraction: 0.0) + } + + if cameraIsAlreadyOpened { + transitionFraction = 0.0 + return } if selectedIndex >= maxFilterIndex && translation.x < 0.0 { diff --git a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift index 92af4bf216..68e3e61e6c 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift @@ -84,6 +84,7 @@ private final class ItemNode: ASDisplayNode { private var isDisabled: Bool = false private var theme: PresentationTheme? + private var currentTitle: (String, String)? private var pointerInteraction: PointerInteraction? @@ -197,16 +198,34 @@ private final class ItemNode: ASDisplayNode { self.isEditing = isEditing self.isDisabled = isDisabled + var themeUpdated = false if self.theme !== presentationData.theme { self.theme = presentationData.theme self.badgeBackgroundActiveNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.chatList.unreadBadgeActiveBackgroundColor) self.badgeBackgroundInactiveNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.chatList.unreadBadgeInactiveBackgroundColor) + + themeUpdated = true + } + + var titleUpdated = false + if self.currentTitle?.0 != title || self.currentTitle?.1 != shortTitle { + self.currentTitle = (title, shortTitle) + + titleUpdated = true + } + + var unreadCountUpdated = false + if self.unreadCount != unreadCount { + unreadCountUpdated = true + self.unreadCount = unreadCount } self.buttonNode.accessibilityLabel = title if unreadCount > 0 { - self.buttonNode.accessibilityValue = strings.VoiceOver_Chat_UnreadMessages(Int32(unreadCount)) + if self.buttonNode.accessibilityValue == nil || unreadCountUpdated { + self.buttonNode.accessibilityValue = strings.VoiceOver_Chat_UnreadMessages(Int32(unreadCount)) + } } else { self.buttonNode.accessibilityValue = "" } @@ -252,14 +271,19 @@ private final class ItemNode: ASDisplayNode { transition.updateAlpha(node: self.shortTitleNode, alpha: deselectionAlpha) transition.updateAlpha(node: self.shortTitleActiveNode, alpha: selectionAlpha) - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: presentationData.theme.list.itemSecondaryTextColor) - self.titleActiveNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: presentationData.theme.list.itemAccentColor) - self.shortTitleNode.attributedText = NSAttributedString(string: shortTitle, font: Font.medium(14.0), textColor: presentationData.theme.list.itemSecondaryTextColor) - self.shortTitleActiveNode.attributedText = NSAttributedString(string: shortTitle, font: Font.medium(14.0), textColor: presentationData.theme.list.itemAccentColor) + if themeUpdated || titleUpdated { + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: presentationData.theme.list.itemSecondaryTextColor) + self.titleActiveNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: presentationData.theme.list.itemAccentColor) + self.shortTitleNode.attributedText = NSAttributedString(string: shortTitle, font: Font.medium(14.0), textColor: presentationData.theme.list.itemSecondaryTextColor) + self.shortTitleActiveNode.attributedText = NSAttributedString(string: shortTitle, font: Font.medium(14.0), textColor: presentationData.theme.list.itemAccentColor) + } + if unreadCount != 0 { - self.badgeTextNode.attributedText = NSAttributedString(string: "\(unreadCount)", font: Font.regular(14.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) - let badgeSelectionFraction: CGFloat = unreadHasUnmuted ? 1.0 : selectionFraction + if themeUpdated || unreadCountUpdated || self.badgeTextNode.attributedText == nil { + self.badgeTextNode.attributedText = NSAttributedString(string: "\(unreadCount)", font: Font.regular(14.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) + } + let badgeSelectionFraction: CGFloat = unreadHasUnmuted ? 1.0 : selectionFraction let badgeSelectionAlpha: CGFloat = badgeSelectionFraction //let badgeDeselectionAlpha: CGFloat = 1.0 - badgeSelectionFraction diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsAlbumItem.swift b/submodules/MediaPickerUI/Sources/MediaGroupsAlbumItem.swift index 8caeec2bae..670c099d96 100644 --- a/submodules/MediaPickerUI/Sources/MediaGroupsAlbumItem.swift +++ b/submodules/MediaPickerUI/Sources/MediaGroupsAlbumItem.swift @@ -140,7 +140,6 @@ class MediaGroupsAlbumItemNode: ListViewItemNode { init() { self.backgroundNode = ASDisplayNode() self.backgroundNode.isLayerBacked = true - self.backgroundNode.backgroundColor = .white self.topStripeNode = ASDisplayNode() self.topStripeNode.isLayerBacked = true @@ -226,7 +225,6 @@ class MediaGroupsAlbumItemNode: ListViewItemNode { if let _ = updatedTheme { strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor - strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor strongSelf.iconNode.image = generateTintedImage(image: item.icon?.image, color: item.presentationData.theme.list.itemAccentColor) diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift b/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift index effd22b390..a1a18dd424 100644 --- a/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift @@ -149,11 +149,23 @@ private func preparedTransition(from fromEntries: [MediaGroupsEntry], to toEntri return MediaGroupsTransition(deletions: deletions, insertions: insertions, updates: updates) } -public final class MediaGroupsScreen: ViewController { +public final class MediaGroupsScreen: ViewController, AttachmentContainable { + public var requestAttachmentMenuExpansion: () -> Void = {} + public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } + public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + public var cancelPanGesture: () -> Void = { } + public var isContainerPanning: () -> Bool = { return false } + public var isContainerExpanded: () -> Bool = { return false } + + public var mediaPickerContext: AttachmentMediaPickerContext? { + return nil + } + private let context: AccountContext private var presentationData: PresentationData private var presentationDataDisposable: Disposable? private let mediaAssetsContext: MediaAssetsContext + private let embedded: Bool private let openGroup: (PHAssetCollection) -> Void private class Node: ViewControllerTracingNode { @@ -166,6 +178,7 @@ public final class MediaGroupsScreen: ViewController { private var presentationData: PresentationData private let containerNode: ASDisplayNode + private let backgroundNode: NavigationBackgroundNode private let listNode: ListView private var nextStableId: Int = 1 @@ -188,12 +201,17 @@ public final class MediaGroupsScreen: ViewController { self.presentationData = controller.presentationData self.containerNode = ASDisplayNode() + self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor) + self.listNode = ListView() super.init() - self.containerNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor - + if !controller.embedded { + self.addSubnode(self.backgroundNode) + } else { + self.containerNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + } self.addSubnode(self.containerNode) self.containerNode.addSubnode(self.listNode) @@ -209,6 +227,10 @@ public final class MediaGroupsScreen: ViewController { self.listNode.beganInteractiveDragging = { [weak self] _ in self?.view.window?.endEditing(true) } + + self.listNode.visibleContentOffsetChanged = { [weak self] _ in + self?.updateNavigation(transition: .immediate) + } } deinit { @@ -284,6 +306,11 @@ public final class MediaGroupsScreen: ViewController { func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData + + if self.controller?.embedded == true { + self.containerNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + } + self.backgroundNode.updateColor(color: self.presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate) } private func enqueueTransaction(_ transaction: MediaGroupsTransition) { @@ -320,10 +347,19 @@ public final class MediaGroupsScreen: ViewController { } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + guard let controller = self.controller else { + return + } let firstTime = self.validLayout == nil self.validLayout = (layout, navigationBarHeight) - transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + 12.0), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight - 12.0))) + let topInset: CGFloat = controller.embedded ? 12.0 : 0.0 + + let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + topInset), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight - topInset)) + transition.updateFrame(node: self.containerNode, frame: containerFrame) + + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: .zero, size: layout.size)) + self.backgroundNode.update(size: layout.size, transition: transition) let size = layout.size let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) @@ -334,6 +370,28 @@ public final class MediaGroupsScreen: ViewController { self.dequeueTransaction() } } + + private var previousContentOffset: GridNodeVisibleContentOffset? + func updateNavigation(delayDisappear: Bool = false, transition: ContainedViewLayoutTransition) { + var previousContentOffsetValue: CGFloat? + if let previousContentOffset = self.previousContentOffset, case let .known(value) = previousContentOffset { + previousContentOffsetValue = value + } + + let offset = self.listNode.visibleContentOffset() + switch offset { + case let .known(value): + let transition: ContainedViewLayoutTransition + if let previousContentOffsetValue = previousContentOffsetValue, value <= 0.0, previousContentOffsetValue > 2.0 { + transition = .animated(duration: 0.2, curve: .easeInOut) + } else { + transition = .immediate + } + self.controller?.navigationBar?.updateBackgroundAlpha(min(2.0, value) / 2.0, transition: transition) + case .unknown, .none: + self.controller?.navigationBar?.updateBackgroundAlpha(1.0, transition: .immediate) + } + } } private var validLayout: ContainerViewLayout? @@ -347,13 +405,14 @@ public final class MediaGroupsScreen: ViewController { return self._ready } - init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mediaAssetsContext: MediaAssetsContext, openGroup: @escaping (PHAssetCollection) -> Void) { + init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mediaAssetsContext: MediaAssetsContext, embedded: Bool = false, openGroup: @escaping (PHAssetCollection) -> Void) { self.context = context self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.mediaAssetsContext = mediaAssetsContext + self.embedded = embedded self.openGroup = openGroup - super.init(navigationBarPresentationData: nil) + super.init(navigationBarPresentationData: !embedded ? NavigationBarPresentationData(presentationData: presentationData) : nil) self.statusBar.statusBarStyle = .Ignore @@ -376,6 +435,12 @@ public final class MediaGroupsScreen: ViewController { strongSelf.controllerNode.scrollToTop() } } + + if !embedded { + self.title = "Albums" + + self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed)) + } } required init(coder aDecoder: NSCoder) { @@ -393,6 +458,20 @@ public final class MediaGroupsScreen: ViewController { super.displayNodeDidLoad() } + + @objc private func backPressed() { + if let _ = self.navigationController { + self.dismiss() + } else { + self.updateNavigationStack { current in + var mediaPickerContext: AttachmentMediaPickerContext? + if let first = current.first as? MediaPickerScreen { + mediaPickerContext = first.webSearchController?.mediaPickerContext ?? first.mediaPickerContext + } + return (current.filter { $0 !== self }, mediaPickerContext) + } + } + } override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 54206cdf45..b2ef7e0a16 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -169,7 +169,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { public var presentWebSearch: (MediaGroupsScreen, Bool) -> Void = { _, _ in } public var getCaptionPanelView: () -> TGCaptionPanelView? = { return nil } - public var customSelection: ((Any) -> Void)? = nil + public var customSelection: ((MediaPickerScreen, Any) -> Void)? = nil private var completed = false public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void = { _, _, _, _, _ in } @@ -226,7 +226,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { private var itemsDimensionsUpdatedDisposable: Disposable? private var hiddenMediaDisposable: Disposable? - private let hiddenMediaId = Promise(nil) + fileprivate let hiddenMediaId = Promise(nil) private var selectionGesture: MediaPickerGridSelectionGesture? @@ -783,7 +783,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { if let customSelection = controller.customSelection { self.openingMedia = true - customSelection(fetchResult[index]) + customSelection(controller, fetchResult[index]) Queue.mainQueue().after(0.3) { self.openingMedia = false } @@ -879,7 +879,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { if let customSelection = controller.customSelection { self.openingMedia = true - customSelection(draft) + customSelection(controller, draft) Queue.mainQueue().after(0.3) { self.openingMedia = false } @@ -1433,13 +1433,17 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.navigationItem.titleView = self.titleView - if case let .assets(_, mode) = self.subject, mode != .default { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) - - if mode == .story { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) - self.navigationItem.rightBarButtonItem?.action = #selector(self.rightButtonPressed) - self.navigationItem.rightBarButtonItem?.target = self + if case let .assets(collection, mode) = self.subject, mode != .default { + if collection == nil { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + + if mode == .story { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) + self.navigationItem.rightBarButtonItem?.action = #selector(self.rightButtonPressed) + self.navigationItem.rightBarButtonItem?.target = self + } + } else { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed)) } } else { if case let .assets(collection, _) = self.subject, collection != nil { @@ -1806,9 +1810,16 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { guard self.moreButtonNode.iconNode.iconState == .search, case let .assets(_, mode) = self.subject else { return } - self.requestAttachmentMenuExpansion() - let groupsController = MediaGroupsScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, mediaAssetsContext: self.controllerNode.mediaAssetsContext, openGroup: { [weak self] collection in + var embedded = true + if case .story = mode { + embedded = false + } else { + self.requestAttachmentMenuExpansion() + } + + var updateNavigationStackImpl: ((AttachmentContainable) -> Void)? + let groupsController = MediaGroupsScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, mediaAssetsContext: self.controllerNode.mediaAssetsContext, embedded: embedded, openGroup: { [weak self] collection in if let strongSelf = self { let mediaPicker = MediaPickerScreen(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: strongSelf.peer, threadTitle: strongSelf.threadTitle, chatLocation: strongSelf.chatLocation, bannedSendPhotos: strongSelf.bannedSendPhotos, bannedSendVideos: strongSelf.bannedSendVideos, subject: .assets(collection, mode), editingContext: strongSelf.interaction?.editingState, selectionContext: strongSelf.interaction?.selectionState) @@ -1816,17 +1827,34 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { mediaPicker.presentTimerPicker = strongSelf.presentTimerPicker mediaPicker.getCaptionPanelView = strongSelf.getCaptionPanelView mediaPicker.legacyCompletion = strongSelf.legacyCompletion + mediaPicker.customSelection = strongSelf.customSelection mediaPicker.dismissAll = { [weak self] in self?.dismiss(animated: true, completion: nil) } mediaPicker._presentedInModal = true mediaPicker.updateNavigationStack = strongSelf.updateNavigationStack - strongSelf.updateNavigationStack({ _ in return ([strongSelf, mediaPicker], strongSelf.mediaPickerContext)}) + + updateNavigationStackImpl?(mediaPicker) } }) + groupsController.updateNavigationStack = self.updateNavigationStack + if !embedded { + groupsController._presentedInModal = true + } + + updateNavigationStackImpl = { [weak self, weak groupsController] c in + if let self { + if case .story = mode, let groupsController { + self.updateNavigationStack({ _ in return ([self, groupsController, c], self.mediaPickerContext)}) + } else { + self.updateNavigationStack({ _ in return ([self, c], self.mediaPickerContext)}) + } + } + } + if case .story = mode { - self.present(groupsController, in: .current) + self.updateNavigationStack({ _ in return ([self, groupsController], self.mediaPickerContext)}) } else { self.presentWebSearch(groupsController, activateOnDisplay) } @@ -1928,6 +1956,10 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { return self.controllerNode.transitionImage(for: identifier) } + func updateHiddenMediaId(_ id: String?) { + self.controllerNode.hiddenMediaId.set(.single(id)) + } + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) @@ -2113,7 +2145,7 @@ public func wallpaperMediaPickerController( updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, animateAppearance: Bool, - completion: @escaping (Any) -> Void = { _ in }, + completion: @escaping (MediaPickerScreen, Any) -> Void = { _, _ in }, openColors: @escaping () -> Void ) -> ViewController { let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { @@ -2135,7 +2167,7 @@ public func wallpaperMediaPickerController( public func storyMediaPickerController( context: AccountContext, - completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping () -> (UIView, CGRect)?) -> Void = { _, _, _, _, _ in }, + completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping () -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void ) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme) @@ -2145,11 +2177,9 @@ public func storyMediaPickerController( }) controller.requestController = { _, present in let mediaPickerController = MediaPickerScreen(context: context, updatedPresentationData: updatedPresentationData, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .story), mainButtonState: nil, mainButtonAction: nil) - mediaPickerController.customSelection = { [weak mediaPickerController] result in - guard let controller = mediaPickerController else { - return - } + mediaPickerController.customSelection = { controller, result in if let result = result as? MediaEditorDraft { + controller.updateHiddenMediaId(result.path) if let transitionView = controller.transitionView(for: result.path, snapshot: false) { let transitionOut: () -> (UIView, CGRect)? = { if let transitionView = controller.transitionView(for: result.path, snapshot: false) { @@ -2157,9 +2187,12 @@ public func storyMediaPickerController( } return nil } - completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.path), transitionOut) + completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.path), transitionOut, { [weak controller] in + controller?.updateHiddenMediaId(nil) + }) } } else if let result = result as? PHAsset { + controller.updateHiddenMediaId(result.localIdentifier) if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) { let transitionOut: () -> (UIView, CGRect)? = { if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) { @@ -2167,7 +2200,9 @@ public func storyMediaPickerController( } return nil } - completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.localIdentifier), transitionOut) + completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.localIdentifier), transitionOut, { [weak controller] in + controller?.updateHiddenMediaId(nil) + }) } } } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift index e48d870b65..2ace0bffe4 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift @@ -155,7 +155,7 @@ public final class ThemeGridController: ViewController { } let controller = MediaPickerScreen(context: strongSelf.context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .wallpaper)) - controller.customSelection = { [weak self] asset in + controller.customSelection = { [weak self] _, asset in guard let strongSelf = self, let asset = asset as? PHAsset else { return } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index 4b242cfb85..4d870443c5 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -137,7 +137,7 @@ private final class CameraScreenComponent: CombinedComponent { private var cameraStateDisposable: Disposable? private var resultDisposable = MetaDisposable() - private var mediaAssetsContext: MediaAssetsContext + private var mediaAssetsContext: MediaAssetsContext? fileprivate var lastGalleryAsset: PHAsset? private var lastGalleryAssetsDisposable: Disposable? @@ -150,8 +150,6 @@ private final class CameraScreenComponent: CombinedComponent { self.present = present self.completion = completion - self.mediaAssetsContext = MediaAssetsContext() - super.init() self.cameraStateDisposable = (camera.flashMode @@ -163,7 +161,21 @@ private final class CameraScreenComponent: CombinedComponent { self.updated(transition: .easeInOut(duration: 0.2)) }) - self.lastGalleryAssetsDisposable = (self.mediaAssetsContext.recentAssets() + Queue.mainQueue().async { + self.setupRecentAssetSubscription() + } + } + + deinit { + self.cameraStateDisposable?.dispose() + self.lastGalleryAssetsDisposable?.dispose() + self.resultDisposable.dispose() + } + + func setupRecentAssetSubscription() { + let mediaAssetsContext = MediaAssetsContext() + self.mediaAssetsContext = mediaAssetsContext + self.lastGalleryAssetsDisposable = (mediaAssetsContext.recentAssets() |> map { fetchResult in return fetchResult?.lastObject } @@ -176,12 +188,6 @@ private final class CameraScreenComponent: CombinedComponent { }) } - deinit { - self.cameraStateDisposable?.dispose() - self.lastGalleryAssetsDisposable?.dispose() - self.resultDisposable.dispose() - } - func updateCameraMode(_ mode: CameraMode) { self.cameraState = self.cameraState.updatedMode(mode) self.updated(transition: .spring(duration: 0.3)) @@ -818,7 +824,8 @@ public class CameraScreen: ViewController { } } }, - nil + nil, + {} ) } } @@ -1102,7 +1109,7 @@ public class CameraScreen: ViewController { self.validLayout = layout let previewSize = CGSize(width: layout.size.width, height: floorToScreenPixels(layout.size.width * 1.77778)) - let topInset: CGFloat = floor(layout.size.height - previewSize.height) / 2.0 + let topInset: CGFloat = floorToScreenPixels(layout.size.height - previewSize.height) / 2.0 let environment = ViewControllerComponentContainer.Environment( statusBarHeight: layout.statusBarHeight ?? 0.0, @@ -1227,7 +1234,7 @@ public class CameraScreen: ViewController { self.transitionOut = transitionOut } } - fileprivate let completion: (Signal, ResultTransition?) -> Void + fileprivate let completion: (Signal, ResultTransition?, @escaping () -> Void) -> Void public var transitionedIn: () -> Void = {} private var audioSessionDisposable: Disposable? @@ -1238,7 +1245,7 @@ public class CameraScreen: ViewController { holder: CameraHolder? = nil, transitionIn: TransitionIn?, transitionOut: @escaping (Bool) -> TransitionOut?, - completion: @escaping (Signal, ResultTransition?) -> Void + completion: @escaping (Signal, ResultTransition?, @escaping () -> Void) -> Void ) { self.context = context self.mode = mode @@ -1293,7 +1300,7 @@ public class CameraScreen: ViewController { self.node.pauseCameraCapture() } - let controller = self.context.sharedContext.makeMediaPickerScreen(context: self.context, completion: { [weak self] result, transitionView, transitionRect, transitionImage, transitionOut in + let controller = self.context.sharedContext.makeMediaPickerScreen(context: self.context, completion: { [weak self] result, transitionView, transitionRect, transitionImage, transitionOut, dismissed in if let self { stopCameraCapture() @@ -1304,9 +1311,9 @@ public class CameraScreen: ViewController { transitionOut: transitionOut ) if let asset = result as? PHAsset { - self.completion(.single(.asset(asset)), resultTransition) + self.completion(.single(.asset(asset)), resultTransition, dismissed) } else if let draft = result as? MediaEditorDraft { - self.completion(.single(.draft(draft)), resultTransition) + self.completion(.single(.draft(draft)), resultTransition, dismissed) } } }, dismissed: { [weak self] in @@ -1359,7 +1366,7 @@ public class CameraScreen: ViewController { let offsetX = floorToScreenPixels((1.0 - transitionFraction) * self.node.frame.width * -1.0) transition.updateTransform(layer: self.node.backgroundView.layer, transform: CGAffineTransform(translationX: offsetX, y: 0.0)) transition.updateTransform(layer: self.node.containerView.layer, transform: CGAffineTransform(translationX: offsetX, y: 0.0)) - let scale = max(0.8, min(1.0, 0.8 + 0.2 * transitionFraction)) + let scale: CGFloat = max(0.8, min(1.0, 0.8 + 0.2 * transitionFraction)) transition.updateSublayerTransformScaleAndOffset(layer: self.node.containerView.layer, scale: scale, offset: CGPoint(x: -offsetX * 1.0 / scale * 0.5, y: 0.0), completion: { _ in completion() }) @@ -1367,8 +1374,6 @@ public class CameraScreen: ViewController { let dimAlpha = 0.6 * (1.0 - transitionFraction) transition.updateAlpha(layer: self.node.transitionDimView.layer, alpha: dimAlpha) transition.updateTransform(layer: self.node.transitionCornersView.layer, transform: CGAffineTransform(translationX: offsetX, y: 0.0)) - - self.statusBar.updateStatusBarStyle(transitionFraction > 0.45 ? .White : .Ignore, animated: true) if let navigationController = self.navigationController as? NavigationController { let offsetX = floorToScreenPixels(transitionFraction * self.node.frame.width) @@ -1380,8 +1385,10 @@ public class CameraScreen: ViewController { self.isTransitioning = false if dismissing { if transitionFraction < 0.7 || velocity < -1000.0 { + self.statusBar.updateStatusBarStyle(.Ignore, animated: true) self.requestDismiss(animated: true, interactive: true) } else { + self.statusBar.updateStatusBarStyle(.White, animated: true) self.updateTransitionProgress(1.0, transition: .animated(duration: 0.4, curve: .spring), completion: { [weak self] in if let self, let navigationController = self.navigationController as? NavigationController { navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate) @@ -1390,6 +1397,7 @@ public class CameraScreen: ViewController { } } else { if transitionFraction > 0.33 || velocity > 1000.0 { + self.statusBar.updateStatusBarStyle(.White, animated: true) self.updateTransitionProgress(1.0, transition: .animated(duration: 0.4, curve: .spring), completion: { [weak self] in if let self, let navigationController = self.navigationController as? NavigationController { navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate) @@ -1398,6 +1406,7 @@ public class CameraScreen: ViewController { } }) } else { + self.statusBar.updateStatusBarStyle(.Ignore, animated: true) self.requestDismiss(animated: true, interactive: true) } } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index 74692b08db..ac0b1908a3 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -499,6 +499,8 @@ public final class MediaEditor { let trimStart = self.values.videoTrimRange?.lowerBound ?? 0.0 let trimRange = trimStart ..< trimEnd self.values = self.values.withUpdatedVideoTrimRange(trimRange) + + self.player?.currentItem?.forwardPlaybackEndTime = CMTime(seconds: trimEnd, preferredTimescale: CMTimeScale(NSEC_PER_SEC)) } public func setDrawingAndEntities(data: Data?, image: UIImage?, entities: [CodableDrawingEntity]) { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift index 30d7645c89..03abd3ed8f 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift @@ -920,17 +920,38 @@ extension CodableToolValue: Codable { } } +private let hasHEVCHardwareEncoder: Bool = { + let spec: [CFString: Any] = [:] + var outID: CFString? + var properties: CFDictionary? + let result = VTCopySupportedPropertyDictionaryForEncoder(width: 1920, height: 1080, codecType: kCMVideoCodecType_HEVC, encoderSpecification: spec as CFDictionary, encoderIDOut: &outID, supportedPropertiesOut: &properties) + if result == kVTCouldNotFindVideoEncoderErr { + return false + } + return result == noErr +}() + public func recommendedVideoExportConfiguration(values: MediaEditorValues, frameRate: Float) -> MediaEditorVideoExport.Configuration { - let compressionProperties: [String: Any] = [ - AVVideoAverageBitRateKey: 2000000, - AVVideoProfileLevelKey: kVTProfileLevel_HEVC_Main_AutoLevel - //AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel, - //AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC - ] + let compressionProperties: [String: Any] + let codecType: AVVideoCodecType + + if hasHEVCHardwareEncoder { + codecType = AVVideoCodecType.hevc + compressionProperties = [ + AVVideoAverageBitRateKey: 2000000, + AVVideoProfileLevelKey: kVTProfileLevel_HEVC_Main_AutoLevel + ] + } else { + codecType = AVVideoCodecType.h264 + compressionProperties = [ + AVVideoAverageBitRateKey: 2000000, + AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel, + AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC + ] + } let videoSettings: [String: Any] = [ - //AVVideoCodecKey: AVVideoCodecType.h264, - AVVideoCodecKey: AVVideoCodecType.hevc, + AVVideoCodecKey: codecType, AVVideoCompressionPropertiesKey: compressionProperties, AVVideoWidthKey: 720, AVVideoHeightKey: 1280 diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index c3b4d9f6cc..fbbf76d448 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -1507,7 +1507,7 @@ public final class MediaEditorScreen: ViewController { self.validLayout = layout let previewSize = CGSize(width: layout.size.width, height: floorToScreenPixels(layout.size.width * 1.77778)) - let topInset: CGFloat = floor(layout.size.height - previewSize.height) / 2.0 + let topInset: CGFloat = floorToScreenPixels(layout.size.height - previewSize.height) / 2.0 let environment = ViewControllerComponentContainer.Environment( statusBarHeight: layout.statusBarHeight ?? 0.0, @@ -1725,6 +1725,7 @@ public final class MediaEditorScreen: ViewController { public var cancelled: (Bool) -> Void = { _ in } public var completion: (MediaEditorScreen.Result, @escaping () -> Void, MediaEditorResultPrivacy) -> Void = { _, _, _ in } + public var dismissed: () -> Void = { } public init( context: AccountContext, @@ -2008,6 +2009,7 @@ public final class MediaEditorScreen: ViewController { self.node.animateOut(finished: false, completion: { [weak self] in self?.dismiss() + self?.dismissed() }) } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift index a072eb531b..d2a066a61a 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift @@ -880,7 +880,7 @@ public final class MediaToolsScreen: ViewController { self.validLayout = layout let previewSize = CGSize(width: layout.size.width, height: floorToScreenPixels(layout.size.width * 1.77778)) - let topInset: CGFloat = floor(layout.size.height - previewSize.height) / 2.0 + let topInset: CGFloat = floorToScreenPixels(layout.size.height - previewSize.height) / 2.0 let environment = ViewControllerComponentContainer.Environment( statusBarHeight: layout.statusBarHeight ?? 0.0, diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 107c044084..eaa5f5a4ad 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -18696,7 +18696,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G updatedPresentationData: strongSelf.updatedPresentationData, peer: EnginePeer(peer), animateAppearance: animateAppearance, - completion: { [weak self] result in + completion: { [weak self] _, result in guard let strongSelf = self, let asset = result as? PHAsset else { return } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 96527a96c2..9f96b37970 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1829,7 +1829,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, parentNavigationController: parentNavigationController, sendSticker: sendSticker) } - public func makeMediaPickerScreen(context: AccountContext, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping () -> (UIView, CGRect)?) -> Void, dismissed: @escaping () -> Void) -> ViewController { + public func makeMediaPickerScreen(context: AccountContext, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping () -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController { return storyMediaPickerController(context: context, completion: completion, dismissed: dismissed) } diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 1b634103f0..a9a61db960 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -320,7 +320,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon return nil } }, - completion: { result, resultTransition in + completion: { result, resultTransition, dismissed in let subject: Signal = result |> map { value -> MediaEditorScreen.Subject? in switch value { @@ -488,6 +488,9 @@ public final class TelegramRootController: NavigationController, TelegramRootCon } returnToCameraImpl?() } + controller.dismissed = { + dismissed() + } presentImpl?(controller) } )