Merge commit '1ac9959731ec19c154f90ebf9c557f1d91c3038b'

This commit is contained in:
Ali 2023-05-31 21:26:20 +04:00
commit 787d9597a0
32 changed files with 1010 additions and 372 deletions

View File

@ -891,7 +891,7 @@ public protocol SharedAccountContext: AnyObject {
func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, 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

View File

@ -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
}()

View File

@ -2410,6 +2410,38 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return nil
}
fileprivate func openStoryCamera() {
var cameraTransitionIn: StoryCameraTransitionIn?
if let componentView = self.chatListHeaderView() {
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) {
cameraTransitionIn = StoryCameraTransitionIn(
sourceView: transitionView,
sourceRect: transitionView.bounds,
sourceCornerRadius: transitionView.bounds.height * 0.5
)
}
}
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
let coordinator = rootController.openStoryCamera(transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: { [weak self] _ in
guard let self else {
return nil
}
if let componentView = self.chatListHeaderView() {
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) {
return StoryCameraTransitionOut(
destinationView: transitionView,
destinationRect: transitionView.bounds,
destinationCornerRadius: transitionView.bounds.height * 0.5
)
}
}
return nil
})
coordinator?.animateIn()
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
@ -2442,35 +2474,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
if let peer, peer.id == self.context.account.peerId, storyContentState.slice == nil {
var cameraTransitionIn: StoryCameraTransitionIn?
if let componentView = self.chatListHeaderView() {
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) {
cameraTransitionIn = StoryCameraTransitionIn(
sourceView: transitionView,
sourceRect: transitionView.bounds,
sourceCornerRadius: transitionView.bounds.height * 0.5
)
}
}
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
rootController.openStoryCamera(transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: { [weak self] _ in
guard let self else {
return nil
}
if let componentView = self.chatListHeaderView() {
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) {
return StoryCameraTransitionOut(
destinationView: transitionView,
destinationRect: transitionView.bounds,
destinationCornerRadius: transitionView.bounds.height * 0.5
)
}
}
return nil
})
}
self.openStoryCamera()
return
}
@ -4862,6 +4866,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
@ -4990,12 +4997,16 @@ private final class ChatListLocationContext {
var leftButton: AnyComponentWithIdentity<NavigationButtonComponentEnvironment>?
var rightButton: AnyComponentWithIdentity<NavigationButtonComponentEnvironment>?
var proxyButton: AnyComponentWithIdentity<NavigationButtonComponentEnvironment>?
var storyButton: AnyComponentWithIdentity<NavigationButtonComponentEnvironment>?
var rightButtons: [AnyComponentWithIdentity<NavigationButtonComponentEnvironment>] {
var result: [AnyComponentWithIdentity<NavigationButtonComponentEnvironment>] = []
if let rightButton = self.rightButton {
result.append(rightButton)
}
if let storyButton = self.storyButton {
result.append(storyButton)
}
if let proxyButton = self.proxyButton {
result.append(proxyButton)
}
@ -5452,6 +5463,16 @@ private final class ChatListLocationContext {
self.proxyButton = nil
}
self.storyButton = AnyComponentWithIdentity(id: "story", component: AnyComponent(NavigationButtonComponent(
content: .icon(imageName: "Chat List/AddStoryIcon"),
pressed: { [weak self] _ in
guard let self, let parentController = self.parentController else {
return
}
parentController.openStoryCamera()
}
)))
self.chatListTitle = titleContent
if case .chatList(.root) = self.location, checkProxy {

View File

@ -1186,13 +1186,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 {

View File

@ -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

View File

@ -21,11 +21,13 @@ public final class LottieAnimationComponent: Component {
public var name: String
public var mode: Mode
public var range: (CGFloat, CGFloat)?
public var waitForCompletion: Bool
public init(name: String, mode: Mode, range: (CGFloat, CGFloat)? = nil) {
public init(name: String, mode: Mode, range: (CGFloat, CGFloat)? = nil, waitForCompletion: Bool = true) {
self.name = name
self.mode = mode
self.range = range
self.waitForCompletion = waitForCompletion
}
public static func == (lhs: LottieAnimationComponent.AnimationItem, rhs: LottieAnimationComponent.AnimationItem) -> Bool {
@ -157,7 +159,7 @@ public final class LottieAnimationComponent: Component {
}
}
if let animationView = self.animationView, animationView.isAnimationPlaying {
if let animationView = self.animationView, animationView.isAnimationPlaying && component.animation.waitForCompletion {
updateComponent = false
self.currentCompletion = { [weak self] in
guard let strongSelf = self else {

View File

@ -8,6 +8,21 @@ public struct Font {
case monospace
case round
case camera
var key: String {
switch self {
case .regular:
return "regular"
case .serif:
return "serif"
case .monospace:
return "monospace"
case .round:
return "round"
case .camera:
return "camera"
}
}
}
public struct Traits: OptionSet {
@ -58,9 +73,58 @@ public struct Font {
return .regular
}
}
var key: String {
switch self {
case .regular:
return "regular"
case .light:
return "light"
case .medium:
return "medium"
case .semibold:
return "semibold"
case .bold:
return "bold"
case .heavy:
return "heavy"
}
}
}
private final class Cache {
private var lock: pthread_rwlock_t
private var fonts: [String: UIFont] = [:]
init() {
self.lock = pthread_rwlock_t()
let status = pthread_rwlock_init(&self.lock, nil)
assert(status == 0)
}
func get(_ key: String) -> UIFont? {
let font: UIFont?
pthread_rwlock_rdlock(&self.lock)
font = self.fonts[key]
pthread_rwlock_unlock(&self.lock)
return font
}
func set(_ font: UIFont, key: String) {
pthread_rwlock_wrlock(&self.lock)
self.fonts[key] = font
pthread_rwlock_unlock(&self.lock)
}
}
private static let cache = Cache()
public static func with(size: CGFloat, design: Design = .regular, weight: Weight = .regular, traits: Traits = []) -> UIFont {
let key = "\(size)_\(design.key)_\(weight.key)_\(traits.rawValue)"
if let cachedFont = self.cache.get(key) {
return cachedFont
}
if #available(iOS 13.0, *), design != .camera {
let descriptor: UIFontDescriptor
if #available(iOS 14.0, *) {
@ -101,45 +165,51 @@ public struct Font {
}
}
let font: UIFont
if let updatedDescriptor = updatedDescriptor {
return UIFont(descriptor: updatedDescriptor, size: size)
font = UIFont(descriptor: updatedDescriptor, size: size)
} else {
return UIFont(descriptor: descriptor, size: size)
font = UIFont(descriptor: descriptor, size: size)
}
self.cache.set(font, key: key)
return font
} else {
let font: UIFont
switch design {
case .regular:
if traits.contains(.italic) {
if let descriptor = UIFont.systemFont(ofSize: size, weight: weight.weight).fontDescriptor.withSymbolicTraits([.traitItalic]) {
return UIFont(descriptor: descriptor, size: size)
font = UIFont(descriptor: descriptor, size: size)
} else {
return UIFont.italicSystemFont(ofSize: size)
font = UIFont.italicSystemFont(ofSize: size)
}
} else {
return UIFont.systemFont(ofSize: size, weight: weight.weight)
}
case .serif:
if weight.isBold && traits.contains(.italic) {
return UIFont(name: "Georgia-BoldItalic", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
font = UIFont(name: "Georgia-BoldItalic", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
} else if weight.isBold {
return UIFont(name: "Georgia-Bold", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
font = UIFont(name: "Georgia-Bold", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
} else if traits.contains(.italic) {
return UIFont(name: "Georgia-Italic", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
font = UIFont(name: "Georgia-Italic", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
} else {
return UIFont(name: "Georgia", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
font = UIFont(name: "Georgia", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
}
case .monospace:
if weight.isBold && traits.contains(.italic) {
return UIFont(name: "Menlo-BoldItalic", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
font = UIFont(name: "Menlo-BoldItalic", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
} else if weight.isBold {
return UIFont(name: "Menlo-Bold", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
font = UIFont(name: "Menlo-Bold", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
} else if traits.contains(.italic) {
return UIFont(name: "Menlo-Italic", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
font = UIFont(name: "Menlo-Italic", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
} else {
return UIFont(name: "Menlo", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
font = UIFont(name: "Menlo", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
}
case .round:
return UIFont(name: ".SFCompactRounded-Semibold", size: size) ?? UIFont.systemFont(ofSize: size)
font = UIFont(name: ".SFCompactRounded-Semibold", size: size) ?? UIFont.systemFont(ofSize: size)
case .camera:
func encodeText(string: String, key: Int16) -> String {
let nsString = string as NSString
@ -152,11 +222,15 @@ public struct Font {
return result as String
}
if case .semibold = weight {
return UIFont(name: encodeText(string: "TGDbnfsb.Tfnjcpme", key: -1), size: size) ?? UIFont.systemFont(ofSize: size, weight: weight.weight)
font = UIFont(name: encodeText(string: "TGDbnfsb.Tfnjcpme", key: -1), size: size) ?? UIFont.systemFont(ofSize: size, weight: weight.weight)
} else {
return UIFont(name: encodeText(string: "TGDbnfsb.Sfhvmbs", key: -1), size: size) ?? UIFont.systemFont(ofSize: size, weight: weight.weight)
font = UIFont(name: encodeText(string: "TGDbnfsb.Sfhvmbs", key: -1), size: size) ?? UIFont.systemFont(ofSize: size, weight: weight.weight)
}
}
self.cache.set(font, key: key)
return font
}
}

View File

@ -149,10 +149,10 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
}
}
public static func encodeEntities(_ entities: [DrawingEntity], entitiesView: DrawingEntitiesView? = nil) -> Data? {
public static func encodeEntities(_ entities: [DrawingEntity], entitiesView: DrawingEntitiesView? = nil) -> [CodableDrawingEntity] {
let entities = entities
guard !entities.isEmpty else {
return nil
return []
}
if let entitiesView {
for entity in entities {
@ -161,7 +161,11 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
}
}
}
let codableEntities = entities.compactMap({ CodableDrawingEntity(entity: $0) })
return entities.compactMap({ CodableDrawingEntity(entity: $0) })
}
public static func encodeEntitiesData(_ entities: [DrawingEntity], entitiesView: DrawingEntitiesView? = nil) -> Data? {
let codableEntities = encodeEntities(entities, entitiesView: entitiesView)
if let data = try? JSONEncoder().encode(codableEntities) {
return data
} else {
@ -170,7 +174,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
}
var entitiesData: Data? {
return DrawingEntitiesView.encodeEntities(self.entities, entitiesView: self)
return DrawingEntitiesView.encodeEntitiesData(self.entities, entitiesView: self)
}
var hasChanges: Bool {

View File

@ -2392,6 +2392,8 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
}
func animateIn() {
self.entitiesView.selectEntity(nil)
if let view = self.componentHost.findTaggedView(tag: topGradientTag) {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
@ -2797,15 +2799,11 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
}
public func animateOut(_ completion: @escaping (() -> Void)) {
self.selectionContainerView.alpha = 0.0
self.entitiesView.selectEntity(nil)
self.node.animateOut(completion: {
completion()
})
//
// Queue.mainQueue().after(0.4) {
// self.node.isHidden = true
// }
}
private var orientation: UIInterfaceOrientation?
@ -2932,7 +2930,7 @@ public final class DrawingToolsInteraction {
self.activate()
}
func activate() {
public func activate() {
self.isActive = true
self.entitiesView.selectionContainerView = self.selectionContainerView

View File

@ -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)

View File

@ -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<PresentationData, NoError>)? = nil, mediaAssetsContext: MediaAssetsContext, openGroup: @escaping (PHAssetCollection) -> Void) {
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = 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)

View File

@ -75,11 +75,11 @@ final class MediaPickerGridItem: GridItem {
}
}
private let maskImage = generateImage(CGSize(width: 1.0, height: 24.0), opaque: false, rotatedContext: { size, context in
private let maskImage = generateImage(CGSize(width: 1.0, height: 36.0), opaque: false, rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
let gradientColors = [UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.withAlphaComponent(0.6).cgColor] as CFArray
let gradientColors = [UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.withAlphaComponent(0.45).cgColor] as CFArray
var locations: [CGFloat] = [0.0, 1.0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
@ -93,8 +93,10 @@ final class MediaPickerGridItemNode: GridItemNode {
var currentState: (PHFetchResult<PHAsset>, Int)?
var currentDraftState: (MediaEditorDraft, Int)?
var enableAnimations: Bool = true
var stories: Bool = false
private var selectable: Bool = false
private let backgroundNode: ASImageNode
private let imageNode: ImageNode
private var checkNode: InteractiveCheckNode?
private let gradientNode: ASImageNode
@ -115,6 +117,9 @@ final class MediaPickerGridItemNode: GridItemNode {
var selected: (() -> Void)?
override init() {
self.backgroundNode = ASImageNode()
self.backgroundNode.contentMode = .scaleToFill
self.imageNode = ImageNode()
self.imageNode.clipsToBounds = true
self.imageNode.contentMode = .scaleAspectFill
@ -273,8 +278,15 @@ final class MediaPickerGridItemNode: GridItemNode {
self.theme = theme
self.selectable = selectable
self.enableAnimations = enableAnimations
self.stories = stories
self.backgroundColor = theme.list.mediaPlaceholderColor
if stories {
if self.backgroundNode.supernode == nil {
self.insertSubnode(self.backgroundNode, at: 0)
}
}
if self.currentMediaState == nil || self.currentMediaState!.0.uniqueIdentifier != media.identifier || self.currentMediaState!.1 != index {
self.currentMediaState = (media.asset, index)
@ -290,9 +302,16 @@ final class MediaPickerGridItemNode: GridItemNode {
self.theme = theme
self.selectable = selectable
self.enableAnimations = enableAnimations
self.stories = stories
self.backgroundColor = theme.list.mediaPlaceholderColor
if stories {
if self.backgroundNode.supernode == nil {
self.insertSubnode(self.backgroundNode, at: 0)
}
}
if self.currentState == nil || self.currentState!.0 !== fetchResult || self.currentState!.1 != index {
let editingContext = interaction.editingState
let asset = fetchResult.object(at: index)
@ -334,6 +353,27 @@ final class MediaPickerGridItemNode: GridItemNode {
|> delay(0.03, queue: Queue.concurrentDefaultQueue())
)
if stories {
self.imageNode.contentUpdated = { [weak self] image in
if let self {
if self.backgroundNode.image == nil {
if let image, image.size.width > image.size.height {
self.imageNode.contentMode = .scaleAspectFit
Queue.concurrentDefaultQueue().async {
let colors = mediaEditorGetGradientColors(from: image)
let gradientImage = mediaEditorGenerateGradientImage(size: CGSize(width: 3.0, height: 128.0), colors: [colors.0, colors.1])
Queue.mainQueue().async {
self.backgroundNode.image = gradientImage
}
}
} else {
self.imageNode.contentMode = .scaleAspectFill
}
}
}
}
}
let originalSignal = assetImageSignal //assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false, synchronous: true)
let imageSignal: Signal<UIImage?, NoError> = editedSignal
|> mapToSignal { result in
@ -344,7 +384,7 @@ final class MediaPickerGridItemNode: GridItemNode {
}
}
self.imageNode.setSignal(imageSignal)
let spoilerSignal = Signal<Bool, NoError> { subscriber in
if let signal = editingContext.spoilerSignal(forIdentifier: asset.localIdentifier) {
let disposable = signal.start(next: { next in
@ -443,8 +483,9 @@ final class MediaPickerGridItemNode: GridItemNode {
override func layout() {
super.layout()
self.backgroundNode.frame = self.bounds
self.imageNode.frame = self.bounds.insetBy(dx: -1.0 + UIScreenPixel, dy: -1.0 + UIScreenPixel)
self.gradientNode.frame = CGRect(x: 0.0, y: self.bounds.height - 24.0, width: self.bounds.width, height: 24.0)
self.gradientNode.frame = CGRect(x: 0.0, y: self.bounds.height - 36.0, width: self.bounds.width, height: 36.0)
self.typeIconNode.frame = CGRect(x: 0.0, y: self.bounds.height - 20.0, width: 19.0, height: 19.0)
self.activateAreaNode.frame = self.bounds
@ -478,7 +519,20 @@ final class MediaPickerGridItemNode: GridItemNode {
}
func transitionImage() -> UIImage? {
return self.imageNode.image
if let backgroundImage = self.backgroundNode.image {
return generateImage(self.bounds.size, contextGenerator: { size, context in
if let cgImage = backgroundImage.cgImage {
context.draw(cgImage, in: CGRect(origin: .zero, size: size))
if let image = self.imageNode.image, let cgImage = image.cgImage {
let fittedSize = image.size.fitted(size)
let fittedFrame = CGRect(origin: CGPoint(x: (size.width - fittedSize.width) / 2.0, y: (size.height - fittedSize.height) / 2.0), size: fittedSize)
context.draw(cgImage, in: fittedFrame)
}
}
})
} else {
return self.imageNode.image
}
}
@objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) {

View File

@ -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<String?>(nil)
fileprivate let hiddenMediaId = Promise<String?>(nil)
private var selectionGesture: MediaPickerGridSelectionGesture<TGMediaSelectableItem>?
@ -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
}
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<PresentationData, NoError>)? = 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)
})
}
}
}

View File

@ -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
}

View File

@ -1410,7 +1410,7 @@ final class AvatarEditorScreenComponent: Component {
let colors: [NSNumber] = state.selectedBackground.colors.map { Int32(bitPattern: $0) as NSNumber }
let entitiesData = DrawingEntitiesView.encodeEntities([entity])
let entitiesData = DrawingEntitiesView.encodeEntitiesData([entity])
let paintingData = TGPaintingData(
drawing: nil,

View File

@ -137,21 +137,21 @@ 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?
var cameraState = CameraState(mode: .photo, flashMode: .off, flashModeDidChange: false, recording: .none, duration: 0.0)
var swipeHint: CaptureControlsComponent.SwipeHint = .none
private let hapticFeedback = HapticFeedback()
init(context: AccountContext, camera: Camera, present: @escaping (ViewController) -> Void, completion: ActionSlot<Signal<CameraScreen.Result, NoError>>) {
self.context = context
self.camera = camera
self.present = present
self.completion = completion
self.mediaAssetsContext = MediaAssetsContext()
super.init()
self.cameraStateDisposable = (camera.flashMode
@ -163,7 +163,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,17 +190,27 @@ 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))
}
func toggleFlashMode() {
if self.cameraState.flashMode == .off {
self.camera.setFlashMode(.on)
} else if self.cameraState.flashMode == .on {
self.camera.setFlashMode(.auto)
} else {
self.camera.setFlashMode(.off)
}
self.hapticFeedback.impact(.veryLight)
}
func togglePosition() {
self.camera.togglePosition()
self.hapticFeedback.impact(.veryLight)
}
func updateSwipeHint(_ hint: CaptureControlsComponent.SwipeHint) {
guard hint != self.swipeHint else {
return
@ -324,7 +348,8 @@ private final class CameraScreenComponent: CombinedComponent {
animation: LottieAnimationComponent.AnimationItem(
name: flashIconName,
mode: !state.cameraState.flashModeDidChange ? .still(position: .end) : .animating(loop: false),
range: nil
range: nil,
waitForCompletion: false
),
colors: [:],
size: CGSize(width: 40.0, height: 40.0)
@ -350,13 +375,7 @@ private final class CameraScreenComponent: CombinedComponent {
guard let state else {
return
}
if state.cameraState.flashMode == .off {
state.camera.setFlashMode(.on)
} else if state.cameraState.flashMode == .on {
state.camera.setFlashMode(.auto)
} else {
state.camera.setFlashMode(.off)
}
state.toggleFlashMode()
}
).tagged(flashButtonTag),
availableSize: CGSize(width: 40.0, height: 40.0),
@ -447,7 +466,7 @@ private final class CameraScreenComponent: CombinedComponent {
guard let state else {
return
}
state.camera.togglePosition()
state.togglePosition()
},
galleryTapped: {
guard let controller = environment.controller() as? CameraScreen else {
@ -818,7 +837,8 @@ public class CameraScreen: ViewController {
}
}
},
nil
nil,
{}
)
}
}
@ -875,7 +895,7 @@ public class CameraScreen: ViewController {
let transitionFraction = 1.0 - max(0.0, translation.x * -1.0) / self.frame.width
controller.updateTransitionProgress(transitionFraction, transition: .immediate)
} else if translation.y < -10.0 {
controller.presentGallery()
controller.presentGallery(fromGesture: true)
gestureRecognizer.isEnabled = false
gestureRecognizer.isEnabled = true
}
@ -1102,7 +1122,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,18 +1247,20 @@ public class CameraScreen: ViewController {
self.transitionOut = transitionOut
}
}
fileprivate let completion: (Signal<CameraScreen.Result, NoError>, ResultTransition?) -> Void
fileprivate let completion: (Signal<CameraScreen.Result, NoError>, ResultTransition?, @escaping () -> Void) -> Void
public var transitionedIn: () -> Void = {}
private var audioSessionDisposable: Disposable?
private let hapticFeedback = HapticFeedback()
public init(
context: AccountContext,
mode: Mode,
holder: CameraHolder? = nil,
transitionIn: TransitionIn?,
transitionOut: @escaping (Bool) -> TransitionOut?,
completion: @escaping (Signal<CameraScreen.Result, NoError>, ResultTransition?) -> Void
completion: @escaping (Signal<CameraScreen.Result, NoError>, ResultTransition?, @escaping () -> Void) -> Void
) {
self.context = context
self.mode = mode
@ -1282,7 +1304,11 @@ public class CameraScreen: ViewController {
self.node.animateInFromEditor(toGallery: self.galleryController != nil)
}
func presentGallery() {
func presentGallery(fromGesture: Bool = false) {
if !fromGesture {
self.hapticFeedback.impact(.veryLight)
}
var didStopCameraCapture = false
let stopCameraCapture = { [weak self] in
guard !didStopCameraCapture, let self else {
@ -1293,7 +1319,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 +1330,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
@ -1337,9 +1363,15 @@ public class CameraScreen: ViewController {
guard !self.isDismissed else {
return
}
if !interactive {
self.hapticFeedback.impact(.veryLight)
}
self.node.camera.stopCapture(invalidate: true)
self.isDismissed = true
if animated {
self.statusBar.updateStatusBarStyle(.Ignore, animated: true)
if !interactive {
if let navigationController = self.navigationController as? NavigationController {
navigationController.updateRootContainerTransitionOffset(self.node.frame.width, transition: .immediate)
@ -1353,13 +1385,11 @@ public class CameraScreen: ViewController {
}
}
private var isTransitioning = false
public func updateTransitionProgress(_ transitionFraction: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void = {}) {
self.isTransitioning = true
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 +1397,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)
@ -1377,11 +1405,12 @@ public class CameraScreen: ViewController {
}
public func completeWithTransitionProgress(_ transitionFraction: CGFloat, velocity: CGFloat, dismissing: Bool) {
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 +1419,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 +1428,7 @@ public class CameraScreen: ViewController {
}
})
} else {
self.statusBar.updateStatusBarStyle(.Ignore, animated: true)
self.requestDismiss(animated: true, interactive: true)
}
}

View File

@ -107,6 +107,8 @@ public final class MediaEditor {
}
private let playerPlaybackStatePromise = Promise<(Double, Double, Bool)>((0.0, 0.0, false))
public var onFirstDisplay: () -> Void = {}
public func playerState(framesCount: Int) -> Signal<MediaEditorPlayerState?, NoError> {
return self.playerPromise.get()
|> mapToSignal { [weak self] player in
@ -228,6 +230,7 @@ public final class MediaEditor {
gradientColors: nil,
videoTrimRange: nil,
videoIsMuted: false,
videoIsFullHd: false,
drawing: nil,
entities: [],
toolValues: [:]
@ -267,21 +270,11 @@ public final class MediaEditor {
if let device = renderTarget.mtlDevice, CVMetalTextureCacheCreate(nil, nil, device, nil, &self.textureCache) != kCVReturnSuccess {
print("error")
}
func gradientColors(from image: UIImage) -> (UIColor, UIColor) {
let context = DrawingContext(size: CGSize(width: 1.0, height: 4.0), scale: 1.0, clear: false)!
context.withFlippedContext({ context in
if let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 4.0))
}
})
return (context.colorAt(CGPoint(x: 0.0, y: 0.0)), context.colorAt(CGPoint(x: 0.0, y: 3.0)))
}
let textureSource: Signal<(TextureSource, UIImage?, AVPlayer?, UIColor, UIColor), NoError>
switch subject {
case let .image(image, _):
let colors = gradientColors(from: image)
let colors = mediaEditorGetGradientColors(from: image)
textureSource = .single((ImageTextureSource(image: image, renderTarget: renderTarget), image, nil, colors.0, colors.1))
case let .draft(draft):
guard let image = UIImage(contentsOfFile: draft.path) else {
@ -291,7 +284,7 @@ public final class MediaEditor {
if let gradientColors = draft.values.gradientColors {
colors = (gradientColors.first!, gradientColors.last!)
} else {
colors = gradientColors(from: image)
colors = mediaEditorGetGradientColors(from: image)
}
textureSource = .single((ImageTextureSource(image: image, renderTarget: renderTarget), image, nil, colors.0, colors.1))
case let .video(path, _):
@ -304,7 +297,7 @@ public final class MediaEditor {
let playerItem = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: playerItem)
if let image {
let colors = gradientColors(from: UIImage(cgImage: image))
let colors = mediaEditorGetGradientColors(from: UIImage(cgImage: image))
subscriber.putNext((VideoTextureSource(player: player, renderTarget: renderTarget), nil, player, colors.0, colors.1))
} else {
subscriber.putNext((VideoTextureSource(player: player, renderTarget: renderTarget), nil, player, .black, .black))
@ -329,7 +322,7 @@ public final class MediaEditor {
}
}
if !degraded {
let colors = gradientColors(from: image)
let colors = mediaEditorGetGradientColors(from: image)
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil, resultHandler: { asset, _, _ in
if let asset {
let playerItem = AVPlayerItem(asset: asset)
@ -359,7 +352,7 @@ public final class MediaEditor {
}
}
if !degraded {
let colors = gradientColors(from: image)
let colors = mediaEditorGetGradientColors(from: image)
subscriber.putNext((ImageTextureSource(image: image, renderTarget: renderTarget), image, nil, colors.0, colors.1))
subscriber.putCompletion()
}
@ -377,7 +370,7 @@ public final class MediaEditor {
if let self {
let (source, image, player, topColor, bottomColor) = sourceAndColors
self.renderer.onNextRender = { [weak self] in
self?.previewView?.removeTransitionImage()
self?.onFirstDisplay()
}
self.renderer.textureSource = source
self.player = player
@ -446,6 +439,10 @@ public final class MediaEditor {
self.values = self.values.withUpdatedVideoIsMuted(videoIsMuted)
}
public func setVideoIsFullHd(_ videoIsFullHd: Bool) {
self.values = self.values.withUpdatedVideoIsFullHd(videoIsFullHd)
}
private var targetTimePosition: (CMTime, Bool)?
private var updatingTimePosition = false
public func seek(_ position: Double, andPlay play: Bool) {
@ -499,6 +496,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]) {

View File

@ -12,7 +12,7 @@ import TelegramAnimatedStickerNode
import YuvConversion
import StickerResources
func mediaEditorGenerateGradientImage(size: CGSize, colors: [UIColor]) -> UIImage? {
public func mediaEditorGenerateGradientImage(size: CGSize, colors: [UIColor]) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 1.0)
if let context = UIGraphicsGetCurrentContext() {
let gradientColors = colors.map { $0.cgColor } as CFArray
@ -29,6 +29,16 @@ func mediaEditorGenerateGradientImage(size: CGSize, colors: [UIColor]) -> UIImag
return image
}
public func mediaEditorGetGradientColors(from image: UIImage) -> (UIColor, UIColor) {
let context = DrawingContext(size: CGSize(width: 5.0, height: 5.0), scale: 1.0, clear: false)!
context.withFlippedContext({ context in
if let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 5.0, height: 5.0))
}
})
return (context.colorAt(CGPoint(x: 2.0, y: 0.0)), context.colorAt(CGPoint(x: 2.0, y: 4.0)))
}
final class MediaEditorComposer {
let device: MTLDevice?
private let colorSpace: CGColorSpace
@ -201,7 +211,7 @@ private func makeEditorImageFrameComposition(inputImage: CIImage, gradientImage:
var initialScale: CGFloat
if mediaImage.extent.height > mediaImage.extent.width {
initialScale = dimensions.height / mediaImage.extent.height
initialScale = max(dimensions.width / mediaImage.extent.width, dimensions.height / mediaImage.extent.height)
} else {
initialScale = dimensions.width / mediaImage.extent.width
}

View File

@ -67,33 +67,4 @@ public final class MediaEditorPreviewView: MTKView, MTKViewDelegate, RenderTarge
}
self.renderer?.renderFrame()
}
private var transitionView: UIImageView?
public func setTransitionImage(_ image: UIImage) {
self.transitionView?.removeFromSuperview()
let transitionView = UIImageView(image: image)
transitionView.frame = self.bounds
self.addSubview(transitionView)
self.transitionView = transitionView
}
public func removeTransitionImage() {
if let transitionView = self.transitionView {
// transitionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak transitionView] _ in
//
// })
transitionView.removeFromSuperview()
self.transitionView = nil
}
}
public override func layoutSubviews() {
super.layoutSubviews()
if let transitionView = self.transitionView {
transitionView.frame = self.bounds
}
}
}

View File

@ -183,7 +183,7 @@ final class MediaEditorRenderer: TextureConsumer {
}
}
if self.renderTarget != nil {
let _ = self.outputRenderPass.process(input: texture, device: device, commandBuffer: commandBuffer)
self.outputRenderPass.process(input: texture, device: device, commandBuffer: commandBuffer)
}
self.finalTexture = texture
@ -191,14 +191,30 @@ final class MediaEditorRenderer: TextureConsumer {
if let self {
self.semaphore.signal()
#if targetEnvironment(simulator)
if let onNextRender = self.onNextRender {
self.onNextRender = nil
Queue.mainQueue().async {
onNextRender()
}
}
#endif
}
}
#if targetEnvironment(simulator)
#else
if let renderTarget = self.renderTarget, let drawable = renderTarget.drawable {
drawable.addPresentedHandler { [weak self] _ in
if let self, let onNextRender = self.onNextRender {
self.onNextRender = nil
Queue.mainQueue().async {
onNextRender()
}
}
}
}
#endif
if let _ = self.renderTarget {
commandBuffer.commit()

View File

@ -51,6 +51,7 @@ public final class MediaEditorValues: Codable {
case videoTrimRange
case videoIsMuted
case videoIsFullHd
case drawing
case entities
@ -68,6 +69,7 @@ public final class MediaEditorValues: Codable {
public let videoTrimRange: Range<Double>?
public let videoIsMuted: Bool
public let videoIsFullHd: Bool
public let drawing: UIImage?
public let entities: [CodableDrawingEntity]
@ -83,6 +85,7 @@ public final class MediaEditorValues: Codable {
gradientColors: [UIColor]?,
videoTrimRange: Range<Double>?,
videoIsMuted: Bool,
videoIsFullHd: Bool,
drawing: UIImage?,
entities: [CodableDrawingEntity],
toolValues: [EditorToolKey: Any]
@ -96,6 +99,7 @@ public final class MediaEditorValues: Codable {
self.gradientColors = gradientColors
self.videoTrimRange = videoTrimRange
self.videoIsMuted = videoIsMuted
self.videoIsFullHd = videoIsFullHd
self.drawing = drawing
self.entities = entities
self.toolValues = toolValues
@ -122,6 +126,7 @@ public final class MediaEditorValues: Codable {
self.videoTrimRange = try container.decodeIfPresent(Range<Double>.self, forKey: .videoTrimRange)
self.videoIsMuted = try container.decode(Bool.self, forKey: .videoIsMuted)
self.videoIsFullHd = try container.decode(Bool.self, forKey: .videoIsFullHd)
if let drawingData = try container.decodeIfPresent(Data.self, forKey: .drawing), let image = UIImage(data: drawingData) {
self.drawing = image
@ -175,31 +180,35 @@ public final class MediaEditorValues: Codable {
}
public func makeCopy() -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedCrop(offset: CGPoint, scale: CGFloat, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: offset, cropSize: self.cropSize, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: offset, cropSize: self.cropSize, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedGradientColors(gradientColors: [UIColor]) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedVideoIsMuted(_ videoIsMuted: Bool) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedVideoIsFullHd(_ videoIsFullHd: Bool) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedVideoTrimRange(_ videoTrimRange: Range<Double>) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedDrawingAndEntities(drawing: UIImage?, entities: [CodableDrawingEntity]) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: drawing, entities: entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, drawing: drawing, entities: entities, toolValues: self.toolValues)
}
func withUpdatedToolValues(_ toolValues: [EditorToolKey: Any]) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, drawing: self.drawing, entities: self.entities, toolValues: toolValues)
}
}
@ -920,20 +929,51 @@ extension CodableToolValue: Codable {
}
}
public func recommendedVideoExportConfiguration(values: MediaEditorValues, frameRate: Float) -> MediaEditorVideoExport.Configuration {
let compressionProperties: [String: Any] = [
AVVideoAverageBitRateKey: 2000000,
AVVideoProfileLevelKey: kVTProfileLevel_HEVC_Main_AutoLevel
//AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
//AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC
]
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, forceFullHd: Bool = false, frameRate: Float) -> MediaEditorVideoExport.Configuration {
let compressionProperties: [String: Any]
let codecType: AVVideoCodecType
if hasHEVCHardwareEncoder {
codecType = AVVideoCodecType.hevc
compressionProperties = [
AVVideoAverageBitRateKey: 3800000,
AVVideoProfileLevelKey: kVTProfileLevel_HEVC_Main_AutoLevel
]
} else {
codecType = AVVideoCodecType.h264
compressionProperties = [
AVVideoAverageBitRateKey: 3800000,
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC
]
}
let width: Int
let height: Int
if values.videoIsFullHd {
width = 1080
height = 1920
} else {
width = 720
height = 1280
}
let videoSettings: [String: Any] = [
//AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoCodecKey: AVVideoCodecType.hevc,
AVVideoCodecKey: codecType,
AVVideoCompressionPropertiesKey: compressionProperties,
AVVideoWidthKey: 720,
AVVideoHeightKey: 1280
AVVideoWidthKey: width,
AVVideoHeightKey: height
]
let audioSettings: [String: Any] = [

View File

@ -131,6 +131,7 @@ class DefaultRenderPass: RenderPass {
final class OutputRenderPass: DefaultRenderPass {
weak var renderTarget: RenderTarget?
@discardableResult
override func process(input: MTLTexture, device: MTLDevice, commandBuffer: MTLCommandBuffer) -> MTLTexture? {
guard let renderTarget = self.renderTarget, let renderPassDescriptor = renderTarget.renderPassDescriptor else {
return nil
@ -154,10 +155,9 @@ final class OutputRenderPass: DefaultRenderPass {
renderCommandEncoder.endEncoding()
if let drawable = renderTarget.drawable {
if let drawable = renderTarget.drawable {
commandBuffer.present(drawable)
}
return nil
}
}

View File

@ -29,6 +29,7 @@ swift_library(
"//submodules/TelegramUI/Components/MediaEditor",
"//submodules/DrawingUI:DrawingUI",
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
"//submodules/Components/BundleIconComponent:BundleIconComponent",
"//submodules/TelegramUI/Components/MessageInputPanelComponent",
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
"//submodules/TooltipUI",

View File

@ -22,6 +22,7 @@ import AvatarNode
import ShareWithPeersScreen
import PresentationDataUtils
import ContextUI
import BundleIconComponent
enum DrawingScreenType {
case drawing
@ -172,6 +173,7 @@ final class MediaEditorScreenComponent: Component {
private let privacyButton = ComponentView<Empty>()
private let muteButton = ComponentView<Empty>()
private let saveButton = ComponentView<Empty>()
private let settingsButton = ComponentView<Empty>()
private var component: MediaEditorScreenComponent?
private weak var state: State?
@ -235,6 +237,11 @@ final class MediaEditorScreenComponent: Component {
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
}
if let view = self.settingsButton.view {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
}
if let view = self.privacyButton.view {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
@ -284,6 +291,11 @@ final class MediaEditorScreenComponent: Component {
transition.setScale(view: view, scale: 0.1)
}
if let view = self.settingsButton.view {
transition.setAlpha(view: view, alpha: 0.0)
transition.setScale(view: view, scale: 0.1)
}
if let view = self.privacyButton.view {
transition.setAlpha(view: view, alpha: 0.0)
transition.setScale(view: view, scale: 0.1)
@ -337,6 +349,11 @@ final class MediaEditorScreenComponent: Component {
transition.setScale(view: view, scale: 0.1)
}
if let view = self.settingsButton.view {
transition.setAlpha(view: view, alpha: 0.0)
transition.setScale(view: view, scale: 0.1)
}
if let view = self.privacyButton.view {
transition.setAlpha(view: view, alpha: 0.0)
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
@ -389,6 +406,11 @@ final class MediaEditorScreenComponent: Component {
transition.setScale(view: view, scale: 1.0)
}
if let view = self.settingsButton.view {
transition.setAlpha(view: view, alpha: 1.0)
transition.setScale(view: view, scale: 1.0)
}
if let view = self.privacyButton.view {
transition.setAlpha(view: view, alpha: 1.0)
view.layer.animateScale(from: 0.0, to: 1.0, duration: 0.2)
@ -866,6 +888,44 @@ final class MediaEditorScreenComponent: Component {
}
}
if let _ = state.playerState {
let settingsButtonSize = self.settingsButton.update(
transition: transition,
component: AnyComponent(Button(
content: AnyComponent(
BundleIconComponent(
name: "Chat/Input/Media/EntityInputSettingsIcon",
tintColor: UIColor(rgb: 0xffffff)
)
),
action: {
if let controller = environment.controller() as? MediaEditorScreen {
controller.requestSettings()
}
}
)),
environment: {},
containerSize: CGSize(width: 44.0, height: 44.0)
)
let settingsButtonFrame = CGRect(
origin: CGPoint(x: floorToScreenPixels((availableSize.width - settingsButtonSize.width) / 2.0), y: environment.safeInsets.top + 20.0 - inputPanelOffset),
size: settingsButtonSize
)
if let settingsButtonView = self.settingsButton.view {
if settingsButtonView.superview == nil {
settingsButtonView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
settingsButtonView.layer.shadowRadius = 4.0
settingsButtonView.layer.shadowColor = UIColor.black.cgColor
settingsButtonView.layer.shadowOpacity = 0.2
//self.addSubview(settingsButtonView)
}
transition.setPosition(view: settingsButtonView, position: settingsButtonFrame.center)
transition.setBounds(view: settingsButtonView, bounds: CGRect(origin: .zero, size: settingsButtonFrame.size))
transition.setScale(view: settingsButtonView, scale: self.inputPanelExternalState.isEditing || isEditingTextEntity ? 0.01 : 1.0)
transition.setAlpha(view: settingsButtonView, alpha: self.inputPanelExternalState.isEditing || isEditingTextEntity ? 0.0 : 1.0)
}
}
return availableSize
}
}
@ -948,6 +1008,7 @@ public final class MediaEditorScreen: ViewController {
fileprivate let componentHost: ComponentView<ViewControllerComponentContainer.Environment>
private let previewContainerView: UIView
private var transitionInView: UIImageView?
private let gradientView: UIImageView
private var gradientColorsDisposable: Disposable?
@ -1096,7 +1157,7 @@ public final class MediaEditorScreen: ViewController {
let mediaEntity = DrawingMediaEntity(content: subject.mediaContent, size: fittedSize)
mediaEntity.position = CGPoint(x: storyDimensions.width / 2.0, y: storyDimensions.height / 2.0)
if fittedSize.height > fittedSize.width {
mediaEntity.scale = storyDimensions.height / fittedSize.height
mediaEntity.scale = max(storyDimensions.width / fittedSize.width, storyDimensions.height / fittedSize.height)
} else {
mediaEntity.scale = storyDimensions.width / fittedSize.width
}
@ -1238,10 +1299,17 @@ public final class MediaEditorScreen: ViewController {
}
@objc func handleTap(_ gestureRecognizer: UITapGestureRecognizer) {
if self.entitiesView.hasSelection {
self.entitiesView.selectEntity(nil)
let location = gestureRecognizer.location(in: self.view)
var entitiesHitTestResult = self.entitiesView.hitTest(self.view.convert(location, to: self.entitiesView), with: nil)
if entitiesHitTestResult is DrawingMediaEntityView {
entitiesHitTestResult = nil
}
if entitiesHitTestResult == nil {
if self.entitiesView.hasSelection {
self.entitiesView.selectEntity(nil)
}
self.view.endEditing(true)
}
self.view.endEditing(true)
}
func animateIn() {
@ -1252,9 +1320,27 @@ public final class MediaEditorScreen: ViewController {
view.animateIn(from: .camera)
}
case let .gallery(transitionIn):
if let transitionImage = transitionIn.sourceImage {
if let sourceImage = transitionIn.sourceImage {
self.previewContainerView.alpha = 1.0
self.previewView.setTransitionImage(transitionImage)
let transitionInView = UIImageView(image: sourceImage)
var initialScale: CGFloat
if sourceImage.size.height > sourceImage.size.width {
initialScale = max(self.previewContainerView.bounds.width / sourceImage.size.width, self.previewContainerView.bounds.height / sourceImage.size.height)
} else {
initialScale = self.previewContainerView.bounds.width / sourceImage.size.width
}
transitionInView.center = CGPoint(x: self.previewContainerView.bounds.width / 2.0, y: self.previewContainerView.bounds.height / 2.0)
transitionInView.transform = CGAffineTransformMakeScale(initialScale, initialScale)
self.previewContainerView.addSubview(transitionInView)
self.transitionInView = transitionInView
self.mediaEditor?.onFirstDisplay = { [weak self] in
if let self, let transitionInView = self.transitionInView {
transitionInView.removeFromSuperview()
self.transitionInView = nil
}
}
}
if let sourceView = transitionIn.sourceView {
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
@ -1265,7 +1351,7 @@ public final class MediaEditorScreen: ViewController {
let sourceScale = sourceLocalFrame.width / self.previewContainerView.frame.width
let sourceAspectRatio = sourceLocalFrame.height / sourceLocalFrame.width
let duration: Double = 0.5
let duration: Double = 0.4
self.previewContainerView.layer.animatePosition(from: sourceLocalFrame.center, to: self.previewContainerView.center, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
self.previewContainerView.layer.animateScale(from: sourceScale, to: 1.0, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
@ -1311,7 +1397,7 @@ public final class MediaEditorScreen: ViewController {
let transitionOutView = UIImageView(image: sourceImage)
var initialScale: CGFloat
if sourceImage.size.height > sourceImage.size.width {
initialScale = self.previewContainerView.bounds.height / sourceImage.size.height
initialScale = max(self.previewContainerView.bounds.width / sourceImage.size.width, self.previewContainerView.bounds.height / sourceImage.size.height)
} else {
initialScale = self.previewContainerView.bounds.width / sourceImage.size.width
}
@ -1507,7 +1593,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,
@ -1560,6 +1646,7 @@ public final class MediaEditorScreen: ViewController {
self.interaction?.insertEntity(textEntity)
return
case .drawing:
self.interaction?.deactivate()
let controller = DrawingScreen(context: self.context, sourceHint: .storyEditor, size: self.previewContainerView.frame.size, originalSize: storyDimensions, isVideo: false, isAvatar: false, drawingView: self.drawingView, entitiesView: self.entitiesView, selectionContainerView: self.selectionContainerView, existingStickerPickerInputData: self.stickerPickerInputData)
self.drawingScreen = controller
self.drawingView.isUserInteractionEnabled = true
@ -1573,6 +1660,7 @@ public final class MediaEditorScreen: ViewController {
self?.animateInFromTool()
self?.entitiesView.selectEntity(nil)
self?.interaction?.activate()
}
controller.requestApply = { [weak controller, weak self] in
self?.drawingScreen = nil
@ -1589,6 +1677,7 @@ public final class MediaEditorScreen: ViewController {
}
self?.entitiesView.selectEntity(nil)
self?.interaction?.activate()
}
self.controller?.present(controller, in: .current)
self.animateOutToTool()
@ -1725,6 +1814,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 +2098,7 @@ public final class MediaEditorScreen: ViewController {
self.node.animateOut(finished: false, completion: { [weak self] in
self?.dismiss()
self?.dismissed()
})
}
@ -2018,7 +2109,8 @@ public final class MediaEditorScreen: ViewController {
mediaEditor.stop()
let codableEntities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }.compactMap({ CodableDrawingEntity(entity: $0) })
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
if mediaEditor.resultIsVideo {
@ -2098,7 +2190,8 @@ public final class MediaEditorScreen: ViewController {
return
}
let codableEntities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }.compactMap({ CodableDrawingEntity(entity: $0) })
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
let tempVideoPath = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max)).mp4"
@ -2167,7 +2260,7 @@ public final class MediaEditorScreen: ViewController {
guard let self else {
return
}
let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values, frameRate: 60.0)
let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values, forceFullHd: true, frameRate: 60.0)
let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).mp4"
let videoExport = MediaEditorVideoExport(account: self.context.account, subject: exportSubject, configuration: configuration, outputPath: outputPath)
self.videoExport = videoExport
@ -2199,6 +2292,10 @@ public final class MediaEditorScreen: ViewController {
}
}
func requestSettings() {
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)

View File

@ -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,

View File

@ -15,6 +15,20 @@ private let borderHeight: CGFloat = 1.0 + UIScreenPixel
private let frameWidth: CGFloat = 24.0
private let minumumDuration: CGFloat = 1.0
private class VideoFrameLayer: SimpleShapeLayer {
private let stripeLayer = SimpleShapeLayer()
override func layoutSublayers() {
super.layoutSublayers()
if self.stripeLayer.superlayer == nil {
self.stripeLayer.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3).cgColor
self.addSublayer(self.stripeLayer)
}
self.stripeLayer.frame = CGRect(x: self.bounds.width - UIScreenPixel, y: 0.0, width: UIScreenPixel, height: self.bounds.height)
}
}
final class VideoScrubberComponent: Component {
typealias EnvironmentType = Empty
@ -87,8 +101,8 @@ final class VideoScrubberComponent: Component {
private let transparentFramesContainer = UIView()
private let opaqueFramesContainer = UIView()
private var transparentFrameLayers: [CALayer] = []
private var opaqueFrameLayers: [CALayer] = []
private var transparentFrameLayers: [VideoFrameLayer] = []
private var opaqueFrameLayers: [VideoFrameLayer] = []
private var component: VideoScrubberComponent?
private weak var state: EmptyComponentState?
@ -118,6 +132,15 @@ final class VideoScrubberComponent: Component {
context.fillPath()
})?.withRenderingMode(.alwaysTemplate)
let positionImage = generateImage(CGSize(width: 2.0, height: 42.0), rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor.white.cgColor)
let path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: CGSize(width: 2.0, height: 42.0)), cornerRadius: 1.0)
context.addPath(path.cgPath)
context.fillPath()
})
self.leftHandleView.image = handleImage
self.leftHandleView.isUserInteractionEnabled = true
self.leftHandleView.tintColor = .white
@ -127,6 +150,9 @@ final class VideoScrubberComponent: Component {
self.rightHandleView.isUserInteractionEnabled = true
self.rightHandleView.tintColor = .white
self.cursorView.image = positionImage
self.cursorView.isUserInteractionEnabled = true
self.borderView.image = generateImage(CGSize(width: 1.0, height: scrubberHeight), rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor.white.cgColor)
@ -151,7 +177,7 @@ final class VideoScrubberComponent: Component {
self.leftHandleView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handleLeftHandlePan(_:))))
self.rightHandleView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handleRightHandlePan(_:))))
//self.rightHandleView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handlePositionHandlePan(_:))))
self.cursorView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handlePositionHandlePan(_:))))
}
required init?(coder: NSCoder) {
@ -234,6 +260,13 @@ final class VideoScrubberComponent: Component {
}
self.state?.updated(transition: transition)
}
@objc private func handlePositionHandlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let _ = self.component else {
return
}
//let location = gestureRecognizer.location(in: self)
}
func update(component: VideoScrubberComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
let previousFramesUpdateTimestamp = self.component?.framesUpdateTimestamp
@ -245,15 +278,15 @@ final class VideoScrubberComponent: Component {
if component.framesUpdateTimestamp != previousFramesUpdateTimestamp {
for i in 0 ..< component.frames.count {
let transparentFrameLayer: CALayer
let opaqueFrameLayer: CALayer
let transparentFrameLayer: VideoFrameLayer
let opaqueFrameLayer: VideoFrameLayer
if i >= self.transparentFrameLayers.count {
transparentFrameLayer = SimpleLayer()
transparentFrameLayer = VideoFrameLayer()
transparentFrameLayer.masksToBounds = true
transparentFrameLayer.contentsGravity = .resizeAspectFill
self.transparentFramesContainer.layer.addSublayer(transparentFrameLayer)
self.transparentFrameLayers.append(transparentFrameLayer)
opaqueFrameLayer = SimpleLayer()
opaqueFrameLayer = VideoFrameLayer()
opaqueFrameLayer.masksToBounds = true
opaqueFrameLayer.contentsGravity = .resizeAspectFill
self.opaqueFramesContainer.layer.addSublayer(opaqueFrameLayer)

View File

@ -916,72 +916,69 @@ final class ShareWithPeersScreenComponent: Component {
let navigationSideInset: CGFloat = 16.0
var navigationButtonsWidth: CGFloat = 0.0
if case .stories = component.stateContext.subject {
} else {
let navigationLeftButtonSize = self.navigationLeftButton.update(
transition: transition,
component: AnyComponent(Button(
content: AnyComponent(Text(text: "Cancel", font: Font.regular(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)),
action: { [weak self] in
guard let self, let environment = self.environment, let controller = environment.controller() else {
return
}
controller.dismiss()
}
).minSize(CGSize(width: navigationHeight, height: navigationHeight))),
environment: {},
containerSize: CGSize(width: availableSize.width, height: navigationHeight)
)
let navigationLeftButtonFrame = CGRect(origin: CGPoint(x: navigationSideInset, y: floor((navigationHeight - navigationLeftButtonSize.height) * 0.5)), size: navigationLeftButtonSize)
if let navigationLeftButtonView = self.navigationLeftButton.view {
if navigationLeftButtonView.superview == nil {
self.navigationContainerView.addSubview(navigationLeftButtonView)
}
transition.setFrame(view: navigationLeftButtonView, frame: navigationLeftButtonFrame)
}
navigationButtonsWidth += navigationLeftButtonSize.width + navigationSideInset
}
let navigationRightButtonSize = self.navigationRightButton.update(
let navigationLeftButtonSize = self.navigationLeftButton.update(
transition: transition,
component: AnyComponent(Button(
content: AnyComponent(Text(text: "Done", font: Font.semibold(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)),
content: AnyComponent(Text(text: "Cancel", font: Font.regular(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)),
action: { [weak self] in
guard let self, let component = self.component, let controller = self.environment?.controller() else {
guard let self, let environment = self.environment, let controller = environment.controller() else {
return
}
let base: EngineStoryPrivacy.Base
if self.selectedCategories.contains(.everyone) {
base = .everyone
} else if self.selectedCategories.contains(.closeFriends) {
base = .closeFriends
} else if self.selectedCategories.contains(.contacts) {
base = .contacts
} else if self.selectedCategories.contains(.selectedContacts) {
base = .nobody
} else {
base = .nobody
}
component.completion(EngineStoryPrivacy(
base: base,
additionallyIncludePeers: self.selectedPeers
))
controller.dismiss()
}
).minSize(CGSize(width: navigationHeight, height: navigationHeight))),
environment: {},
containerSize: CGSize(width: availableSize.width, height: navigationHeight)
)
let navigationRightButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - navigationSideInset - navigationRightButtonSize.width, y: floor((navigationHeight - navigationRightButtonSize.height) * 0.5)), size: navigationRightButtonSize)
if let navigationRightButtonView = self.navigationRightButton.view {
if navigationRightButtonView.superview == nil {
self.navigationContainerView.addSubview(navigationRightButtonView)
let navigationLeftButtonFrame = CGRect(origin: CGPoint(x: navigationSideInset, y: floor((navigationHeight - navigationLeftButtonSize.height) * 0.5)), size: navigationLeftButtonSize)
if let navigationLeftButtonView = self.navigationLeftButton.view {
if navigationLeftButtonView.superview == nil {
self.navigationContainerView.addSubview(navigationLeftButtonView)
}
transition.setFrame(view: navigationRightButtonView, frame: navigationRightButtonFrame)
transition.setFrame(view: navigationLeftButtonView, frame: navigationLeftButtonFrame)
}
navigationButtonsWidth += navigationRightButtonSize.width + navigationSideInset
navigationButtonsWidth += navigationLeftButtonSize.width + navigationSideInset
// let navigationRightButtonSize = self.navigationRightButton.update(
// transition: transition,
// component: AnyComponent(Button(
// content: AnyComponent(Text(text: "Done", font: Font.semibold(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)),
// action: { [weak self] in
// guard let self, let component = self.component, let controller = self.environment?.controller() else {
// return
// }
//
// let base: EngineStoryPrivacy.Base
// if self.selectedCategories.contains(.everyone) {
// base = .everyone
// } else if self.selectedCategories.contains(.closeFriends) {
// base = .closeFriends
// } else if self.selectedCategories.contains(.contacts) {
// base = .contacts
// } else if self.selectedCategories.contains(.selectedContacts) {
// base = .nobody
// } else {
// base = .nobody
// }
//
// component.completion(EngineStoryPrivacy(
// base: base,
// additionallyIncludePeers: self.selectedPeers
// ))
// controller.dismiss()
// }
// ).minSize(CGSize(width: navigationHeight, height: navigationHeight))),
// environment: {},
// containerSize: CGSize(width: availableSize.width, height: navigationHeight)
// )
// let navigationRightButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - navigationSideInset - navigationRightButtonSize.width, y: floor((navigationHeight - navigationRightButtonSize.height) * 0.5)), size: navigationRightButtonSize)
// if let navigationRightButtonView = self.navigationRightButton.view {
// if navigationRightButtonView.superview == nil {
// self.navigationContainerView.addSubview(navigationRightButtonView)
// }
// transition.setFrame(view: navigationRightButtonView, frame: navigationRightButtonFrame)
// }
// navigationButtonsWidth += navigationRightButtonSize.width + navigationSideInset
let title: String
switch component.stateContext.subject {
@ -1034,7 +1031,7 @@ final class ShareWithPeersScreenComponent: Component {
topInset = 0.0
} else {
if case .stories = component.stateContext.subject {
topInset = max(0.0, availableSize.height - containerInset - 410.0)
topInset = max(0.0, availableSize.height - containerInset - 427.0)
} else {
topInset = max(0.0, availableSize.height - containerInset - 600.0)
}
@ -1045,48 +1042,76 @@ final class ShareWithPeersScreenComponent: Component {
transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
var bottomPanelHeight: CGFloat = 0.0
if case .stories = component.stateContext.subject {
let actionButtonSize = self.actionButton.update(
transition: transition,
component: AnyComponent(Button(
content: AnyComponent(Text(
text: "Send as a Message",
font: Font.regular(17.0),
color: environment.theme.list.itemAccentColor
)),
action: { [weak self] in
guard let self, let component = self.component, let controller = self.environment?.controller() else {
return
}
component.secondaryAction()
controller.dismiss()
let actionButtonTitle: String = "Save Settings"
let actionButtonSize = self.actionButton.update(
transition: transition,
component: AnyComponent(ButtonComponent(
background: ButtonComponent.Background(
color: environment.theme.list.itemCheckColors.fillColor,
foreground: environment.theme.list.itemCheckColors.foregroundColor,
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
),
content: AnyComponentWithIdentity(
id: actionButtonTitle,
component: AnyComponent(ButtonTextContentComponent(
text: actionButtonTitle,
badge: 0,
textColor: environment.theme.list.itemCheckColors.foregroundColor,
badgeBackground: environment.theme.list.itemCheckColors.foregroundColor,
badgeForeground: environment.theme.list.itemCheckColors.fillColor
))
),
isEnabled: true,
displaysProgress: false,
action: { [weak self] in
guard let self, let component = self.component, let controller = self.environment?.controller() else {
return
}
).minSize(CGSize(width: 200.0, height: 44.0))),
environment: {},
containerSize: CGSize(width: availableSize.width - navigationSideInset * 2.0, height: 44.0)
)
if environment.inputHeight != 0.0 {
bottomPanelHeight += environment.inputHeight + 8.0 + actionButtonSize.height
} else {
bottomPanelHeight += environment.safeInsets.bottom + actionButtonSize.height
}
let actionButtonFrame = CGRect(origin: CGPoint(x: (availableSize.width - actionButtonSize.width) / 2.0, y: availableSize.height - bottomPanelHeight), size: actionButtonSize)
if let actionButtonView = self.actionButton.view {
if actionButtonView.superview == nil {
self.addSubview(actionButtonView)
let base: EngineStoryPrivacy.Base
if self.selectedCategories.contains(.everyone) {
base = .everyone
} else if self.selectedCategories.contains(.closeFriends) {
base = .closeFriends
} else if self.selectedCategories.contains(.contacts) {
base = .contacts
} else if self.selectedCategories.contains(.selectedContacts) {
base = .nobody
} else {
base = .nobody
}
component.completion(EngineStoryPrivacy(
base: base,
additionallyIncludePeers: self.selectedPeers
))
controller.dismiss()
}
transition.setFrame(view: actionButtonView, frame: actionButtonFrame)
)),
environment: {},
containerSize: CGSize(width: availableSize.width - navigationSideInset * 2.0, height: 50.0)
)
var bottomPanelHeight: CGFloat = 0.0
if environment.inputHeight != 0.0 {
bottomPanelHeight += environment.inputHeight + 8.0 + actionButtonSize.height
} else {
bottomPanelHeight += 10.0 + environment.safeInsets.bottom + actionButtonSize.height
}
let actionButtonFrame = CGRect(origin: CGPoint(x: navigationSideInset, y: availableSize.height - bottomPanelHeight), size: actionButtonSize)
if let actionButtonView = self.actionButton.view {
if actionButtonView.superview == nil {
self.addSubview(actionButtonView)
}
transition.setFrame(view: self.bottomBackgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight - 8.0), size: CGSize(width: availableSize.width, height: bottomPanelHeight + 8.0)))
self.bottomBackgroundView.update(size: self.bottomBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition)
transition.setFrame(layer: self.bottomSeparatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight - 8.0 - UIScreenPixel), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
transition.setFrame(view: actionButtonView, frame: actionButtonFrame)
}
let itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: bottomPanelHeight + environment.safeInsets.bottom, topInset: topInset, sideInset: sideInset, navigationHeight: navigationHeight, sections: sections)
transition.setFrame(view: self.bottomBackgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight - 8.0), size: CGSize(width: availableSize.width, height: bottomPanelHeight + 8.0)))
self.bottomBackgroundView.update(size: self.bottomBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition)
transition.setFrame(layer: self.bottomSeparatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight - 8.0 - UIScreenPixel), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
let itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: bottomPanelHeight, topInset: topInset, sideInset: sideInset, navigationHeight: navigationHeight, sections: sections)
let previousItemLayout = self.itemLayout
self.itemLayout = itemLayout

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_newstory.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,136 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 5.334961 5.334961 cm
0.000000 0.000000 0.000000 scn
9.449870 17.997356 m
9.521360 17.999166 9.593074 18.000078 9.665000 18.000078 c
10.034567 18.000078 10.398193 17.976067 10.754469 17.929594 c
11.118653 17.882090 11.452393 18.138809 11.499898 18.502993 c
11.547402 18.867176 11.290683 19.200916 10.926498 19.248421 c
10.513331 19.302315 10.092241 19.330078 9.665000 19.330078 c
7.079488 19.330078 4.731096 18.314842 2.996726 16.661268 c
2.869770 16.540228 2.746105 16.415766 2.625877 16.288031 c
0.997681 14.558178 0.000000 12.228138 0.000000 9.665078 c
0.000000 7.162970 0.950794 4.882930 2.510792 3.166550 c
2.803406 2.844603 3.117454 2.542486 3.450681 2.262457 c
5.042018 0.925171 7.070760 0.091568 9.291392 0.007164 c
9.415345 0.002453 9.539894 0.000076 9.665000 0.000076 c
10.092241 0.000076 10.513330 0.027840 10.926498 0.081734 c
11.290683 0.129238 11.547402 0.462978 11.499897 0.827162 c
11.452393 1.191347 11.118653 1.448067 10.754469 1.400562 c
10.398193 1.354088 10.034567 1.330078 9.665000 1.330078 c
9.530147 1.330078 9.396041 1.333281 9.262755 1.339613 c
7.263335 1.434616 5.448418 2.234112 4.060734 3.495360 c
3.838658 3.697204 3.627523 3.910872 3.428325 4.135371 c
2.167055 5.556847 1.384340 7.412554 1.332722 9.449948 c
1.330911 9.521439 1.330000 9.593152 1.330000 9.665078 c
1.330000 9.757984 1.331520 9.850536 1.334537 9.942709 c
1.403918 12.062651 2.264948 13.982349 3.631555 15.415731 c
3.736506 15.525810 3.844438 15.633020 3.955222 15.737234 c
5.398290 17.094700 7.325295 17.943529 9.449870 17.997356 c
h
15.549259 17.332964 m
15.258044 17.556751 14.840552 17.502090 14.616766 17.210875 c
14.392979 16.919661 14.447639 16.502169 14.738854 16.278381 c
15.316504 15.834481 15.834403 15.316582 16.278303 14.738933 c
16.502090 14.447718 16.919582 14.393057 17.210798 14.616844 c
17.502012 14.840631 17.556675 15.258122 17.332888 15.549337 c
16.818552 16.218645 16.218567 16.818628 15.549259 17.332964 c
h
19.248344 10.926577 m
19.200840 11.290761 18.867100 11.547480 18.502916 11.499975 c
18.138731 11.452471 17.882011 11.118731 17.929516 10.754547 c
17.975990 10.398272 18.000000 10.034645 18.000000 9.665078 c
18.000000 9.295511 17.975990 8.931885 17.929516 8.575609 c
17.882011 8.211425 18.138731 7.877686 18.502914 7.830180 c
18.867100 7.782676 19.200840 8.039395 19.248344 8.403580 c
19.302238 8.816747 19.330002 9.237837 19.330002 9.665078 c
19.330002 10.092319 19.302238 10.513409 19.248344 10.926577 c
h
17.332888 3.780819 m
17.556675 4.072034 17.502012 4.489526 17.210798 4.713312 c
16.919582 4.937099 16.502090 4.882439 16.278303 4.591224 c
15.834403 4.013574 15.316504 3.495675 14.738854 3.051775 c
14.447639 2.827988 14.392979 2.410496 14.616766 2.119280 c
14.840552 1.828066 15.258044 1.773403 15.549259 1.997190 c
16.218567 2.511526 16.818552 3.111511 17.332888 3.780819 c
h
9.665000 14.330078 m
10.032269 14.330078 10.330000 14.032348 10.330000 13.665078 c
10.330000 10.330078 l
13.665000 10.330078 l
14.032269 10.330078 14.330000 10.032348 14.330000 9.665078 c
14.330000 9.297809 14.032269 9.000078 13.665000 9.000078 c
10.330000 9.000078 l
10.330000 5.665078 l
10.330000 5.297809 10.032269 5.000078 9.665000 5.000078 c
9.297730 5.000078 9.000000 5.297809 9.000000 5.665078 c
9.000000 9.000078 l
5.665000 9.000078 l
5.297730 9.000078 5.000000 9.297809 5.000000 9.665078 c
5.000000 10.032348 5.297730 10.330078 5.665000 10.330078 c
9.000000 10.330078 l
9.000000 13.665078 l
9.000000 14.032348 9.297730 14.330078 9.665000 14.330078 c
h
f*
n
Q
endstream
endobj
3 0 obj
3544
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000003634 00000 n
0000003657 00000 n
0000003830 00000 n
0000003904 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
3963
%%EOF

View File

@ -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
}

View File

@ -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)
}

View File

@ -191,42 +191,6 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
accountSettingsController.parentController = self
controllers.append(accountSettingsController)
tabBarController.cameraItemAndAction = (
UITabBarItem(title: "Camera", image: UIImage(bundleImageName: "Chat List/Tabs/IconCamera"), tag: 2),
{ [weak self] in
guard let self else {
return
}
let coordinator = self.openStoryCamera(
transitionIn: nil,
transitionedIn: { [weak self] in
guard let self, let rootTabController = self.rootTabController else {
return
}
if let index = rootTabController.controllers.firstIndex(where: { $0 is ChatListController}) {
rootTabController.selectedIndex = index
}
},
transitionOut: { [weak self] finished in
guard let self else {
return nil
}
if finished {
if let chatListController = self.chatListController as? ChatListControllerImpl, let transitionView = chatListController.transitionViewForOwnStoryItem() {
return StoryCameraTransitionOut(
destinationView: transitionView,
destinationRect: transitionView.bounds,
destinationCornerRadius: transitionView.bounds.height / 2.0
)
}
}
return nil
}
)
coordinator?.animateIn()
}
)
tabBarController.setControllers(controllers, selectedIndex: restoreSettignsController != nil ? (controllers.count - 1) : (controllers.count - 2))
self.contactsController = contactsController
@ -320,7 +284,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
return nil
}
},
completion: { result, resultTransition in
completion: { result, resultTransition, dismissed in
let subject: Signal<MediaEditorScreen.Subject?, NoError> = result
|> map { value -> MediaEditorScreen.Subject? in
switch value {
@ -400,16 +364,6 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
var attributes: [MessageAttribute] = []
let imageFlags: TelegramMediaImageFlags = []
// var stickerFiles: [TelegramMediaFile] = []
// if !stickers.isEmpty {
// for fileReference in stickers {
// stickerFiles.append(fileReference.media)
// }
// }
// if !stickerFiles.isEmpty {
// attributes.append(EmbeddedMediaStickersMessageAttribute(files: stickerFiles))
// imageFlags.insert(.hasStickers)
// }
let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: imageFlags)
if let timeout, timeout > 0 && timeout <= 60 {
@ -488,6 +442,9 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
}
returnToCameraImpl?()
}
controller.dismissed = {
dismissed()
}
presentImpl?(controller)
}
)