mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Camera and media editor improvements
This commit is contained in:
@@ -0,0 +1,979 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import ViewControllerComponent
|
||||
import ComponentDisplayAdapters
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import MultilineTextComponent
|
||||
import DrawingUI
|
||||
import MediaEditor
|
||||
import Photos
|
||||
import LottieAnimationComponent
|
||||
import MessageInputPanelComponent
|
||||
|
||||
private enum MediaToolsSection: Equatable {
|
||||
case adjustments
|
||||
case highlights
|
||||
case blur
|
||||
case curves
|
||||
}
|
||||
|
||||
private final class ToolIconComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
|
||||
let icon: UIImage?
|
||||
let isActive: Bool
|
||||
let isSelected: Bool
|
||||
|
||||
init(
|
||||
icon: UIImage?,
|
||||
isActive: Bool,
|
||||
isSelected: Bool
|
||||
) {
|
||||
self.icon = icon
|
||||
self.isActive = isActive
|
||||
self.isSelected = isSelected
|
||||
}
|
||||
|
||||
static func ==(lhs: ToolIconComponent, rhs: ToolIconComponent) -> Bool {
|
||||
if lhs.icon !== rhs.icon {
|
||||
return false
|
||||
}
|
||||
if lhs.isActive != rhs.isActive {
|
||||
return false
|
||||
}
|
||||
if lhs.isSelected != rhs.isSelected {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let selection = SimpleShapeLayer()
|
||||
private let icon = ComponentView<Empty>()
|
||||
|
||||
private var component: ToolIconComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.selection.path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0)), cornerRadius: 10.0).cgPath
|
||||
self.selection.fillColor = UIColor(rgb: 0xd1d1d1).cgColor
|
||||
self.layer.addSublayer(self.selection)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: ToolIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let iconColor: UIColor
|
||||
if component.isSelected {
|
||||
iconColor = .black
|
||||
} else {
|
||||
iconColor = component.isActive ? UIColor(rgb: 0xf8d74a) : .white
|
||||
}
|
||||
|
||||
let iconSize = self.icon.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
Image(
|
||||
image: component.icon,
|
||||
tintColor: iconColor,
|
||||
size: CGSize(width: 30.0, height: 30.0)
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let size = CGSize(width: 33.0, height: 33.0)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - iconSize.width) / 2.0), y: floorToScreenPixels((size.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
if let view = self.icon.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
transition.setFrame(view: view, frame: iconFrame)
|
||||
}
|
||||
|
||||
self.selection.isHidden = !component.isSelected
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class MediaToolsScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let mediaEditor: MediaEditor
|
||||
let section: MediaToolsSection
|
||||
let sectionUpdated: (MediaToolsSection) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
mediaEditor: MediaEditor,
|
||||
section: MediaToolsSection,
|
||||
sectionUpdated: @escaping (MediaToolsSection) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.mediaEditor = mediaEditor
|
||||
self.section = section
|
||||
self.sectionUpdated = sectionUpdated
|
||||
}
|
||||
|
||||
static func ==(lhs: MediaToolsScreenComponent, rhs: MediaToolsScreenComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.section != rhs.section {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class State: ComponentState {
|
||||
enum ImageKey: Hashable {
|
||||
case adjustments
|
||||
case highlights
|
||||
case blur
|
||||
case curves
|
||||
case done
|
||||
}
|
||||
private var cachedImages: [ImageKey: UIImage] = [:]
|
||||
func image(_ key: ImageKey) -> UIImage {
|
||||
if let image = self.cachedImages[key] {
|
||||
return image
|
||||
} else {
|
||||
var image: UIImage
|
||||
switch key {
|
||||
case .adjustments:
|
||||
image = UIImage(bundleImageName: "Media Editor/Tools")!
|
||||
case .highlights:
|
||||
image = UIImage(bundleImageName: "Media Editor/Tint")!
|
||||
case .blur:
|
||||
image = UIImage(bundleImageName: "Media Editor/Blur")!
|
||||
case .curves:
|
||||
image = UIImage(bundleImageName: "Media Editor/Curves")!
|
||||
case .done:
|
||||
image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Done"), color: .white)!
|
||||
}
|
||||
cachedImages[key] = image
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
let context: AccountContext
|
||||
var histogram: MediaEditorHistogram?
|
||||
private var histogramDisposable: Disposable?
|
||||
|
||||
init(context: AccountContext, mediaEditor: MediaEditor) {
|
||||
self.context = context
|
||||
|
||||
super.init()
|
||||
|
||||
self.histogramDisposable = (mediaEditor.histogram
|
||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||
if let self {
|
||||
self.histogram = MediaEditorHistogram(data: data)
|
||||
self.updated()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.histogramDisposable?.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State(
|
||||
context: self.context,
|
||||
mediaEditor: self.mediaEditor
|
||||
)
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private let buttonsContainerView = UIView()
|
||||
private let cancelButton = ComponentView<Empty>()
|
||||
private let adjustmentsButton = ComponentView<Empty>()
|
||||
private let highlightsButton = ComponentView<Empty>()
|
||||
private let blurButton = ComponentView<Empty>()
|
||||
private let curvesButton = ComponentView<Empty>()
|
||||
private let doneButton = ComponentView<Empty>()
|
||||
|
||||
private let previewContainerView = UIView()
|
||||
private var optionsContainerView = UIView()
|
||||
private var toolOptions = ComponentView<Empty>()
|
||||
private var toolScreen: ComponentView<Empty>?
|
||||
|
||||
private var curvesState: CurvesInternalState?
|
||||
|
||||
private var component: MediaToolsScreenComponent?
|
||||
private weak var state: State?
|
||||
private var environment: ViewControllerComponentContainer.Environment?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.buttonsContainerView.clipsToBounds = true
|
||||
self.previewContainerView.clipsToBounds = true
|
||||
|
||||
self.optionsContainerView.clipsToBounds = true
|
||||
self.optionsContainerView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.9)
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.backgroundColor = .clear
|
||||
|
||||
self.addSubview(self.buttonsContainerView)
|
||||
self.addSubview(self.previewContainerView)
|
||||
self.previewContainerView.addSubview(self.optionsContainerView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func animateInFromEditor() {
|
||||
let buttons = [
|
||||
self.adjustmentsButton,
|
||||
self.highlightsButton,
|
||||
self.blurButton,
|
||||
self.curvesButton
|
||||
]
|
||||
|
||||
var delay: Double = 0.0
|
||||
for button in buttons {
|
||||
if let view = button.view {
|
||||
view.layer.animatePosition(from: CGPoint(x: 0.0, y: 64.0), to: .zero, duration: 0.3, delay: delay, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: delay)
|
||||
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2, delay: delay)
|
||||
delay += 0.05
|
||||
}
|
||||
}
|
||||
|
||||
if let view = self.doneButton.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)
|
||||
}
|
||||
|
||||
self.optionsContainerView.layer.animatePosition(from: CGPoint(x: 0.0, y: self.optionsContainerView.frame.height), to: .zero, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
}
|
||||
|
||||
private var animatingOut = false
|
||||
func animateOutToEditor(completion: @escaping () -> Void) {
|
||||
self.animatingOut = true
|
||||
|
||||
let buttons = [
|
||||
self.adjustmentsButton,
|
||||
self.highlightsButton,
|
||||
self.blurButton,
|
||||
self.curvesButton
|
||||
]
|
||||
|
||||
for button in buttons {
|
||||
if let view = button.view {
|
||||
view.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 64.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
if let view = self.doneButton.view {
|
||||
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
|
||||
}
|
||||
|
||||
if let view = self.toolScreen?.view {
|
||||
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
self.optionsContainerView.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: self.optionsContainerView.frame.height), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
|
||||
self.state?.updated()
|
||||
}
|
||||
|
||||
func update(component: MediaToolsScreenComponent, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||
self.environment = environment
|
||||
|
||||
let previousSection = self.component?.section
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let mediaEditor = (environment.controller() as? MediaToolsScreen)?.mediaEditor
|
||||
|
||||
let sectionUpdated = component.sectionUpdated
|
||||
|
||||
let previewContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: environment.safeInsets.top), size: CGSize(width: availableSize.width, height: availableSize.height - environment.safeInsets.top - environment.safeInsets.bottom))
|
||||
let buttonsContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - environment.safeInsets.bottom), size: CGSize(width: availableSize.width, height: environment.safeInsets.bottom))
|
||||
|
||||
let buttonSideInset: CGFloat = 10.0
|
||||
let buttonBottomInset: CGFloat = 8.0
|
||||
|
||||
let cancelButtonSize = self.cancelButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(
|
||||
LottieAnimationComponent(
|
||||
animation: LottieAnimationComponent.AnimationItem(
|
||||
name: "media_backToCancel",
|
||||
mode: .animating(loop: false),
|
||||
range: self.animatingOut ? (0.5, 1.0) : (0.0, 0.5)
|
||||
),
|
||||
colors: ["__allcolors__": .white],
|
||||
size: CGSize(width: 33.0, height: 33.0)
|
||||
)
|
||||
),
|
||||
action: {
|
||||
guard let controller = environment.controller() as? MediaToolsScreen else {
|
||||
return
|
||||
}
|
||||
controller.requestDismiss(animated: true)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 44.0, height: 44.0)
|
||||
)
|
||||
let cancelButtonFrame = CGRect(
|
||||
origin: CGPoint(x: buttonSideInset, y: buttonBottomInset),
|
||||
size: cancelButtonSize
|
||||
)
|
||||
if let cancelButtonView = self.cancelButton.view {
|
||||
if cancelButtonView.superview == nil {
|
||||
self.buttonsContainerView.addSubview(cancelButtonView)
|
||||
}
|
||||
transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame)
|
||||
}
|
||||
|
||||
let doneButtonSize = self.doneButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(Image(
|
||||
image: state.image(.done),
|
||||
size: CGSize(width: 33.0, height: 33.0)
|
||||
)),
|
||||
action: {
|
||||
guard let controller = environment.controller() as? MediaToolsScreen else {
|
||||
return
|
||||
}
|
||||
controller.requestDismiss(animated: true)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 44.0, height: 44.0)
|
||||
)
|
||||
let doneButtonFrame = CGRect(
|
||||
origin: CGPoint(x: availableSize.width - buttonSideInset - doneButtonSize.width, y: buttonBottomInset),
|
||||
size: doneButtonSize
|
||||
)
|
||||
if let doneButtonView = self.doneButton.view {
|
||||
if doneButtonView.superview == nil {
|
||||
self.buttonsContainerView.addSubview(doneButtonView)
|
||||
}
|
||||
transition.setFrame(view: doneButtonView, frame: doneButtonFrame)
|
||||
}
|
||||
|
||||
let adjustmentsButtonSize = self.adjustmentsButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(ToolIconComponent(
|
||||
icon: state.image(.adjustments),
|
||||
isActive: false,
|
||||
isSelected: component.section == .adjustments
|
||||
)),
|
||||
action: {
|
||||
sectionUpdated(.adjustments)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 40.0, height: 40.0)
|
||||
)
|
||||
let adjustmentsButtonFrame = CGRect(
|
||||
origin: CGPoint(x: floorToScreenPixels(availableSize.width / 4.0 - 3.0 - adjustmentsButtonSize.width / 2.0), y: buttonBottomInset),
|
||||
size: adjustmentsButtonSize
|
||||
)
|
||||
if let adjustmentsButtonView = self.adjustmentsButton.view {
|
||||
if adjustmentsButtonView.superview == nil {
|
||||
self.buttonsContainerView.addSubview(adjustmentsButtonView)
|
||||
}
|
||||
transition.setFrame(view: adjustmentsButtonView, frame: adjustmentsButtonFrame)
|
||||
}
|
||||
|
||||
let highlightsButtonSize = self.highlightsButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(ToolIconComponent(
|
||||
icon: state.image(.highlights),
|
||||
isActive: false,
|
||||
isSelected: component.section == .highlights
|
||||
)),
|
||||
action: {
|
||||
sectionUpdated(.highlights)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 40.0, height: 40.0)
|
||||
)
|
||||
let highlightsButtonFrame = CGRect(
|
||||
origin: CGPoint(x: floorToScreenPixels(availableSize.width / 2.5 + 5.0 - highlightsButtonSize.width / 2.0), y: buttonBottomInset),
|
||||
size: highlightsButtonSize
|
||||
)
|
||||
if let highlightsButtonView = self.highlightsButton.view {
|
||||
if highlightsButtonView.superview == nil {
|
||||
self.buttonsContainerView.addSubview(highlightsButtonView)
|
||||
}
|
||||
transition.setFrame(view: highlightsButtonView, frame: highlightsButtonFrame)
|
||||
}
|
||||
|
||||
let blurButtonSize = self.blurButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(ToolIconComponent(
|
||||
icon: state.image(.blur),
|
||||
isActive: false,
|
||||
isSelected: component.section == .blur
|
||||
)),
|
||||
action: {
|
||||
sectionUpdated(.blur)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 40.0, height: 40.0)
|
||||
)
|
||||
let blurButtonFrame = CGRect(
|
||||
origin: CGPoint(x: floorToScreenPixels(availableSize.width - availableSize.width / 2.5 - 5.0 - blurButtonSize.width / 2.0), y: buttonBottomInset),
|
||||
size: blurButtonSize
|
||||
)
|
||||
if let blurButtonView = self.blurButton.view {
|
||||
if blurButtonView.superview == nil {
|
||||
self.buttonsContainerView.addSubview(blurButtonView)
|
||||
}
|
||||
transition.setFrame(view: blurButtonView, frame: blurButtonFrame)
|
||||
}
|
||||
|
||||
let curvesButtonSize = self.curvesButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(ToolIconComponent(
|
||||
icon: state.image(.curves),
|
||||
isActive: false,
|
||||
isSelected: component.section == .curves
|
||||
)),
|
||||
action: {
|
||||
sectionUpdated(.curves)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 40.0, height: 40.0)
|
||||
)
|
||||
let curvesButtonFrame = CGRect(
|
||||
origin: CGPoint(x: floorToScreenPixels(availableSize.width / 4.0 * 3.0 + 3.0 - curvesButtonSize.width / 2.0), y: buttonBottomInset),
|
||||
size: curvesButtonSize
|
||||
)
|
||||
if let curvesButtonView = self.curvesButton.view {
|
||||
if curvesButtonView.superview == nil {
|
||||
self.buttonsContainerView.addSubview(curvesButtonView)
|
||||
}
|
||||
transition.setFrame(view: curvesButtonView, frame: curvesButtonFrame)
|
||||
}
|
||||
|
||||
var sectionChanged = false
|
||||
if previousSection != component.section {
|
||||
sectionChanged = true
|
||||
if let previousView = self.toolOptions.view {
|
||||
previousView.layer.allowsGroupOpacity = true
|
||||
previousView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak previousView] _ in
|
||||
previousView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
self.toolOptions = ComponentView<Empty>()
|
||||
}
|
||||
|
||||
var toolScreen: ComponentView<Empty>?
|
||||
|
||||
if sectionChanged && previousSection != nil, let view = self.toolScreen?.view {
|
||||
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak view] _ in
|
||||
view?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
let screenSize: CGSize
|
||||
let optionsSize: CGSize
|
||||
let optionsTransition: Transition = sectionChanged ? .immediate : transition
|
||||
switch component.section {
|
||||
case .adjustments:
|
||||
self.curvesState = nil
|
||||
let tools: [AdjustmentTool] = [
|
||||
AdjustmentTool(
|
||||
key: .enhance,
|
||||
title: "Enhance",
|
||||
value: mediaEditor?.getToolValue(.enhance) as? Float ?? 0.0,
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
startValue: 0.0
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .brightness,
|
||||
title: "Brightness",
|
||||
value: mediaEditor?.getToolValue(.brightness) as? Float ?? 0.0,
|
||||
minValue: -1.0,
|
||||
maxValue: 1.0,
|
||||
startValue: 0.0
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .contrast,
|
||||
title: "Contrast",
|
||||
value: mediaEditor?.getToolValue(.contrast) as? Float ?? 0.0,
|
||||
minValue: -1.0,
|
||||
maxValue: 1.0,
|
||||
startValue: 0.0
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .saturation,
|
||||
title: "Saturation",
|
||||
value: mediaEditor?.getToolValue(.saturation) as? Float ?? 0.0,
|
||||
minValue: -1.0,
|
||||
maxValue: 1.0,
|
||||
startValue: 0.0
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .warmth,
|
||||
title: "Warmth",
|
||||
value: mediaEditor?.getToolValue(.warmth) as? Float ?? 0.0,
|
||||
minValue: -1.0,
|
||||
maxValue: 1.0,
|
||||
startValue: 0.0
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .fade,
|
||||
title: "Fade",
|
||||
value: mediaEditor?.getToolValue(.fade) as? Float ?? 0.0,
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
startValue: 0.0
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .highlights,
|
||||
title: "Highlights",
|
||||
value: mediaEditor?.getToolValue(.highlights) as? Float ?? 0.0,
|
||||
minValue: -1.0,
|
||||
maxValue: 1.0,
|
||||
startValue: 0.0
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .shadows,
|
||||
title: "Shadows",
|
||||
value: mediaEditor?.getToolValue(.shadows) as? Float ?? 0.0,
|
||||
minValue: -1.0,
|
||||
maxValue: 1.0,
|
||||
startValue: 0.0
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .vignette,
|
||||
title: "Vignette",
|
||||
value: mediaEditor?.getToolValue(.vignette) as? Float ?? 0.0,
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
startValue: 0.0
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .grain,
|
||||
title: "Grain",
|
||||
value: mediaEditor?.getToolValue(.grain) as? Float ?? 0.0,
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
startValue: 0.0
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .sharpen,
|
||||
title: "Sharpen",
|
||||
value: mediaEditor?.getToolValue(.sharpen) as? Float ?? 0.0,
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
startValue: 0.0
|
||||
)
|
||||
]
|
||||
optionsSize = self.toolOptions.update(
|
||||
transition: optionsTransition,
|
||||
component: AnyComponent(AdjustmentsComponent(
|
||||
tools: tools,
|
||||
valueUpdated: { [weak state] key, value in
|
||||
if let controller = environment.controller() as? MediaToolsScreen {
|
||||
controller.mediaEditor.setToolValue(key, value: value)
|
||||
state?.updated()
|
||||
}
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
screenSize = previewContainerFrame.size
|
||||
self.toolScreen = nil
|
||||
case .highlights:
|
||||
self.curvesState = nil
|
||||
optionsSize = self.toolOptions.update(
|
||||
transition: optionsTransition,
|
||||
component: AnyComponent(TintComponent(
|
||||
shadowsValue: mediaEditor?.getToolValue(.shadowsTint) as? TintValue ?? TintValue.initial,
|
||||
highlightsValue: mediaEditor?.getToolValue(.highlightsTint) as? TintValue ?? TintValue.initial,
|
||||
shadowsValueUpdated: { [weak state] value in
|
||||
if let controller = environment.controller() as? MediaToolsScreen {
|
||||
controller.mediaEditor.setToolValue(.shadowsTint, value: value)
|
||||
state?.updated()
|
||||
}
|
||||
},
|
||||
highlightsValueUpdated: { [weak state] value in
|
||||
if let controller = environment.controller() as? MediaToolsScreen {
|
||||
controller.mediaEditor.setToolValue(.highlightsTint, value: value)
|
||||
state?.updated()
|
||||
}
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
screenSize = previewContainerFrame.size
|
||||
self.toolScreen = nil
|
||||
case .blur:
|
||||
self.curvesState = nil
|
||||
optionsSize = self.toolOptions.update(
|
||||
transition: optionsTransition,
|
||||
component: AnyComponent(BlurComponent(
|
||||
value: mediaEditor?.getToolValue(.blur) as? BlurValue ?? BlurValue.initial,
|
||||
hasPortrait: mediaEditor?.hasPortraitMask ?? false,
|
||||
valueUpdated: { [weak state] value in
|
||||
if let controller = environment.controller() as? MediaToolsScreen {
|
||||
controller.mediaEditor.setToolValue(.blur, value: value)
|
||||
state?.updated()
|
||||
}
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let blurToolScreen: ComponentView<Empty>
|
||||
if let current = self.toolScreen, !sectionChanged {
|
||||
blurToolScreen = current
|
||||
} else {
|
||||
blurToolScreen = ComponentView<Empty>()
|
||||
self.toolScreen = blurToolScreen
|
||||
}
|
||||
toolScreen = blurToolScreen
|
||||
screenSize = blurToolScreen.update(
|
||||
transition: optionsTransition,
|
||||
component: AnyComponent(
|
||||
BlurScreenComponent(
|
||||
value: mediaEditor?.getToolValue(.blur) as? BlurValue ?? BlurValue.initial,
|
||||
valueUpdated: { [weak state] value in
|
||||
if let controller = environment.controller() as? MediaToolsScreen {
|
||||
controller.mediaEditor.setToolValue(.blur, value: value)
|
||||
state?.updated()
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: previewContainerFrame.width, height: previewContainerFrame.height - optionsSize.height)
|
||||
)
|
||||
case .curves:
|
||||
let internalState: CurvesInternalState
|
||||
if let current = self.curvesState {
|
||||
internalState = current
|
||||
} else {
|
||||
internalState = CurvesInternalState()
|
||||
self.curvesState = internalState
|
||||
}
|
||||
self.toolOptions.parentState = state
|
||||
optionsSize = self.toolOptions.update(
|
||||
transition: optionsTransition,
|
||||
component: AnyComponent(CurvesComponent(
|
||||
histogram: state.histogram,
|
||||
internalState: internalState
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let curvesToolScreen: ComponentView<Empty>
|
||||
if let current = self.toolScreen, !sectionChanged {
|
||||
curvesToolScreen = current
|
||||
} else {
|
||||
curvesToolScreen = ComponentView<Empty>()
|
||||
self.toolScreen = curvesToolScreen
|
||||
}
|
||||
toolScreen = curvesToolScreen
|
||||
screenSize = curvesToolScreen.update(
|
||||
transition: optionsTransition,
|
||||
component: AnyComponent(
|
||||
CurvesScreenComponent(
|
||||
value: mediaEditor?.getToolValue(.curves) as? CurvesValue ?? CurvesValue.initial,
|
||||
section: internalState.section,
|
||||
valueUpdated: { [weak state] value in
|
||||
if let controller = environment.controller() as? MediaToolsScreen {
|
||||
controller.mediaEditor.setToolValue(.curves, value: value)
|
||||
state?.updated()
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: previewContainerFrame.width, height: previewContainerFrame.height - optionsSize.height)
|
||||
)
|
||||
}
|
||||
|
||||
let optionsFrame = CGRect(origin: .zero, size: optionsSize)
|
||||
if let optionsView = self.toolOptions.view {
|
||||
if optionsView.superview == nil {
|
||||
self.optionsContainerView.addSubview(optionsView)
|
||||
}
|
||||
optionsTransition.setFrame(view: optionsView, frame: optionsFrame)
|
||||
|
||||
if sectionChanged && previousSection != nil {
|
||||
optionsView.layer.allowsGroupOpacity = true
|
||||
optionsView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
|
||||
optionsView.layer.allowsGroupOpacity = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let optionsBackgroundFrame = CGRect(
|
||||
origin: CGPoint(x: 0.0, y: previewContainerFrame.height - optionsSize.height),
|
||||
size: optionsSize
|
||||
)
|
||||
transition.setFrame(view: self.optionsContainerView, frame: optionsBackgroundFrame)
|
||||
|
||||
if let toolScreen = toolScreen {
|
||||
let screenFrame = CGRect(origin: .zero, size: screenSize)
|
||||
if let screenView = toolScreen.view {
|
||||
if screenView.superview == nil {
|
||||
self.previewContainerView.insertSubview(screenView, at: 0)
|
||||
|
||||
screenView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
optionsTransition.setFrame(view: screenView, frame: screenFrame)
|
||||
}
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.previewContainerView, frame: previewContainerFrame)
|
||||
transition.setFrame(view: self.buttonsContainerView, frame: buttonsContainerFrame)
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private let storyDimensions = CGSize(width: 1080.0, height: 1920.0)
|
||||
|
||||
public final class MediaToolsScreen: ViewController {
|
||||
fileprivate final class Node: ViewControllerTracingNode, UIGestureRecognizerDelegate {
|
||||
private weak var controller: MediaToolsScreen?
|
||||
private let context: AccountContext
|
||||
|
||||
fileprivate let componentHost: ComponentView<ViewControllerComponentContainer.Environment>
|
||||
private var currentSection: MediaToolsSection = .adjustments
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
init(controller: MediaToolsScreen) {
|
||||
self.controller = controller
|
||||
self.context = controller.context
|
||||
|
||||
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.componentHost = ComponentView<ViewControllerComponentContainer.Environment>()
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = .clear
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
self.view.disablesInteractiveKeyboardGestureRecognizer = true
|
||||
|
||||
// let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:)))
|
||||
// panGestureRecognizer.delegate = self
|
||||
// panGestureRecognizer.minimumNumberOfTouches = 2
|
||||
// panGestureRecognizer.maximumNumberOfTouches = 2
|
||||
// self.previewContainerView.addGestureRecognizer(panGestureRecognizer)
|
||||
//
|
||||
// let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(self.handlePinch(_:)))
|
||||
// pinchGestureRecognizer.delegate = self
|
||||
// self.previewContainerView.addGestureRecognizer(pinchGestureRecognizer)
|
||||
//
|
||||
// let rotateGestureRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(self.handleRotate(_:)))
|
||||
// rotateGestureRecognizer.delegate = self
|
||||
// self.previewContainerView.addGestureRecognizer(rotateGestureRecognizer)
|
||||
}
|
||||
|
||||
@objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func animateInFromEditor() {
|
||||
if let view = self.componentHost.view as? MediaToolsScreenComponent.View {
|
||||
view.animateInFromEditor()
|
||||
}
|
||||
}
|
||||
|
||||
func animateOutToEditor(completion: @escaping () -> Void) {
|
||||
if let view = self.componentHost.view as? MediaToolsScreenComponent.View {
|
||||
view.animateOutToEditor(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, animateOut: Bool = false, transition: Transition) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let isFirstTime = self.validLayout == nil
|
||||
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 environment = ViewControllerComponentContainer.Environment(
|
||||
statusBarHeight: layout.statusBarHeight ?? 0.0,
|
||||
navigationHeight: 0.0,
|
||||
safeInsets: UIEdgeInsets(
|
||||
top: topInset,
|
||||
left: layout.safeInsets.left,
|
||||
bottom: topInset,
|
||||
right: layout.safeInsets.right
|
||||
),
|
||||
inputHeight: layout.inputHeight ?? 0.0,
|
||||
metrics: layout.metrics,
|
||||
deviceMetrics: layout.deviceMetrics,
|
||||
orientation: nil,
|
||||
isVisible: true,
|
||||
theme: self.presentationData.theme,
|
||||
strings: self.presentationData.strings,
|
||||
dateTimeFormat: self.presentationData.dateTimeFormat,
|
||||
controller: { [weak self] in
|
||||
return self?.controller
|
||||
}
|
||||
)
|
||||
|
||||
// var transition = transition
|
||||
// if isFirstTime {
|
||||
// transition = transition.withUserData(CameraScreenTransition.animateIn)
|
||||
// } else if animateOut {
|
||||
// transition = transition.withUserData(CameraScreenTransition.animateOut)
|
||||
// }
|
||||
|
||||
let componentSize = self.componentHost.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
MediaToolsScreenComponent(
|
||||
context: self.context,
|
||||
mediaEditor: controller.mediaEditor,
|
||||
section: self.currentSection,
|
||||
sectionUpdated: { [weak self] section in
|
||||
if let self {
|
||||
self.currentSection = section
|
||||
if let layout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout: layout, transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {
|
||||
environment
|
||||
},
|
||||
forceUpdate: forceUpdate || animateOut,
|
||||
containerSize: layout.size
|
||||
)
|
||||
if let componentView = self.componentHost.view {
|
||||
if componentView.superview == nil {
|
||||
self.view.insertSubview(componentView, at: 3)
|
||||
componentView.clipsToBounds = true
|
||||
}
|
||||
let componentFrame = CGRect(origin: .zero, size: componentSize)
|
||||
transition.setFrame(view: componentView, frame: CGRect(origin: componentFrame.origin, size: CGSize(width: componentFrame.width, height: componentFrame.height)))
|
||||
}
|
||||
|
||||
if isFirstTime {
|
||||
self.animateInFromEditor()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var node: Node {
|
||||
return self.displayNode as! Node
|
||||
}
|
||||
|
||||
fileprivate let context: AccountContext
|
||||
fileprivate let mediaEditor: MediaEditor
|
||||
|
||||
public var dismissed: () -> Void = {}
|
||||
|
||||
public init(context: AccountContext, mediaEditor: MediaEditor) {
|
||||
self.context = context
|
||||
self.mediaEditor = mediaEditor
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
self.navigationPresentation = .flatModal
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
|
||||
self.statusBar.statusBarStyle = .White
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = Node(controller: self)
|
||||
|
||||
super.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
func requestDismiss(animated: Bool) {
|
||||
self.dismissed()
|
||||
|
||||
self.node.animateOutToEditor(completion: {
|
||||
self.dismiss()
|
||||
})
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
(self.displayNode as! Node).containerLayoutUpdated(layout: layout, transition: Transition(transition))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user