mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Merge commit '1ac9959731ec19c154f90ebf9c557f1d91c3038b'
This commit is contained in:
commit
787d9597a0
@ -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
|
||||
|
||||
|
@ -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
|
||||
}()
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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]) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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] = [
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat List/AddStoryIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat List/AddStoryIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_newstory.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
136
submodules/TelegramUI/Images.xcassets/Chat List/AddStoryIcon.imageset/ic_newstory.pdf
vendored
Normal file
136
submodules/TelegramUI/Images.xcassets/Chat List/AddStoryIcon.imageset/ic_newstory.pdf
vendored
Normal 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
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user