mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-06 06:13:10 +00:00
Merge commit '5fe2e3f69b6087831bf100b218b5fef4ac6bb1de'
This commit is contained in:
commit
7e95cc3273
@ -8535,3 +8535,6 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"Privacy.Exceptions.DeleteAll" = "Delete All";
|
"Privacy.Exceptions.DeleteAll" = "Delete All";
|
||||||
"Privacy.Exceptions.DeleteAllConfirmation" = "Are you sure you want to delete all exceptions?";
|
"Privacy.Exceptions.DeleteAllConfirmation" = "Are you sure you want to delete all exceptions?";
|
||||||
|
|
||||||
|
"Attachment.EnableSpoiler" = "Hide With Spoiler";
|
||||||
|
"Attachment.DisableSpoiler" = "Disable Spoiler";
|
||||||
|
|||||||
@ -169,9 +169,13 @@ public final class AnimationNode : ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func loop() {
|
public func loop(count: Int? = nil) {
|
||||||
if let animationView = self.animationView() {
|
if let animationView = self.animationView() {
|
||||||
animationView.loopMode = .loop
|
if let count = count {
|
||||||
|
animationView.loopMode = .repeat(Float(count))
|
||||||
|
} else {
|
||||||
|
animationView.loopMode = .loop
|
||||||
|
}
|
||||||
animationView.play()
|
animationView.play()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
private func findTaggedViewImpl(view: UIView, tag: Any) -> UIView? {
|
public func findTaggedComponentViewImpl(view: UIView, tag: Any) -> UIView? {
|
||||||
if let view = view as? ComponentTaggedView {
|
if let view = view as? ComponentTaggedView {
|
||||||
if view.matches(tag: tag) {
|
if view.matches(tag: tag) {
|
||||||
return view
|
return view
|
||||||
@ -9,7 +9,7 @@ private func findTaggedViewImpl(view: UIView, tag: Any) -> UIView? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for subview in view.subviews {
|
for subview in view.subviews {
|
||||||
if let result = findTaggedViewImpl(view: subview, tag: tag) {
|
if let result = findTaggedComponentViewImpl(view: subview, tag: tag) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,7 +131,7 @@ public final class ComponentHostView<EnvironmentType>: UIView {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return findTaggedViewImpl(view: componentView, tag: tag)
|
return findTaggedComponentViewImpl(view: componentView, tag: tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +230,7 @@ public final class ComponentView<EnvironmentType> {
|
|||||||
guard let view = self.view else {
|
guard let view = self.view else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return findTaggedViewImpl(view: view, tag: tag)
|
return findTaggedComponentViewImpl(view: view, tag: tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -187,6 +187,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
|||||||
public let isTopPanelExpandedUpdated: (Bool, Transition) -> Void
|
public let isTopPanelExpandedUpdated: (Bool, Transition) -> Void
|
||||||
public let isTopPanelHiddenUpdated: (Bool, Transition) -> Void
|
public let isTopPanelHiddenUpdated: (Bool, Transition) -> Void
|
||||||
public let panelHideBehavior: PagerComponentPanelHideBehavior
|
public let panelHideBehavior: PagerComponentPanelHideBehavior
|
||||||
|
public let clipContentToTopPanel: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
contentInsets: UIEdgeInsets,
|
contentInsets: UIEdgeInsets,
|
||||||
@ -204,7 +205,8 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
|||||||
panelStateUpdated: ((PagerComponentPanelState, Transition) -> Void)?,
|
panelStateUpdated: ((PagerComponentPanelState, Transition) -> Void)?,
|
||||||
isTopPanelExpandedUpdated: @escaping (Bool, Transition) -> Void,
|
isTopPanelExpandedUpdated: @escaping (Bool, Transition) -> Void,
|
||||||
isTopPanelHiddenUpdated: @escaping (Bool, Transition) -> Void,
|
isTopPanelHiddenUpdated: @escaping (Bool, Transition) -> Void,
|
||||||
panelHideBehavior: PagerComponentPanelHideBehavior
|
panelHideBehavior: PagerComponentPanelHideBehavior,
|
||||||
|
clipContentToTopPanel: Bool
|
||||||
) {
|
) {
|
||||||
self.contentInsets = contentInsets
|
self.contentInsets = contentInsets
|
||||||
self.contents = contents
|
self.contents = contents
|
||||||
@ -222,6 +224,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
|||||||
self.isTopPanelExpandedUpdated = isTopPanelExpandedUpdated
|
self.isTopPanelExpandedUpdated = isTopPanelExpandedUpdated
|
||||||
self.isTopPanelHiddenUpdated = isTopPanelHiddenUpdated
|
self.isTopPanelHiddenUpdated = isTopPanelHiddenUpdated
|
||||||
self.panelHideBehavior = panelHideBehavior
|
self.panelHideBehavior = panelHideBehavior
|
||||||
|
self.clipContentToTopPanel = clipContentToTopPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: PagerComponent, rhs: PagerComponent) -> Bool {
|
public static func ==(lhs: PagerComponent, rhs: PagerComponent) -> Bool {
|
||||||
@ -258,6 +261,9 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
|||||||
if lhs.panelHideBehavior != rhs.panelHideBehavior {
|
if lhs.panelHideBehavior != rhs.panelHideBehavior {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.clipContentToTopPanel != rhs.clipContentToTopPanel {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -282,6 +288,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
|||||||
var fraction: CGFloat = 0.0
|
var fraction: CGFloat = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var contentClippingView: UIView
|
||||||
private var contentViews: [AnyHashable: ContentView] = [:]
|
private var contentViews: [AnyHashable: ContentView] = [:]
|
||||||
private var contentBackgroundView: ComponentHostView<Empty>?
|
private var contentBackgroundView: ComponentHostView<Empty>?
|
||||||
private let topPanelVisibilityFractionUpdated = ActionSlot<(CGFloat, Transition)>()
|
private let topPanelVisibilityFractionUpdated = ActionSlot<(CGFloat, Transition)>()
|
||||||
@ -307,8 +314,13 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
|||||||
}
|
}
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
|
self.contentClippingView = UIView()
|
||||||
|
self.contentClippingView.clipsToBounds = true
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.contentClippingView)
|
||||||
|
|
||||||
self.disablesInteractiveTransitionGestureRecognizer = true
|
self.disablesInteractiveTransitionGestureRecognizer = true
|
||||||
|
|
||||||
let panRecognizer = PagerPanGestureRecognizerImpl(target: self, action: #selector(self.panGesture(_:)))
|
let panRecognizer = PagerPanGestureRecognizerImpl(target: self, action: #selector(self.panGesture(_:)))
|
||||||
@ -444,6 +456,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
|||||||
self.centralId = centralId
|
self.centralId = centralId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let contentSize = CGSize(width: availableSize.width, height: availableSize.height)
|
||||||
var contentInsets = component.contentInsets
|
var contentInsets = component.contentInsets
|
||||||
contentInsets.bottom = 0.0
|
contentInsets.bottom = 0.0
|
||||||
var contentInsetTopPanelValue: CGFloat = 0.0
|
var contentInsetTopPanelValue: CGFloat = 0.0
|
||||||
@ -528,8 +541,15 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
|||||||
visibleTopPanelHeight = 0.0
|
visibleTopPanelHeight = 0.0
|
||||||
}
|
}
|
||||||
panelStateTransition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(), size: CGSize(width: topPanelSize.width, height: visibleTopPanelHeight)))
|
panelStateTransition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(), size: CGSize(width: topPanelSize.width, height: visibleTopPanelHeight)))
|
||||||
|
|
||||||
|
panelStateTransition.setFrame(view: self.contentClippingView, frame: CGRect(origin: .zero, size: contentSize))
|
||||||
|
panelStateTransition.setBounds(view: self.contentClippingView, bounds: CGRect(origin: .zero, size: contentSize))
|
||||||
} else {
|
} else {
|
||||||
panelStateTransition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelOffset), size: topPanelSize))
|
panelStateTransition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelOffset), size: topPanelSize))
|
||||||
|
|
||||||
|
let clippingOffset: CGFloat = component.clipContentToTopPanel ? topPanelSize.height - topPanelOffset : 0.0
|
||||||
|
panelStateTransition.setFrame(view: self.contentClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: clippingOffset), size: contentSize))
|
||||||
|
panelStateTransition.setBounds(view: self.contentClippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: clippingOffset), size: contentSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
contentInsetTopPanelValue = topPanelSize.height
|
contentInsetTopPanelValue = topPanelSize.height
|
||||||
@ -623,7 +643,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
|||||||
contentBackgroundTransition = .immediate
|
contentBackgroundTransition = .immediate
|
||||||
contentBackgroundView = ComponentHostView<Empty>()
|
contentBackgroundView = ComponentHostView<Empty>()
|
||||||
self.contentBackgroundView = contentBackgroundView
|
self.contentBackgroundView = contentBackgroundView
|
||||||
self.insertSubview(contentBackgroundView, at: 0)
|
self.contentClippingView.insertSubview(contentBackgroundView, at: 0)
|
||||||
}
|
}
|
||||||
let _ = contentBackgroundView.update(
|
let _ = contentBackgroundView.update(
|
||||||
transition: contentBackgroundTransition,
|
transition: contentBackgroundTransition,
|
||||||
@ -638,11 +658,9 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
|||||||
contentBackgroundView.removeFromSuperview()
|
contentBackgroundView.removeFromSuperview()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var validIds: [AnyHashable] = []
|
var validIds: [AnyHashable] = []
|
||||||
if let centralId = self.centralId, let centralIndex = component.contents.firstIndex(where: { $0.id == centralId }) {
|
if let centralId = self.centralId, let centralIndex = component.contents.firstIndex(where: { $0.id == centralId }) {
|
||||||
let contentSize = CGSize(width: availableSize.width, height: availableSize.height)
|
|
||||||
|
|
||||||
var referenceFrames: [AnyHashable: CGRect] = [:]
|
var referenceFrames: [AnyHashable: CGRect] = [:]
|
||||||
if case .none = transition.animation {
|
if case .none = transition.animation {
|
||||||
} else {
|
} else {
|
||||||
@ -686,9 +704,9 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
|||||||
contentTransition = transition.withAnimation(.none)
|
contentTransition = transition.withAnimation(.none)
|
||||||
self.contentViews[content.id] = contentView
|
self.contentViews[content.id] = contentView
|
||||||
if let contentBackgroundView = self.contentBackgroundView {
|
if let contentBackgroundView = self.contentBackgroundView {
|
||||||
self.insertSubview(contentView.view, aboveSubview: contentBackgroundView)
|
self.contentClippingView.insertSubview(contentView.view, aboveSubview: contentBackgroundView)
|
||||||
} else {
|
} else {
|
||||||
self.insertSubview(contentView.view, at: 0)
|
self.contentClippingView.insertSubview(contentView.view, at: 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -280,7 +280,7 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
|
|||||||
} else if let animationName = self.item.animationName {
|
} else if let animationName = self.item.animationName {
|
||||||
if self.animationNode == nil {
|
if self.animationNode == nil {
|
||||||
let animationNode = AnimationNode(animation: animationName, colors: ["__allcolors__": titleColor], scale: 1.0)
|
let animationNode = AnimationNode(animation: animationName, colors: ["__allcolors__": titleColor], scale: 1.0)
|
||||||
animationNode.loop()
|
animationNode.loop(count: 3)
|
||||||
self.addSubnode(animationNode)
|
self.addSubnode(animationNode)
|
||||||
self.animationNode = animationNode
|
self.animationNode = animationNode
|
||||||
}
|
}
|
||||||
|
|||||||
@ -718,19 +718,9 @@ private func generateSpectrumImage(size: CGSize) -> UIImage? {
|
|||||||
if let image = UIImage(bundleImageName: "Media Editor/Spectrum") {
|
if let image = UIImage(bundleImageName: "Media Editor/Spectrum") {
|
||||||
context.draw(image.cgImage!, in: CGRect(origin: .zero, size: size))
|
context.draw(image.cgImage!, in: CGRect(origin: .zero, size: size))
|
||||||
}
|
}
|
||||||
|
if let image = UIImage(bundleImageName: "Media Editor/Grayscale") {
|
||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
context.draw(image.cgImage!, in: CGRect(origin: .zero, size: size))
|
||||||
var colors = [UIColor(rgb: 0xffffff).cgColor, UIColor(rgb: 0xffffff, alpha: 0.0).cgColor, UIColor(rgb: 0xffffff, alpha: 0.0).cgColor]
|
}
|
||||||
var locations: [CGFloat] = [0.0, 0.45, 1.0]
|
|
||||||
|
|
||||||
var gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
|
||||||
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: size.width, y: 0.0), options: [.drawsAfterEndLocation])
|
|
||||||
|
|
||||||
colors = [UIColor(rgb: 0x000000, alpha: 0.0).cgColor, UIColor(rgb: 0x000000, alpha: 0.0).cgColor, UIColor(rgb: 0x000000).cgColor]
|
|
||||||
locations = [0.0, 0.5, 1.0]
|
|
||||||
|
|
||||||
gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
|
||||||
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: size.width, y: 0.0), options: [.drawsAfterEndLocation])
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -292,14 +292,32 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func removeAll() {
|
func removeAll() {
|
||||||
self.clear()
|
self.clear(animated: true)
|
||||||
self.selectionChanged(nil)
|
self.selectionChanged(nil)
|
||||||
self.hasSelectionChanged(false)
|
self.hasSelectionChanged(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func clear() {
|
private func clear(animated: Bool = false) {
|
||||||
for case let view as DrawingEntityView in self.subviews {
|
if animated {
|
||||||
view.removeFromSuperview()
|
for case let view as DrawingEntityView in self.subviews {
|
||||||
|
if let selectionView = view.selectionView {
|
||||||
|
selectionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak selectionView] _ in
|
||||||
|
selectionView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak view] _ in
|
||||||
|
view?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
if !(view.entity is DrawingVectorEntity) {
|
||||||
|
view.layer.animateScale(from: 0.0, to: -0.99, duration: 0.2, removeOnCompletion: false, additive: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for case let view as DrawingEntityView in self.subviews {
|
||||||
|
view.selectionView?.removeFromSuperview()
|
||||||
|
view.removeFromSuperview()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -490,8 +490,6 @@ class DrawingGesturePipeline {
|
|||||||
return gf
|
return gf
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates the Gram Polynomial ( s = 0 ), or its s'th
|
|
||||||
/// derivative evaluated at i, order k, over 2m + 1 points
|
|
||||||
private static func gramPoly(_ index: Int, _ window: Int, _ order: Int, _ derivative: Int) -> CGFloat {
|
private static func gramPoly(_ index: Int, _ window: Int, _ order: Int, _ derivative: Int) -> CGFloat {
|
||||||
var gp_val: CGFloat
|
var gp_val: CGFloat
|
||||||
|
|
||||||
@ -513,8 +511,6 @@ class DrawingGesturePipeline {
|
|||||||
return gp_val
|
return gp_val
|
||||||
}
|
}
|
||||||
|
|
||||||
/// calculates the weight of the i'th data point for the t'th Least-square
|
|
||||||
/// point of the s'th derivative, over 2m + 1 points, order n
|
|
||||||
private static func calcWeight(_ index: Int, _ windowLoc: Int, _ windowSize: Int, _ order: Int, _ derivative: Int) -> CGFloat {
|
private static func calcWeight(_ index: Int, _ windowLoc: Int, _ windowSize: Int, _ order: Int, _ derivative: Int) -> CGFloat {
|
||||||
var sum: CGFloat = 0.0
|
var sum: CGFloat = 0.0
|
||||||
|
|
||||||
@ -1002,10 +998,10 @@ struct Polyline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public internal(set) var isComplete: Bool
|
var isComplete: Bool
|
||||||
public let touchIdentifier: String
|
let touchIdentifier: String
|
||||||
public var points: [Point]
|
var points: [Point]
|
||||||
public var bounds: CGRect {
|
var bounds: CGRect {
|
||||||
return self.points.reduce(.null) { partialResult, point -> CGRect in
|
return self.points.reduce(.null) { partialResult, point -> CGRect in
|
||||||
return CGRect(x: min(partialResult.origin.x, point.x),
|
return CGRect(x: min(partialResult.origin.x, point.x),
|
||||||
y: min(partialResult.origin.y, point.y),
|
y: min(partialResult.origin.y, point.y),
|
||||||
@ -1144,7 +1140,7 @@ private class BezierBuilder {
|
|||||||
private class Smoother {
|
private class Smoother {
|
||||||
let smoothFactor: CGFloat
|
let smoothFactor: CGFloat
|
||||||
|
|
||||||
init(smoothFactor: CGFloat = 0.7) {
|
init(smoothFactor: CGFloat = 1.0) {
|
||||||
self.smoothFactor = smoothFactor
|
self.smoothFactor = smoothFactor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -693,8 +693,26 @@ final class Texture {
|
|||||||
|
|
||||||
func createCGImage() -> CGImage? {
|
func createCGImage() -> CGImage? {
|
||||||
let dataProvider: CGDataProvider
|
let dataProvider: CGDataProvider
|
||||||
if #available(iOS 12.0, *) {
|
// if #available(iOS 12.0, *) {
|
||||||
#if targetEnvironment(simulator)
|
//#if targetEnvironment(simulator)
|
||||||
|
// guard let data = NSMutableData(capacity: self.bytesPerRow * self.height) else {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
// data.length = self.bytesPerRow * self.height
|
||||||
|
// self.texture.getBytes(data.mutableBytes, bytesPerRow: self.bytesPerRow, bytesPerImage: self.bytesPerRow * self.height, from: MTLRegion(origin: MTLOrigin(), size: MTLSize(width: self.width, height: self.height, depth: 1)), mipmapLevel: 0, slice: 0)
|
||||||
|
//
|
||||||
|
// guard let provider = CGDataProvider(data: data as CFData) else {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
// dataProvider = provider
|
||||||
|
//#else
|
||||||
|
// guard let buffer = self.buffer, let provider = CGDataProvider(data: Data(bytesNoCopy: buffer.contents(), count: buffer.length, deallocator: .custom { _, _ in
|
||||||
|
// }) as CFData) else {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
// dataProvider = provider
|
||||||
|
//#endif
|
||||||
|
// } else {
|
||||||
guard let data = NSMutableData(capacity: self.bytesPerRow * self.height) else {
|
guard let data = NSMutableData(capacity: self.bytesPerRow * self.height) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -705,25 +723,7 @@ final class Texture {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
dataProvider = provider
|
dataProvider = provider
|
||||||
#else
|
// }
|
||||||
guard let buffer = self.buffer, let provider = CGDataProvider(data: Data(bytesNoCopy: buffer.contents(), count: buffer.length, deallocator: .custom { _, _ in
|
|
||||||
}) as CFData) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
dataProvider = provider
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
guard let data = NSMutableData(capacity: self.bytesPerRow * self.height) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
data.length = self.bytesPerRow * self.height
|
|
||||||
self.texture.getBytes(data.mutableBytes, bytesPerRow: self.bytesPerRow, bytesPerImage: self.bytesPerRow * self.height, from: MTLRegion(origin: MTLOrigin(), size: MTLSize(width: self.width, height: self.height, depth: 1)), mipmapLevel: 0, slice: 0)
|
|
||||||
|
|
||||||
guard let provider = CGDataProvider(data: data as CFData) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
dataProvider = provider
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let image = CGImage(
|
guard let image = CGImage(
|
||||||
width: Int(self.width),
|
width: Int(self.width),
|
||||||
|
|||||||
@ -180,8 +180,8 @@ struct DrawingState: Equatable {
|
|||||||
return DrawingState(
|
return DrawingState(
|
||||||
selectedTool: .pen,
|
selectedTool: .pen,
|
||||||
tools: [
|
tools: [
|
||||||
.pen(DrawingToolState.BrushState(color: DrawingColor(rgb: 0xff453a), size: 0.2)),
|
.pen(DrawingToolState.BrushState(color: DrawingColor(rgb: 0xff453a), size: 0.23)),
|
||||||
.arrow(DrawingToolState.BrushState(color: DrawingColor(rgb: 0xff8a00), size: 0.2)),
|
.arrow(DrawingToolState.BrushState(color: DrawingColor(rgb: 0xff8a00), size: 0.23)),
|
||||||
.marker(DrawingToolState.BrushState(color: DrawingColor(rgb: 0xffd60a), size: 0.75)),
|
.marker(DrawingToolState.BrushState(color: DrawingColor(rgb: 0xffd60a), size: 0.75)),
|
||||||
.neon(DrawingToolState.BrushState(color: DrawingColor(rgb: 0x34c759), size: 0.4)),
|
.neon(DrawingToolState.BrushState(color: DrawingColor(rgb: 0x34c759), size: 0.4)),
|
||||||
.eraser(DrawingToolState.EraserState(size: 0.5)),
|
.eraser(DrawingToolState.EraserState(size: 0.5)),
|
||||||
@ -216,6 +216,7 @@ private let addButtonTag = GenericComponentViewTag()
|
|||||||
private let toolsTag = GenericComponentViewTag()
|
private let toolsTag = GenericComponentViewTag()
|
||||||
private let modeTag = GenericComponentViewTag()
|
private let modeTag = GenericComponentViewTag()
|
||||||
private let flipButtonTag = GenericComponentViewTag()
|
private let flipButtonTag = GenericComponentViewTag()
|
||||||
|
private let fillButtonTag = GenericComponentViewTag()
|
||||||
private let textSettingsTag = GenericComponentViewTag()
|
private let textSettingsTag = GenericComponentViewTag()
|
||||||
private let sizeSliderTag = GenericComponentViewTag()
|
private let sizeSliderTag = GenericComponentViewTag()
|
||||||
private let color1Tag = GenericComponentViewTag()
|
private let color1Tag = GenericComponentViewTag()
|
||||||
@ -374,7 +375,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
self.currentMode = .drawing
|
self.currentMode = .drawing
|
||||||
self.drawingState = .initial
|
self.drawingState = .initial
|
||||||
self.drawingViewState = DrawingView.NavigationState(canUndo: false, canRedo: false, canClear: false, canZoomOut: false)
|
self.drawingViewState = DrawingView.NavigationState(canUndo: false, canRedo: false, canClear: false, canZoomOut: false, isDrawing: false)
|
||||||
self.currentColor = self.drawingState.tools.first?.color ?? DrawingColor(rgb: 0xffffff)
|
self.currentColor = self.drawingState.tools.first?.color ?? DrawingColor(rgb: 0xffffff)
|
||||||
|
|
||||||
self.updateToolState.invoke(self.drawingState.currentToolState)
|
self.updateToolState.invoke(self.drawingState.currentToolState)
|
||||||
@ -449,14 +450,18 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
self.updated(transition: animated ? .easeInOut(duration: 0.2) : .immediate)
|
self.updated(transition: animated ? .easeInOut(duration: 0.2) : .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSelectedTool(_ tool: DrawingToolState.Key) {
|
func updateSelectedTool(_ tool: DrawingToolState.Key, update: Bool = true) {
|
||||||
if self.selectedEntity != nil {
|
if self.selectedEntity != nil {
|
||||||
self.updateCurrentMode(.drawing)
|
self.skipSelectedEntityUpdate = true
|
||||||
|
self.updateCurrentMode(.drawing, update: false)
|
||||||
|
self.skipSelectedEntityUpdate = false
|
||||||
}
|
}
|
||||||
self.drawingState = self.drawingState.withUpdatedSelectedTool(tool)
|
self.drawingState = self.drawingState.withUpdatedSelectedTool(tool)
|
||||||
self.currentColor = self.drawingState.currentToolState.color ?? self.currentColor
|
self.currentColor = self.drawingState.currentToolState.color ?? self.currentColor
|
||||||
self.updateToolState.invoke(self.drawingState.currentToolState)
|
self.updateToolState.invoke(self.drawingState.currentToolState)
|
||||||
self.updated(transition: .easeInOut(duration: 0.2))
|
if update {
|
||||||
|
self.updated(transition: .easeInOut(duration: 0.2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateBrushSize(_ size: CGFloat) {
|
func updateBrushSize(_ size: CGFloat) {
|
||||||
@ -479,6 +484,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
self.updated(transition: .easeInOut(duration: 0.2))
|
self.updated(transition: .easeInOut(duration: 0.2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var skipSelectedEntityUpdate = false
|
||||||
func updateSelectedEntity(_ entity: DrawingEntity?) {
|
func updateSelectedEntity(_ entity: DrawingEntity?) {
|
||||||
self.selectedEntity = entity
|
self.selectedEntity = entity
|
||||||
if let entity = entity {
|
if let entity = entity {
|
||||||
@ -496,7 +502,9 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
self.currentMode = .drawing
|
self.currentMode = .drawing
|
||||||
self.currentColor = self.drawingState.currentToolState.color ?? self.currentColor
|
self.currentColor = self.drawingState.currentToolState.color ?? self.currentColor
|
||||||
}
|
}
|
||||||
self.updated(transition: .easeInOut(duration: 0.2))
|
if !self.skipSelectedEntityUpdate {
|
||||||
|
self.updated(transition: .easeInOut(duration: 0.2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentShapePicker(_ sourceView: UIView) {
|
func presentShapePicker(_ sourceView: UIView) {
|
||||||
@ -636,9 +644,6 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
let flipButton = Child(Button.self)
|
let flipButton = Child(Button.self)
|
||||||
let fillButton = Child(Button.self)
|
let fillButton = Child(Button.self)
|
||||||
let fillButtonTag = GenericComponentViewTag()
|
|
||||||
|
|
||||||
let stickerFlipButton = Child(Button.self)
|
|
||||||
|
|
||||||
let backButton = Child(Button.self)
|
let backButton = Child(Button.self)
|
||||||
let doneButton = Child(Button.self)
|
let doneButton = Child(Button.self)
|
||||||
@ -683,7 +688,10 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
let presentFastColorPicker = component.presentFastColorPicker
|
let presentFastColorPicker = component.presentFastColorPicker
|
||||||
let updateFastColorPickerPan = component.updateFastColorPickerPan
|
let updateFastColorPickerPan = component.updateFastColorPickerPan
|
||||||
let dismissFastColorPicker = component.dismissFastColorPicker
|
let dismissFastColorPicker = component.dismissFastColorPicker
|
||||||
|
|
||||||
|
let topInset = environment.safeInsets.top + 31.0
|
||||||
|
let bottomInset: CGFloat = environment.inputHeight > 0.0 ? environment.inputHeight : 145.0
|
||||||
|
|
||||||
if let textEntity = state.selectedEntity as? DrawingTextEntity {
|
if let textEntity = state.selectedEntity as? DrawingTextEntity {
|
||||||
let textSettings = textSettings.update(
|
let textSettings = textSettings.update(
|
||||||
component: TextSettingsComponent(
|
component: TextSettingsComponent(
|
||||||
@ -692,6 +700,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
alignment: DrawingTextAlignment(alignment: textEntity.alignment),
|
alignment: DrawingTextAlignment(alignment: textEntity.alignment),
|
||||||
font: DrawingTextFont(font: textEntity.font),
|
font: DrawingTextFont(font: textEntity.font),
|
||||||
isEmojiKeyboard: false,
|
isEmojiKeyboard: false,
|
||||||
|
tag: textSettingsTag,
|
||||||
toggleStyle: { [weak state, weak textEntity] in
|
toggleStyle: { [weak state, weak textEntity] in
|
||||||
guard let textEntity = textEntity else {
|
guard let textEntity = textEntity else {
|
||||||
return
|
return
|
||||||
@ -749,8 +758,18 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
context.add(textSettings
|
context.add(textSettings
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - environment.safeInsets.bottom - textSettings.size.height / 2.0 - 89.0))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - environment.safeInsets.bottom - textSettings.size.height / 2.0 - 89.0))
|
||||||
.appear(.default(scale: false, alpha: true))
|
.appear(Transition.Appear({ _, view, transition in
|
||||||
.disappear(.default(scale: false, alpha: true))
|
if let view = findTaggedComponentViewImpl(view: view, tag: textSettingsTag) as? TextFontComponent.View, !transition.animation.isImmediate {
|
||||||
|
view.animateIn()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.disappear(Transition.Disappear({ view, transition, completion in
|
||||||
|
if let view = findTaggedComponentViewImpl(view: view, tag: textSettingsTag) as? TextFontComponent.View, !transition.animation.isImmediate {
|
||||||
|
view.animateOut(completion: completion)
|
||||||
|
} else {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
}))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -762,16 +781,21 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
let applySwatchColor: (DrawingColor) -> Void = { [weak state] color in
|
let applySwatchColor: (DrawingColor) -> Void = { [weak state] color in
|
||||||
if let state = state {
|
if let state = state {
|
||||||
if [.eraser, .blur].contains(state.drawingState.selectedTool) || state.selectedEntity is DrawingStickerEntity {
|
if [.eraser, .blur].contains(state.drawingState.selectedTool) || state.selectedEntity is DrawingStickerEntity {
|
||||||
state.updateSelectedTool(.pen)
|
state.updateSelectedTool(.pen, update: false)
|
||||||
}
|
}
|
||||||
state.updateColor(color, animated: true)
|
state.updateColor(color, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var currentColor: DrawingColor? = state.currentColor
|
||||||
|
if [.eraser, .blur].contains(state.drawingState.selectedTool) || state.selectedEntity is DrawingStickerEntity {
|
||||||
|
currentColor = nil
|
||||||
|
}
|
||||||
|
|
||||||
var delay: Double = 0.0
|
var delay: Double = 0.0
|
||||||
let swatch1Button = swatch1Button.update(
|
let swatch1Button = swatch1Button.update(
|
||||||
component: ColorSwatchComponent(
|
component: ColorSwatchComponent(
|
||||||
type: .pallete(state.currentColor == presetColors[0]),
|
type: .pallete(currentColor == presetColors[0]),
|
||||||
color: presetColors[0],
|
color: presetColors[0],
|
||||||
tag: color1Tag,
|
tag: color1Tag,
|
||||||
action: {
|
action: {
|
||||||
@ -798,7 +822,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
let swatch2Button = swatch2Button.update(
|
let swatch2Button = swatch2Button.update(
|
||||||
component: ColorSwatchComponent(
|
component: ColorSwatchComponent(
|
||||||
type: .pallete(state.currentColor == presetColors[1]),
|
type: .pallete(currentColor == presetColors[1]),
|
||||||
color: presetColors[1],
|
color: presetColors[1],
|
||||||
tag: color2Tag,
|
tag: color2Tag,
|
||||||
action: {
|
action: {
|
||||||
@ -825,7 +849,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
let swatch3Button = swatch3Button.update(
|
let swatch3Button = swatch3Button.update(
|
||||||
component: ColorSwatchComponent(
|
component: ColorSwatchComponent(
|
||||||
type: .pallete(state.currentColor == presetColors[2]),
|
type: .pallete(currentColor == presetColors[2]),
|
||||||
color: presetColors[2],
|
color: presetColors[2],
|
||||||
tag: color3Tag,
|
tag: color3Tag,
|
||||||
action: {
|
action: {
|
||||||
@ -852,7 +876,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
let swatch4Button = swatch4Button.update(
|
let swatch4Button = swatch4Button.update(
|
||||||
component: ColorSwatchComponent(
|
component: ColorSwatchComponent(
|
||||||
type: .pallete(state.currentColor == presetColors[3]),
|
type: .pallete(currentColor == presetColors[3]),
|
||||||
color: presetColors[3],
|
color: presetColors[3],
|
||||||
tag: color4Tag,
|
tag: color4Tag,
|
||||||
action: {
|
action: {
|
||||||
@ -879,7 +903,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
let swatch5Button = swatch5Button.update(
|
let swatch5Button = swatch5Button.update(
|
||||||
component: ColorSwatchComponent(
|
component: ColorSwatchComponent(
|
||||||
type: .pallete(state.currentColor == presetColors[4]),
|
type: .pallete(currentColor == presetColors[4]),
|
||||||
color: presetColors[4],
|
color: presetColors[4],
|
||||||
tag: color5Tag,
|
tag: color5Tag,
|
||||||
action: {
|
action: {
|
||||||
@ -907,7 +931,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
let swatch6Button = swatch6Button.update(
|
let swatch6Button = swatch6Button.update(
|
||||||
component: ColorSwatchComponent(
|
component: ColorSwatchComponent(
|
||||||
type: .pallete(state.currentColor == presetColors[5]),
|
type: .pallete(currentColor == presetColors[5]),
|
||||||
color: presetColors[5],
|
color: presetColors[5],
|
||||||
tag: color6Tag,
|
tag: color6Tag,
|
||||||
action: {
|
action: {
|
||||||
@ -934,7 +958,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
let swatch7Button = swatch7Button.update(
|
let swatch7Button = swatch7Button.update(
|
||||||
component: ColorSwatchComponent(
|
component: ColorSwatchComponent(
|
||||||
type: .pallete(state.currentColor == presetColors[6]),
|
type: .pallete(currentColor == presetColors[6]),
|
||||||
color: presetColors[6],
|
color: presetColors[6],
|
||||||
tag: color7Tag,
|
tag: color7Tag,
|
||||||
action: {
|
action: {
|
||||||
@ -961,7 +985,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
let swatch8Button = swatch8Button.update(
|
let swatch8Button = swatch8Button.update(
|
||||||
component: ColorSwatchComponent(
|
component: ColorSwatchComponent(
|
||||||
type: .pallete(state.currentColor == presetColors[7]),
|
type: .pallete(currentColor == presetColors[7]),
|
||||||
color: presetColors[7],
|
color: presetColors[7],
|
||||||
tag: color8Tag,
|
tag: color8Tag,
|
||||||
action: {
|
action: {
|
||||||
@ -985,7 +1009,8 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
if state.selectedEntity == nil {
|
if state.selectedEntity is DrawingStickerEntity || state.selectedEntity is DrawingTextEntity {
|
||||||
|
} else {
|
||||||
let tools = tools.update(
|
let tools = tools.update(
|
||||||
component: ToolsComponent(
|
component: ToolsComponent(
|
||||||
state: state.drawingState,
|
state: state.drawingState,
|
||||||
@ -1026,6 +1051,104 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hasTopButtons = false
|
||||||
|
if let entity = state.selectedEntity {
|
||||||
|
var isFilled: Bool?
|
||||||
|
if let entity = entity as? DrawingSimpleShapeEntity {
|
||||||
|
isFilled = entity.drawType == .fill
|
||||||
|
} else if let entity = entity as? DrawingBubbleEntity {
|
||||||
|
isFilled = entity.drawType == .fill
|
||||||
|
} else if let _ = entity as? DrawingVectorEntity {
|
||||||
|
isFilled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasFlip = false
|
||||||
|
if state.selectedEntity is DrawingBubbleEntity || state.selectedEntity is DrawingStickerEntity {
|
||||||
|
hasFlip = true
|
||||||
|
}
|
||||||
|
|
||||||
|
hasTopButtons = isFilled != nil || hasFlip
|
||||||
|
|
||||||
|
if let isFilled = isFilled {
|
||||||
|
let fillButton = fillButton.update(
|
||||||
|
component: Button(
|
||||||
|
content: AnyComponent(
|
||||||
|
Image(image: state.image(isFilled ? .fill : .stroke))
|
||||||
|
),
|
||||||
|
action: { [weak state] in
|
||||||
|
guard let state = state else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let entity = state.selectedEntity as? DrawingSimpleShapeEntity {
|
||||||
|
if case .fill = entity.drawType {
|
||||||
|
entity.drawType = .stroke
|
||||||
|
} else {
|
||||||
|
entity.drawType = .fill
|
||||||
|
}
|
||||||
|
entity.currentEntityView?.update()
|
||||||
|
} else if let entity = state.selectedEntity as? DrawingBubbleEntity {
|
||||||
|
if case .fill = entity.drawType {
|
||||||
|
entity.drawType = .stroke
|
||||||
|
} else {
|
||||||
|
entity.drawType = .fill
|
||||||
|
}
|
||||||
|
entity.currentEntityView?.update()
|
||||||
|
} else if let entity = state.selectedEntity as? DrawingVectorEntity {
|
||||||
|
if case .oneSidedArrow = entity.type {
|
||||||
|
entity.type = .twoSidedArrow
|
||||||
|
} else if case .twoSidedArrow = entity.type {
|
||||||
|
entity.type = .line
|
||||||
|
} else {
|
||||||
|
entity.type = .oneSidedArrow
|
||||||
|
}
|
||||||
|
entity.currentEntityView?.update()
|
||||||
|
}
|
||||||
|
state.updated(transition: .easeInOut(duration: 0.2))
|
||||||
|
}
|
||||||
|
).minSize(CGSize(width: 44.0, height: 44.0)).tagged(fillButtonTag),
|
||||||
|
availableSize: CGSize(width: 30.0, height: 30.0),
|
||||||
|
transition: .immediate
|
||||||
|
)
|
||||||
|
context.add(fillButton
|
||||||
|
.position(CGPoint(x: context.availableSize.width / 2.0 - (hasFlip ? 46.0 : 0.0), y: environment.safeInsets.top + 31.0))
|
||||||
|
.appear(.default(scale: true))
|
||||||
|
.disappear(.default(scale: true))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasFlip {
|
||||||
|
let flipButton = flipButton.update(
|
||||||
|
component: Button(
|
||||||
|
content: AnyComponent(
|
||||||
|
Image(image: state.image(.flip))
|
||||||
|
),
|
||||||
|
action: { [weak state] in
|
||||||
|
guard let state = state else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let entity = state.selectedEntity as? DrawingBubbleEntity {
|
||||||
|
var updatedTailPosition = entity.tailPosition
|
||||||
|
updatedTailPosition.x = 1.0 - updatedTailPosition.x
|
||||||
|
entity.tailPosition = updatedTailPosition
|
||||||
|
entity.currentEntityView?.update()
|
||||||
|
} else if let entity = state.selectedEntity as? DrawingStickerEntity {
|
||||||
|
entity.mirrored = !entity.mirrored
|
||||||
|
entity.currentEntityView?.update(animated: true)
|
||||||
|
}
|
||||||
|
state.updated(transition: .easeInOut(duration: 0.2))
|
||||||
|
}
|
||||||
|
).minSize(CGSize(width: 44.0, height: 44.0)).tagged(flipButtonTag),
|
||||||
|
availableSize: CGSize(width: 30.0, height: 30.0),
|
||||||
|
transition: .immediate
|
||||||
|
)
|
||||||
|
context.add(flipButton
|
||||||
|
.position(CGPoint(x: context.availableSize.width / 2.0 + (isFilled != nil ? 46.0 : 0.0), y: environment.safeInsets.top + 31.0))
|
||||||
|
.appear(.default(scale: true))
|
||||||
|
.disappear(.default(scale: true))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var sizeSliderVisible = false
|
var sizeSliderVisible = false
|
||||||
var isEditingText = false
|
var isEditingText = false
|
||||||
var sizeValue: CGFloat?
|
var sizeValue: CGFloat?
|
||||||
@ -1034,11 +1157,20 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
isEditingText = entityView.isEditing
|
isEditingText = entityView.isEditing
|
||||||
sizeValue = textEntity.fontSize
|
sizeValue = textEntity.fontSize
|
||||||
} else {
|
} else {
|
||||||
if state.selectedEntity == nil {
|
if state.selectedEntity == nil || !(state.selectedEntity is DrawingStickerEntity) {
|
||||||
sizeSliderVisible = true
|
sizeSliderVisible = true
|
||||||
sizeValue = state.drawingState.currentToolState.size
|
if state.selectedEntity == nil {
|
||||||
|
sizeValue = state.drawingState.currentToolState.size
|
||||||
|
} else if let entity = state.selectedEntity {
|
||||||
|
if let entity = entity as? DrawingSimpleShapeEntity {
|
||||||
|
sizeSliderVisible = entity.drawType == .stroke
|
||||||
|
} else if let entity = entity as? DrawingBubbleEntity {
|
||||||
|
sizeSliderVisible = entity.drawType == .stroke
|
||||||
|
}
|
||||||
|
sizeValue = entity.lineWidth
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if state.drawingViewState.canZoomOut {
|
if state.drawingViewState.canZoomOut && !hasTopButtons {
|
||||||
let zoomOutButton = zoomOutButton.update(
|
let zoomOutButton = zoomOutButton.update(
|
||||||
component: Button(
|
component: Button(
|
||||||
content: AnyComponent(
|
content: AnyComponent(
|
||||||
@ -1064,9 +1196,10 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
if let sizeValue {
|
if let sizeValue {
|
||||||
state.lastSize = sizeValue
|
state.lastSize = sizeValue
|
||||||
}
|
}
|
||||||
|
if state.drawingViewState.isDrawing {
|
||||||
|
sizeSliderVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
let topInset = environment.safeInsets.top + 31.0
|
|
||||||
let bottomInset: CGFloat = environment.inputHeight > 0.0 ? environment.inputHeight : 145.0
|
|
||||||
let textSize = textSize.update(
|
let textSize = textSize.update(
|
||||||
component: TextSizeSliderComponent(
|
component: TextSizeSliderComponent(
|
||||||
value: sizeValue ?? state.lastSize,
|
value: sizeValue ?? state.lastSize,
|
||||||
@ -1103,7 +1236,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
context.add(undoButton
|
context.add(undoButton
|
||||||
.position(CGPoint(x: environment.safeInsets.left + undoButton.size.width / 2.0 + 2.0, y: environment.safeInsets.top + 31.0))
|
.position(CGPoint(x: environment.safeInsets.left + undoButton.size.width / 2.0 + 2.0, y: topInset))
|
||||||
.scale(isEditingText ? 0.01 : 1.0)
|
.scale(isEditingText ? 0.01 : 1.0)
|
||||||
.opacity(isEditingText ? 0.0 : 1.0)
|
.opacity(isEditingText ? 0.0 : 1.0)
|
||||||
)
|
)
|
||||||
@ -1122,7 +1255,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
context.add(redoButton
|
context.add(redoButton
|
||||||
.position(CGPoint(x: environment.safeInsets.left + undoButton.size.width + 2.0 + redoButton.size.width / 2.0, y: environment.safeInsets.top + 31.0))
|
.position(CGPoint(x: environment.safeInsets.left + undoButton.size.width + 2.0 + redoButton.size.width / 2.0, y: topInset))
|
||||||
.appear(.default(scale: true, alpha: true))
|
.appear(.default(scale: true, alpha: true))
|
||||||
.disappear(.default(scale: true, alpha: true))
|
.disappear(.default(scale: true, alpha: true))
|
||||||
)
|
)
|
||||||
@ -1142,7 +1275,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
context.add(clearAllButton
|
context.add(clearAllButton
|
||||||
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - clearAllButton.size.width / 2.0 - 13.0, y: environment.safeInsets.top + 31.0))
|
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - clearAllButton.size.width / 2.0 - 13.0, y: topInset))
|
||||||
.scale(isEditingText ? 0.01 : 1.0)
|
.scale(isEditingText ? 0.01 : 1.0)
|
||||||
.opacity(isEditingText ? 0.0 : 1.0)
|
.opacity(isEditingText ? 0.0 : 1.0)
|
||||||
)
|
)
|
||||||
@ -1162,7 +1295,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
context.add(textCancelButton
|
context.add(textCancelButton
|
||||||
.position(CGPoint(x: environment.safeInsets.left + textCancelButton.size.width / 2.0 + 13.0, y: environment.safeInsets.top + 31.0))
|
.position(CGPoint(x: environment.safeInsets.left + textCancelButton.size.width / 2.0 + 13.0, y: topInset))
|
||||||
.scale(isEditingText ? 1.0 : 0.01)
|
.scale(isEditingText ? 1.0 : 0.01)
|
||||||
.opacity(isEditingText ? 1.0 : 0.0)
|
.opacity(isEditingText ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
@ -1182,245 +1315,127 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
context.add(textDoneButton
|
context.add(textDoneButton
|
||||||
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - textDoneButton.size.width / 2.0 - 13.0, y: environment.safeInsets.top + 31.0))
|
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - textDoneButton.size.width / 2.0 - 13.0, y: topInset))
|
||||||
.scale(isEditingText ? 1.0 : 0.01)
|
.scale(isEditingText ? 1.0 : 0.01)
|
||||||
.opacity(isEditingText ? 1.0 : 0.0)
|
.opacity(isEditingText ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
var isEditingShapeSize = false
|
|
||||||
if let entity = state.selectedEntity {
|
|
||||||
if entity is DrawingSimpleShapeEntity || entity is DrawingVectorEntity || entity is DrawingBubbleEntity {
|
|
||||||
isEditingShapeSize = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var color: DrawingColor?
|
var color: DrawingColor?
|
||||||
if let entity = state.selectedEntity, presetColors.contains(entity.color) {
|
if let entity = state.selectedEntity, presetColors.contains(entity.color) {
|
||||||
color = nil
|
color = nil
|
||||||
} else if presetColors.contains(state.currentColor) {
|
} else if presetColors.contains(state.currentColor) {
|
||||||
color = nil
|
color = nil
|
||||||
|
} else if state.selectedEntity is DrawingStickerEntity {
|
||||||
|
color = nil
|
||||||
} else {
|
} else {
|
||||||
color = state.currentColor
|
color = state.currentColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let colorButton = colorButton.update(
|
||||||
|
component: ColorSwatchComponent(
|
||||||
|
type: .main,
|
||||||
|
color: color,
|
||||||
|
tag: colorButtonTag,
|
||||||
|
action: { [weak state] in
|
||||||
|
if let state = state {
|
||||||
|
presentColorPicker(state.currentColor)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
holdAction: {
|
||||||
|
if let controller = controller() as? DrawingScreen, let buttonView = controller.node.componentHost.findTaggedView(tag: colorButtonTag) {
|
||||||
|
presentFastColorPicker(buttonView)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pan: { point in
|
||||||
|
updateFastColorPickerPan(point)
|
||||||
|
},
|
||||||
|
release: {
|
||||||
|
dismissFastColorPicker()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
context.add(colorButton
|
||||||
|
.position(CGPoint(x: environment.safeInsets.left + colorButton.size.width / 2.0 + 2.0, y: context.availableSize.height - environment.safeInsets.bottom - colorButton.size.height / 2.0 - 89.0))
|
||||||
|
.appear(.default(scale: true))
|
||||||
|
.disappear(.default(scale: true))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if let _ = state.selectedEntity as? DrawingStickerEntity {
|
let modeRightInset: CGFloat = 57.0
|
||||||
let stickerFlipButton = stickerFlipButton.update(
|
let addButton = addButton.update(
|
||||||
component: Button(
|
component: Button(
|
||||||
content: AnyComponent(
|
content: AnyComponent(ZStack([
|
||||||
Image(image: state.image(.flip))
|
AnyComponentWithIdentity(
|
||||||
|
id: "background",
|
||||||
|
component: AnyComponent(
|
||||||
|
BlurredRectangle(
|
||||||
|
color: UIColor(rgb: 0x888888, alpha: 0.3),
|
||||||
|
radius: 12.0
|
||||||
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
action: { [weak state] in
|
AnyComponentWithIdentity(
|
||||||
guard let state = state else {
|
id: "icon",
|
||||||
return
|
component: AnyComponent(
|
||||||
}
|
Image(image: state.image(.add))
|
||||||
if let entity = state.selectedEntity as? DrawingStickerEntity {
|
)
|
||||||
entity.mirrored = !entity.mirrored
|
),
|
||||||
entity.currentEntityView?.update(animated: true)
|
])),
|
||||||
}
|
action: { [weak state] in
|
||||||
state.updated(transition: .easeInOut(duration: 0.2))
|
guard let controller = controller() as? DrawingScreen, let state = state else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
).minSize(CGSize(width: 44.0, height: 44.0)).tagged(flipButtonTag),
|
switch state.currentMode {
|
||||||
availableSize: CGSize(width: 33.0, height: 33.0),
|
case .drawing:
|
||||||
transition: .immediate
|
if let buttonView = controller.node.componentHost.findTaggedView(tag: addButtonTag) as? Button.View {
|
||||||
)
|
state.presentShapePicker(buttonView)
|
||||||
context.add(stickerFlipButton
|
|
||||||
.position(CGPoint(x: environment.safeInsets.left + stickerFlipButton.size.width / 2.0 + 3.0, y: context.availableSize.height - environment.safeInsets.bottom - stickerFlipButton.size.height / 2.0 - 89.0))
|
|
||||||
.appear(.default(scale: true))
|
|
||||||
.disappear(.default(scale: true))
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let colorButton = colorButton.update(
|
|
||||||
component: ColorSwatchComponent(
|
|
||||||
type: .main,
|
|
||||||
color: color,
|
|
||||||
tag: colorButtonTag,
|
|
||||||
action: { [weak state] in
|
|
||||||
if let state = state {
|
|
||||||
presentColorPicker(state.currentColor)
|
|
||||||
}
|
}
|
||||||
},
|
case .sticker:
|
||||||
holdAction: {
|
state.presentStickerPicker()
|
||||||
if let controller = controller() as? DrawingScreen, let buttonView = controller.node.componentHost.findTaggedView(tag: colorButtonTag) {
|
case .text:
|
||||||
presentFastColorPicker(buttonView)
|
state.addTextEntity()
|
||||||
}
|
|
||||||
},
|
|
||||||
pan: { point in
|
|
||||||
updateFastColorPickerPan(point)
|
|
||||||
},
|
|
||||||
release: {
|
|
||||||
dismissFastColorPicker()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
).minSize(CGSize(width: 44.0, height: 44.0)).tagged(addButtonTag),
|
||||||
|
availableSize: CGSize(width: 24.0, height: 24.0),
|
||||||
|
transition: .immediate
|
||||||
|
)
|
||||||
|
context.add(addButton
|
||||||
|
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - addButton.size.width / 2.0 - 2.0, y: context.availableSize.height - environment.safeInsets.bottom - addButton.size.height / 2.0 - 89.0))
|
||||||
|
.appear(.default(scale: true))
|
||||||
|
.disappear(.default(scale: true))
|
||||||
|
)
|
||||||
|
|
||||||
|
let doneButton = doneButton.update(
|
||||||
|
component: Button(
|
||||||
|
content: AnyComponent(
|
||||||
|
Image(image: state.image(.done))
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: 44.0, height: 44.0),
|
action: {
|
||||||
transition: context.transition
|
apply.invoke(Void())
|
||||||
)
|
}
|
||||||
context.add(colorButton
|
).minSize(CGSize(width: 44.0, height: 44.0)).tagged(doneButtonTag),
|
||||||
.position(CGPoint(x: environment.safeInsets.left + colorButton.size.width / 2.0 + 2.0, y: context.availableSize.height - environment.safeInsets.bottom - colorButton.size.height / 2.0 - 89.0))
|
availableSize: CGSize(width: 33.0, height: 33.0),
|
||||||
.appear(.default(scale: true))
|
transition: .immediate
|
||||||
.disappear(.default(scale: true))
|
)
|
||||||
)
|
context.add(doneButton
|
||||||
}
|
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - doneButton.size.width / 2.0 - 3.0, y: context.availableSize.height - environment.safeInsets.bottom - doneButton.size.height / 2.0 - 2.0 - UIScreenPixel))
|
||||||
|
.appear(Transition.Appear { _, view, transition in
|
||||||
var isModeControlEnabled = true
|
transition.animateScale(view: view, from: 0.1, to: 1.0)
|
||||||
var modeRightInset: CGFloat = 57.0
|
transition.animateAlpha(view: view, from: 0.0, to: 1.0)
|
||||||
if isEditingShapeSize {
|
|
||||||
var isFilled = false
|
transition.animatePosition(view: view, from: CGPoint(x: 12.0, y: 0.0), to: CGPoint(), additive: true)
|
||||||
if let entity = state.selectedEntity as? DrawingSimpleShapeEntity, case .fill = entity.drawType {
|
})
|
||||||
isFilled = true
|
.disappear(Transition.Disappear { view, transition, completion in
|
||||||
isModeControlEnabled = false
|
transition.setScale(view: view, scale: 0.1)
|
||||||
} else if let entity = state.selectedEntity as? DrawingBubbleEntity, case .fill = entity.drawType {
|
transition.setAlpha(view: view, alpha: 0.0, completion: { _ in
|
||||||
isFilled = true
|
completion()
|
||||||
isModeControlEnabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if let _ = state.selectedEntity as? DrawingBubbleEntity {
|
|
||||||
let flipButton = flipButton.update(
|
|
||||||
component: Button(
|
|
||||||
content: AnyComponent(
|
|
||||||
Image(image: state.image(.flip))
|
|
||||||
),
|
|
||||||
action: { [weak state] in
|
|
||||||
guard let state = state else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let entity = state.selectedEntity as? DrawingBubbleEntity {
|
|
||||||
var updatedTailPosition = entity.tailPosition
|
|
||||||
updatedTailPosition.x = 1.0 - updatedTailPosition.x
|
|
||||||
entity.tailPosition = updatedTailPosition
|
|
||||||
entity.currentEntityView?.update()
|
|
||||||
}
|
|
||||||
state.updated(transition: .easeInOut(duration: 0.2))
|
|
||||||
}
|
|
||||||
).minSize(CGSize(width: 44.0, height: 44.0)),
|
|
||||||
availableSize: CGSize(width: 33.0, height: 33.0),
|
|
||||||
transition: .immediate
|
|
||||||
)
|
|
||||||
context.add(flipButton
|
|
||||||
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - flipButton.size.width / 2.0 - 3.0 - flipButton.size.width, y: context.availableSize.height - environment.safeInsets.bottom - flipButton.size.height / 2.0 - 2.0 - UIScreenPixel))
|
|
||||||
.appear(.default(scale: true))
|
|
||||||
.disappear(.default(scale: true))
|
|
||||||
)
|
|
||||||
modeRightInset += 35.0
|
|
||||||
}
|
|
||||||
|
|
||||||
let fillButton = fillButton.update(
|
|
||||||
component: Button(
|
|
||||||
content: AnyComponent(
|
|
||||||
Image(image: state.image(isFilled ? .fill : .stroke))
|
|
||||||
),
|
|
||||||
action: { [weak state] in
|
|
||||||
guard let state = state else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let entity = state.selectedEntity as? DrawingSimpleShapeEntity {
|
|
||||||
if case .fill = entity.drawType {
|
|
||||||
entity.drawType = .stroke
|
|
||||||
} else {
|
|
||||||
entity.drawType = .fill
|
|
||||||
}
|
|
||||||
entity.currentEntityView?.update()
|
|
||||||
} else if let entity = state.selectedEntity as? DrawingBubbleEntity {
|
|
||||||
if case .fill = entity.drawType {
|
|
||||||
entity.drawType = .stroke
|
|
||||||
} else {
|
|
||||||
entity.drawType = .fill
|
|
||||||
}
|
|
||||||
entity.currentEntityView?.update()
|
|
||||||
} else if let entity = state.selectedEntity as? DrawingVectorEntity {
|
|
||||||
if case .oneSidedArrow = entity.type {
|
|
||||||
entity.type = .twoSidedArrow
|
|
||||||
} else if case .twoSidedArrow = entity.type {
|
|
||||||
entity.type = .line
|
|
||||||
} else {
|
|
||||||
entity.type = .oneSidedArrow
|
|
||||||
}
|
|
||||||
entity.currentEntityView?.update()
|
|
||||||
}
|
|
||||||
state.updated(transition: .easeInOut(duration: 0.2))
|
|
||||||
}
|
|
||||||
).minSize(CGSize(width: 44.0, height: 44.0)).tagged(fillButtonTag),
|
|
||||||
availableSize: CGSize(width: 33.0, height: 33.0),
|
|
||||||
transition: .immediate
|
|
||||||
)
|
|
||||||
context.add(fillButton
|
|
||||||
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - fillButton.size.width / 2.0 - 3.0, y: context.availableSize.height - environment.safeInsets.bottom - fillButton.size.height / 2.0 - 2.0 - UIScreenPixel))
|
|
||||||
.appear(.default(scale: true))
|
|
||||||
.disappear(.default(scale: true))
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let addButton = addButton.update(
|
|
||||||
component: Button(
|
|
||||||
content: AnyComponent(ZStack([
|
|
||||||
AnyComponentWithIdentity(
|
|
||||||
id: "background",
|
|
||||||
component: AnyComponent(
|
|
||||||
BlurredRectangle(
|
|
||||||
color: UIColor(rgb: 0x888888, alpha: 0.3),
|
|
||||||
radius: 12.0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
AnyComponentWithIdentity(
|
|
||||||
id: "icon",
|
|
||||||
component: AnyComponent(
|
|
||||||
Image(image: state.image(.add))
|
|
||||||
)
|
|
||||||
),
|
|
||||||
])),
|
|
||||||
action: { [weak state] in
|
|
||||||
guard let controller = controller() as? DrawingScreen, let state = state else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch state.currentMode {
|
|
||||||
case .drawing:
|
|
||||||
if let buttonView = controller.node.componentHost.findTaggedView(tag: addButtonTag) as? Button.View {
|
|
||||||
state.presentShapePicker(buttonView)
|
|
||||||
}
|
|
||||||
case .sticker:
|
|
||||||
state.presentStickerPicker()
|
|
||||||
case .text:
|
|
||||||
state.addTextEntity()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
).minSize(CGSize(width: 44.0, height: 44.0)).tagged(addButtonTag),
|
|
||||||
availableSize: CGSize(width: 24.0, height: 24.0),
|
|
||||||
transition: .immediate
|
|
||||||
)
|
|
||||||
context.add(addButton
|
|
||||||
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - addButton.size.width / 2.0 - 2.0, y: context.availableSize.height - environment.safeInsets.bottom - addButton.size.height / 2.0 - 89.0))
|
|
||||||
.appear(.default(scale: true))
|
|
||||||
.disappear(.default(scale: true))
|
|
||||||
)
|
|
||||||
|
|
||||||
let doneButton = doneButton.update(
|
|
||||||
component: Button(
|
|
||||||
content: AnyComponent(
|
|
||||||
Image(image: state.image(.done))
|
|
||||||
),
|
|
||||||
action: {
|
|
||||||
apply.invoke(Void())
|
|
||||||
}
|
|
||||||
).minSize(CGSize(width: 44.0, height: 44.0)).tagged(doneButtonTag),
|
|
||||||
availableSize: CGSize(width: 33.0, height: 33.0),
|
|
||||||
transition: .immediate
|
|
||||||
)
|
|
||||||
context.add(doneButton
|
|
||||||
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - doneButton.size.width / 2.0 - 3.0, y: context.availableSize.height - environment.safeInsets.bottom - doneButton.size.height / 2.0 - 2.0 - UIScreenPixel))
|
|
||||||
.appear(Transition.Appear { _, view, transition in
|
|
||||||
transition.animateScale(view: view, from: 0.1, to: 1.0)
|
|
||||||
transition.animateAlpha(view: view, from: 0.0, to: 1.0)
|
|
||||||
|
|
||||||
transition.animatePosition(view: view, from: CGPoint(x: 12.0, y: 0.0), to: CGPoint(), additive: true)
|
|
||||||
})
|
})
|
||||||
.disappear(Transition.Disappear { view, transition, completion in
|
transition.animatePosition(view: view, from: CGPoint(), to: CGPoint(x: 12.0, y: 0.0), additive: true)
|
||||||
transition.setScale(view: view, scale: 0.1)
|
})
|
||||||
transition.setAlpha(view: view, alpha: 0.0, completion: { _ in
|
)
|
||||||
completion()
|
|
||||||
})
|
|
||||||
transition.animatePosition(view: view, from: CGPoint(), to: CGPoint(x: 12.0, y: 0.0), additive: true)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let selectedIndex: Int
|
let selectedIndex: Int
|
||||||
switch state.currentMode {
|
switch state.currentMode {
|
||||||
@ -1443,8 +1458,8 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
component: ModeAndSizeComponent(
|
component: ModeAndSizeComponent(
|
||||||
values: ["Draw", "Sticker", "Text"],
|
values: ["Draw", "Sticker", "Text"],
|
||||||
sizeValue: selectedSize,
|
sizeValue: selectedSize,
|
||||||
isEditing: isEditingShapeSize,
|
isEditing: false,
|
||||||
isEnabled: isModeControlEnabled,
|
isEnabled: true,
|
||||||
rightInset: modeRightInset - 57.0,
|
rightInset: modeRightInset - 57.0,
|
||||||
tag: modeTag,
|
tag: modeTag,
|
||||||
selectedIndex: selectedIndex,
|
selectedIndex: selectedIndex,
|
||||||
@ -1478,7 +1493,6 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
context.add(modeAndSize
|
context.add(modeAndSize
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0 - (modeRightInset - 57.0) / 2.0, y: context.availableSize.height - environment.safeInsets.bottom - modeAndSize.size.height / 2.0 - 9.0))
|
.position(CGPoint(x: context.availableSize.width / 2.0 - (modeRightInset - 57.0) / 2.0, y: context.availableSize.height - environment.safeInsets.bottom - modeAndSize.size.height / 2.0 - 9.0))
|
||||||
.opacity(isModeControlEnabled ? 1.0 : 0.4)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var animatingOut = false
|
var animatingOut = false
|
||||||
@ -1494,7 +1508,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
animation: LottieAnimationComponent.AnimationItem(
|
animation: LottieAnimationComponent.AnimationItem(
|
||||||
name: "media_backToCancel",
|
name: "media_backToCancel",
|
||||||
mode: .animating(loop: false),
|
mode: .animating(loop: false),
|
||||||
range: isEditingShapeSize || animatingOut || component.isAvatar ? (0.5, 1.0) : (0.0, 0.5)
|
range: animatingOut || component.isAvatar ? (0.5, 1.0) : (0.0, 0.5)
|
||||||
),
|
),
|
||||||
colors: ["__allcolors__": .white],
|
colors: ["__allcolors__": .white],
|
||||||
size: CGSize(width: 33.0, height: 33.0)
|
size: CGSize(width: 33.0, height: 33.0)
|
||||||
@ -1730,8 +1744,17 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
|||||||
entitiesView.add(entity)
|
entitiesView.add(entity)
|
||||||
entitiesView.selectEntity(entity)
|
entitiesView.selectEntity(entity)
|
||||||
|
|
||||||
if let entityView = entitiesView.getView(for: entity.uuid) as? DrawingTextEntityView {
|
if let entityView = entitiesView.getView(for: entity.uuid) {
|
||||||
entityView.beginEditing(accessoryView: strongSelf.textEditAccessoryView)
|
if let textEntityView = entityView as? DrawingTextEntityView {
|
||||||
|
textEntityView.beginEditing(accessoryView: strongSelf.textEditAccessoryView)
|
||||||
|
} else {
|
||||||
|
entityView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
entityView.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
||||||
|
|
||||||
|
if let selectionView = entityView.selectionView {
|
||||||
|
selectionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1943,7 +1966,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
|||||||
if let view = self.componentHost.findTaggedView(tag: tag) {
|
if let view = self.componentHost.findTaggedView(tag: tag) {
|
||||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, delay: delay)
|
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, delay: delay)
|
||||||
view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3, delay: delay)
|
view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3, delay: delay)
|
||||||
delay += 0.025
|
delay += 0.02
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let view = self.componentHost.findTaggedView(tag: sizeSliderTag) {
|
if let view = self.componentHost.findTaggedView(tag: sizeSliderTag) {
|
||||||
@ -1983,15 +2006,16 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
|||||||
buttonView.alpha = 0.0
|
buttonView.alpha = 0.0
|
||||||
buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
||||||
buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3)
|
buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3)
|
||||||
}
|
}
|
||||||
|
if let buttonView = self.componentHost.findTaggedView(tag: fillButtonTag) {
|
||||||
|
buttonView.alpha = 0.0
|
||||||
|
buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
||||||
|
buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3)
|
||||||
|
}
|
||||||
if let view = self.componentHost.findTaggedView(tag: sizeSliderTag) {
|
if let view = self.componentHost.findTaggedView(tag: sizeSliderTag) {
|
||||||
view.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -33.0, y: 0.0), duration: 0.3, removeOnCompletion: false, additive: true)
|
view.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -33.0, y: 0.0), duration: 0.3, removeOnCompletion: false, additive: true)
|
||||||
}
|
}
|
||||||
if let view = self.componentHost.findTaggedView(tag: textSettingsTag) {
|
|
||||||
view.alpha = 0.0
|
|
||||||
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
|
||||||
}
|
|
||||||
|
|
||||||
let colorTags = [color1Tag, color2Tag, color3Tag, color4Tag, color5Tag, color6Tag, color7Tag, color8Tag]
|
let colorTags = [color1Tag, color2Tag, color3Tag, color4Tag, color5Tag, color6Tag, color7Tag, color8Tag]
|
||||||
for tag in colorTags {
|
for tag in colorTags {
|
||||||
if let view = self.componentHost.findTaggedView(tag: tag) {
|
if let view = self.componentHost.findTaggedView(tag: tag) {
|
||||||
@ -2005,7 +2029,12 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
|||||||
view.animateOut(completion: {
|
view.animateOut(completion: {
|
||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
|
} else if let view = self.componentHost.findTaggedView(tag: textSettingsTag) as? TextFontComponent.View {
|
||||||
|
view.animateOut(completion: {
|
||||||
|
completion()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if let view = self.componentHost.findTaggedView(tag: modeTag) as? ModeAndSizeComponent.View {
|
if let view = self.componentHost.findTaggedView(tag: modeTag) as? ModeAndSizeComponent.View {
|
||||||
view.animateOut()
|
view.animateOut()
|
||||||
}
|
}
|
||||||
@ -2033,7 +2062,12 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
|||||||
let environment = ViewControllerComponentContainer.Environment(
|
let environment = ViewControllerComponentContainer.Environment(
|
||||||
statusBarHeight: layout.statusBarHeight ?? 0.0,
|
statusBarHeight: layout.statusBarHeight ?? 0.0,
|
||||||
navigationHeight: 0.0,
|
navigationHeight: 0.0,
|
||||||
safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right),
|
safeInsets: UIEdgeInsets(
|
||||||
|
top: layout.intrinsicInsets.top + layout.safeInsets.top,
|
||||||
|
left: layout.safeInsets.left,
|
||||||
|
bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom,
|
||||||
|
right: layout.safeInsets.right
|
||||||
|
),
|
||||||
inputHeight: layout.inputHeight ?? 0.0,
|
inputHeight: layout.inputHeight ?? 0.0,
|
||||||
metrics: layout.metrics,
|
metrics: layout.metrics,
|
||||||
deviceMetrics: layout.deviceMetrics,
|
deviceMetrics: layout.deviceMetrics,
|
||||||
@ -2122,6 +2156,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
|||||||
alignment: DrawingTextAlignment(alignment: textEntity.alignment),
|
alignment: DrawingTextAlignment(alignment: textEntity.alignment),
|
||||||
font: DrawingTextFont(font: textEntity.font),
|
font: DrawingTextFont(font: textEntity.font),
|
||||||
isEmojiKeyboard: entityView.textView.inputView != nil,
|
isEmojiKeyboard: entityView.textView.inputView != nil,
|
||||||
|
tag: nil,
|
||||||
presentColorPicker: { [weak self] in
|
presentColorPicker: { [weak self] in
|
||||||
guard let strongSelf = self, let entityView = strongSelf.entitiesView.selectedEntityView as? DrawingTextEntityView, let textEntity = entityView.entity as? DrawingTextEntity else {
|
guard let strongSelf = self, let entityView = strongSelf.entitiesView.selectedEntityView as? DrawingTextEntityView, let textEntity = entityView.entity as? DrawingTextEntity else {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -198,6 +198,9 @@ public final class DrawingTextEntity: DrawingEntity, Codable {
|
|||||||
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
|
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
|
||||||
self.renderImage = UIImage(data: renderImageData)
|
self.renderImage = UIImage(data: renderImageData)
|
||||||
}
|
}
|
||||||
|
if let renderSubEntities = try? container.decodeIfPresent([CodableDrawingEntity].self, forKey: .renderSubEntities) {
|
||||||
|
self.renderSubEntities = renderSubEntities.compactMap { $0.entity as? DrawingStickerEntity }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -226,6 +229,10 @@ public final class DrawingTextEntity: DrawingEntity, Codable {
|
|||||||
if let renderImage, let data = renderImage.pngData() {
|
if let renderImage, let data = renderImage.pngData() {
|
||||||
try container.encode(data, forKey: .renderImage)
|
try container.encode(data, forKey: .renderImage)
|
||||||
}
|
}
|
||||||
|
if let renderSubEntities = self.renderSubEntities {
|
||||||
|
let codableEntities: [CodableDrawingEntity] = renderSubEntities.map { .sticker($0) }
|
||||||
|
try container.encode(codableEntities, forKey: .renderSubEntities)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func duplicate() -> DrawingEntity {
|
public func duplicate() -> DrawingEntity {
|
||||||
@ -247,6 +254,7 @@ public final class DrawingTextEntity: DrawingEntity, Codable {
|
|||||||
|
|
||||||
public func prepareForRender() {
|
public func prepareForRender() {
|
||||||
self.renderImage = (self.currentEntityView as? DrawingTextEntityView)?.getRenderImage()
|
self.renderImage = (self.currentEntityView as? DrawingTextEntityView)?.getRenderImage()
|
||||||
|
self.renderSubEntities = (self.currentEntityView as? DrawingTextEntityView)?.getRenderSubEntities()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,8 +291,6 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
|
|||||||
self.textView.delegate = self
|
self.textView.delegate = self
|
||||||
self.addSubview(self.textView)
|
self.addSubview(self.textView)
|
||||||
|
|
||||||
self.update(animated: false)
|
|
||||||
|
|
||||||
self.emojiViewProvider = { [weak self] emoji in
|
self.emojiViewProvider = { [weak self] emoji in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return UIView()
|
return UIView()
|
||||||
@ -293,6 +299,8 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
|
|||||||
let pointSize: CGFloat = 128.0
|
let pointSize: CGFloat = 128.0
|
||||||
return EmojiTextAttachmentView(context: context, userLocation: .other, emoji: emoji, file: emoji.file, cache: strongSelf.context.animationCache, renderer: strongSelf.context.animationRenderer, placeholderColor: UIColor.white.withAlphaComponent(0.12), pointSize: CGSize(width: pointSize, height: pointSize))
|
return EmojiTextAttachmentView(context: context, userLocation: .other, emoji: emoji, file: emoji.file, cache: strongSelf.context.animationCache, renderer: strongSelf.context.animationRenderer, placeholderColor: UIColor.white.withAlphaComponent(0.12), pointSize: CGSize(width: pointSize, height: pointSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.update(animated: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@ -312,11 +320,13 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
|
|||||||
self.endEditing()
|
self.endEditing()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var emojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = []
|
||||||
func updateEntities() {
|
func updateEntities() {
|
||||||
self.textView.drawingLayoutManager.ensureLayout(for: self.textView.textContainer)
|
self.textView.drawingLayoutManager.ensureLayout(for: self.textView.textContainer)
|
||||||
|
|
||||||
var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = []
|
var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = []
|
||||||
|
|
||||||
|
var shouldRepeat = false
|
||||||
if let attributedText = self.textView.attributedText {
|
if let attributedText = self.textView.attributedText {
|
||||||
let beginning = self.textView.beginningOfDocument
|
let beginning = self.textView.beginningOfDocument
|
||||||
attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, range, _ in
|
attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, range, _ in
|
||||||
@ -324,6 +334,9 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
|
|||||||
if let start = self.textView.position(from: beginning, offset: range.location), let end = self.textView.position(from: start, offset: range.length), let textRange = self.textView.textRange(from: start, to: end) {
|
if let start = self.textView.position(from: beginning, offset: range.location), let end = self.textView.position(from: start, offset: range.length), let textRange = self.textView.textRange(from: start, to: end) {
|
||||||
let rect = self.textView.firstRect(for: textRange)
|
let rect = self.textView.firstRect(for: textRange)
|
||||||
customEmojiRects.append((rect, value))
|
customEmojiRects.append((rect, value))
|
||||||
|
if rect.origin.x.isInfinite {
|
||||||
|
shouldRepeat = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -342,7 +355,8 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
|
|||||||
textColor = color.lightness > 0.99 ? UIColor.black : UIColor.white
|
textColor = color.lightness > 0.99 ? UIColor.black : UIColor.white
|
||||||
}
|
}
|
||||||
|
|
||||||
if !customEmojiRects.isEmpty {
|
self.emojiRects = customEmojiRects
|
||||||
|
if !customEmojiRects.isEmpty && !shouldRepeat {
|
||||||
let customEmojiContainerView: CustomEmojiContainerView
|
let customEmojiContainerView: CustomEmojiContainerView
|
||||||
if let current = self.customEmojiContainerView {
|
if let current = self.customEmojiContainerView {
|
||||||
customEmojiContainerView = current
|
customEmojiContainerView = current
|
||||||
@ -354,15 +368,22 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
|
|||||||
return emojiViewProvider(emoji)
|
return emojiViewProvider(emoji)
|
||||||
})
|
})
|
||||||
customEmojiContainerView.isUserInteractionEnabled = false
|
customEmojiContainerView.isUserInteractionEnabled = false
|
||||||
self.textView.addSubview(customEmojiContainerView)
|
customEmojiContainerView.center = customEmojiContainerView.center.offsetBy(dx: 0.0, dy: 10.0)
|
||||||
|
self.addSubview(customEmojiContainerView)
|
||||||
self.customEmojiContainerView = customEmojiContainerView
|
self.customEmojiContainerView = customEmojiContainerView
|
||||||
}
|
}
|
||||||
|
|
||||||
customEmojiContainerView.update(fontSize: self.displayFontSize, textColor: textColor, emojiRects: customEmojiRects)
|
customEmojiContainerView.update(fontSize: self.displayFontSize * 0.8, textColor: textColor, emojiRects: customEmojiRects)
|
||||||
} else if let customEmojiContainerView = self.customEmojiContainerView {
|
} else if let customEmojiContainerView = self.customEmojiContainerView {
|
||||||
customEmojiContainerView.removeFromSuperview()
|
customEmojiContainerView.removeFromSuperview()
|
||||||
self.customEmojiContainerView = nil
|
self.customEmojiContainerView = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if shouldRepeat {
|
||||||
|
Queue.mainQueue().after(0.01) {
|
||||||
|
self.updateEntities()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func beginEditing(accessoryView: UIView?) {
|
func beginEditing(accessoryView: UIView?) {
|
||||||
@ -526,9 +547,6 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
|
|||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|
||||||
var rect = self.bounds
|
var rect = self.bounds
|
||||||
// CGFloat correction = _textView.font.pointSize * _font.sizeCorrection;
|
|
||||||
// rect.origin.y += correction;
|
|
||||||
// rect.size.height -= correction;
|
|
||||||
rect = rect.offsetBy(dx: 0.0, dy: 10.0) // CGRectOffset(rect, 0.0f, 10.0f);
|
rect = rect.offsetBy(dx: 0.0, dy: 10.0) // CGRectOffset(rect, 0.0f, 10.0f);
|
||||||
self.textView.frame = rect
|
self.textView.frame = rect
|
||||||
}
|
}
|
||||||
@ -636,7 +654,6 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
|
|||||||
self.updateEntities()
|
self.updateEntities()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
super.update(animated: animated)
|
super.update(animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -671,11 +688,39 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
|
|||||||
func getRenderImage() -> UIImage? {
|
func getRenderImage() -> UIImage? {
|
||||||
let rect = self.bounds
|
let rect = self.bounds
|
||||||
UIGraphicsBeginImageContextWithOptions(rect.size, false, 1.0)
|
UIGraphicsBeginImageContextWithOptions(rect.size, false, 1.0)
|
||||||
self.drawHierarchy(in: rect, afterScreenUpdates: false)
|
self.textView.drawHierarchy(in: rect.offsetBy(dx: 0.0, dy: 10.0), afterScreenUpdates: true)
|
||||||
let image = UIGraphicsGetImageFromCurrentImageContext()
|
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
UIGraphicsEndImageContext()
|
UIGraphicsEndImageContext()
|
||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getRenderSubEntities() -> [DrawingStickerEntity] {
|
||||||
|
let textSize = self.textView.bounds.size
|
||||||
|
let textPosition = self.textEntity.position
|
||||||
|
let scale = self.textEntity.scale
|
||||||
|
let rotation = self.textEntity.rotation
|
||||||
|
|
||||||
|
let itemSize: CGFloat = floor(24.0 * self.displayFontSize * 0.8 / 17.0)
|
||||||
|
|
||||||
|
var entities: [DrawingStickerEntity] = []
|
||||||
|
for (emojiRect, emojiAttribute) in self.emojiRects {
|
||||||
|
guard let file = emojiAttribute.file else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let emojiTextPosition = emojiRect.offsetBy(dx: 0.0, dy: 10.0).center.offsetBy(dx: -textSize.width / 2.0, dy: -textSize.height / 2.0)
|
||||||
|
|
||||||
|
let entity = DrawingStickerEntity(file: file)
|
||||||
|
entity.referenceDrawingSize = CGSize(width: itemSize * 2.5, height: itemSize * 2.5)
|
||||||
|
entity.scale = scale
|
||||||
|
entity.position = textPosition.offsetBy(
|
||||||
|
dx: (emojiTextPosition.x * cos(rotation) + emojiTextPosition.y * sin(rotation)) * scale,
|
||||||
|
dy: (emojiTextPosition.y * cos(rotation) + emojiTextPosition.x * sin(rotation)) * scale
|
||||||
|
)
|
||||||
|
entity.rotation = rotation
|
||||||
|
entities.append(entity)
|
||||||
|
}
|
||||||
|
return entities
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class DrawingTextEntititySelectionView: DrawingEntitySelectionView, UIGestureRecognizerDelegate {
|
final class DrawingTextEntititySelectionView: DrawingEntitySelectionView, UIGestureRecognizerDelegate {
|
||||||
|
|||||||
@ -267,27 +267,6 @@ final class NeonTool: DrawingElement {
|
|||||||
|
|
||||||
self.path = bezierPath
|
self.path = bezierPath
|
||||||
|
|
||||||
// if self.arrow && polyline.isComplete, polyline.points.count > 2 {
|
|
||||||
// let lastPoint = lastPosition
|
|
||||||
// var secondPoint = polyline.points[polyline.points.count - 2]
|
|
||||||
// if secondPoint.location.distance(to: lastPoint) < self.renderArrowLineWidth {
|
|
||||||
// secondPoint = polyline.points[polyline.points.count - 3]
|
|
||||||
// }
|
|
||||||
// let angle = lastPoint.angle(to: secondPoint.location)
|
|
||||||
// let point1 = lastPoint.pointAt(distance: self.renderArrowLength, angle: angle - CGFloat.pi * 0.15)
|
|
||||||
// let point2 = lastPoint.pointAt(distance: self.renderArrowLength, angle: angle + CGFloat.pi * 0.15)
|
|
||||||
//
|
|
||||||
// let arrowPath = UIBezierPath()
|
|
||||||
// arrowPath.move(to: point2)
|
|
||||||
// arrowPath.addLine(to: lastPoint)
|
|
||||||
// arrowPath.addLine(to: point1)
|
|
||||||
// let arrowThickPath = arrowPath.cgPath.copy(strokingWithWidth: self.renderArrowLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0)
|
|
||||||
//
|
|
||||||
// combinedPath.usesEvenOddFillRule = false
|
|
||||||
// combinedPath.append(UIBezierPath(cgPath: arrowThickPath))
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
let cgPath = bezierPath.path.cgPath.copy(strokingWithWidth: self.renderLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0)
|
let cgPath = bezierPath.path.cgPath.copy(strokingWithWidth: self.renderLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0)
|
||||||
self.renderPath = cgPath
|
self.renderPath = cgPath
|
||||||
|
|
||||||
|
|||||||
@ -49,6 +49,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
|||||||
let canRedo: Bool
|
let canRedo: Bool
|
||||||
let canClear: Bool
|
let canClear: Bool
|
||||||
let canZoomOut: Bool
|
let canZoomOut: Bool
|
||||||
|
let isDrawing: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Action {
|
enum Action {
|
||||||
@ -107,6 +108,8 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
|||||||
private var previousStrokePoint: CGPoint?
|
private var previousStrokePoint: CGPoint?
|
||||||
private var strokeRecognitionTimer: SwiftSignalKit.Timer?
|
private var strokeRecognitionTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
|
private var isDrawing = false
|
||||||
|
|
||||||
private func loadTemplates() {
|
private func loadTemplates() {
|
||||||
func load(_ name: String) {
|
func load(_ name: String) {
|
||||||
if let url = getAppBundle().url(forResource: name, withExtension: "json"),
|
if let url = getAppBundle().url(forResource: name, withExtension: "json"),
|
||||||
@ -249,6 +252,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
|||||||
} else {
|
} else {
|
||||||
switch state {
|
switch state {
|
||||||
case .began:
|
case .began:
|
||||||
|
strongSelf.isDrawing = true
|
||||||
strongSelf.previousStrokePoint = nil
|
strongSelf.previousStrokePoint = nil
|
||||||
|
|
||||||
if strongSelf.uncommitedElement != nil {
|
if strongSelf.uncommitedElement != nil {
|
||||||
@ -264,11 +268,16 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let renderLayer = newElement.setupRenderLayer() {
|
if let renderLayer = newElement.setupRenderLayer() {
|
||||||
|
if let currentDrawingLayer = strongSelf.currentDrawingLayer {
|
||||||
|
strongSelf.currentDrawingLayer = nil
|
||||||
|
currentDrawingLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
strongSelf.currentDrawingView.layer.addSublayer(renderLayer)
|
strongSelf.currentDrawingView.layer.addSublayer(renderLayer)
|
||||||
strongSelf.currentDrawingLayer = renderLayer
|
strongSelf.currentDrawingLayer = renderLayer
|
||||||
}
|
}
|
||||||
newElement.updatePath(path, state: state)
|
newElement.updatePath(path, state: state)
|
||||||
strongSelf.uncommitedElement = newElement
|
strongSelf.uncommitedElement = newElement
|
||||||
|
strongSelf.updateInternalState()
|
||||||
case .changed:
|
case .changed:
|
||||||
strongSelf.uncommitedElement?.updatePath(path, state: state)
|
strongSelf.uncommitedElement?.updatePath(path, state: state)
|
||||||
|
|
||||||
@ -339,16 +348,20 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
|||||||
}
|
}
|
||||||
|
|
||||||
case .ended:
|
case .ended:
|
||||||
|
strongSelf.isDrawing = false
|
||||||
strongSelf.strokeRecognitionTimer?.invalidate()
|
strongSelf.strokeRecognitionTimer?.invalidate()
|
||||||
strongSelf.strokeRecognitionTimer = nil
|
strongSelf.strokeRecognitionTimer = nil
|
||||||
strongSelf.uncommitedElement?.updatePath(path, state: state)
|
strongSelf.uncommitedElement?.updatePath(path, state: state)
|
||||||
Queue.mainQueue().after(0.05) {
|
Queue.mainQueue().after(0.05) {
|
||||||
strongSelf.finishDrawing()
|
strongSelf.finishDrawing()
|
||||||
}
|
}
|
||||||
|
strongSelf.updateInternalState()
|
||||||
case .cancelled:
|
case .cancelled:
|
||||||
|
strongSelf.isDrawing = false
|
||||||
strongSelf.strokeRecognitionTimer?.invalidate()
|
strongSelf.strokeRecognitionTimer?.invalidate()
|
||||||
strongSelf.strokeRecognitionTimer = nil
|
strongSelf.strokeRecognitionTimer = nil
|
||||||
strongSelf.cancelDrawing()
|
strongSelf.cancelDrawing()
|
||||||
|
strongSelf.updateInternalState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -572,9 +585,20 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
|||||||
self.uncommitedElement = nil
|
self.uncommitedElement = nil
|
||||||
self.elements.removeAll()
|
self.elements.removeAll()
|
||||||
self.redoElements.removeAll()
|
self.redoElements.removeAll()
|
||||||
|
|
||||||
|
let snapshotView = UIImageView(image: self.drawingImage)
|
||||||
|
snapshotView.frame = self.bounds
|
||||||
|
self.addSubview(snapshotView)
|
||||||
|
|
||||||
self.drawingImage = nil
|
self.drawingImage = nil
|
||||||
self.commit(reset: true)
|
self.commit(reset: true)
|
||||||
|
|
||||||
|
Queue.mainQueue().justDispatch {
|
||||||
|
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||||
|
snapshotView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
self.updateInternalState()
|
self.updateInternalState()
|
||||||
|
|
||||||
self.lassoView.reset()
|
self.lassoView.reset()
|
||||||
@ -587,8 +611,18 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
|||||||
self.uncommitedElement = nil
|
self.uncommitedElement = nil
|
||||||
self.redoElements.append(lastElement)
|
self.redoElements.append(lastElement)
|
||||||
self.elements.removeLast()
|
self.elements.removeLast()
|
||||||
|
|
||||||
|
let snapshotView = UIImageView(image: self.drawingImage)
|
||||||
|
snapshotView.frame = self.bounds
|
||||||
|
self.addSubview(snapshotView)
|
||||||
self.commit(reset: true)
|
self.commit(reset: true)
|
||||||
|
|
||||||
|
Queue.mainQueue().justDispatch {
|
||||||
|
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||||
|
snapshotView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
self.updateInternalState()
|
self.updateInternalState()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -600,6 +634,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
|||||||
self.elements.append(lastElement)
|
self.elements.append(lastElement)
|
||||||
self.redoElements.removeLast()
|
self.redoElements.removeLast()
|
||||||
self.uncommitedElement = lastElement
|
self.uncommitedElement = lastElement
|
||||||
|
|
||||||
self.commit(reset: false)
|
self.commit(reset: false)
|
||||||
self.uncommitedElement = nil
|
self.uncommitedElement = nil
|
||||||
|
|
||||||
@ -611,13 +646,13 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
|||||||
func updateToolState(_ state: DrawingToolState) {
|
func updateToolState(_ state: DrawingToolState) {
|
||||||
switch state {
|
switch state {
|
||||||
case let .pen(brushState):
|
case let .pen(brushState):
|
||||||
self.drawingGesturePipeline?.mode = .location
|
self.drawingGesturePipeline?.mode = .polyline
|
||||||
self.tool = .pen
|
self.tool = .pen
|
||||||
self.toolColor = brushState.color
|
self.toolColor = brushState.color
|
||||||
self.toolBrushSize = brushState.size
|
self.toolBrushSize = brushState.size
|
||||||
self.toolHasArrow = false
|
self.toolHasArrow = false
|
||||||
case let .arrow(brushState):
|
case let .arrow(brushState):
|
||||||
self.drawingGesturePipeline?.mode = .location
|
self.drawingGesturePipeline?.mode = .polyline
|
||||||
self.tool = .pen
|
self.tool = .pen
|
||||||
self.toolColor = brushState.color
|
self.toolColor = brushState.color
|
||||||
self.toolBrushSize = brushState.size
|
self.toolBrushSize = brushState.size
|
||||||
@ -691,7 +726,8 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
|||||||
canUndo: !self.elements.isEmpty,
|
canUndo: !self.elements.isEmpty,
|
||||||
canRedo: !self.redoElements.isEmpty,
|
canRedo: !self.redoElements.isEmpty,
|
||||||
canClear: !self.elements.isEmpty,
|
canClear: !self.elements.isEmpty,
|
||||||
canZoomOut: self.zoomScale > 1.0 + .ulpOfOne
|
canZoomOut: self.zoomScale > 1.0 + .ulpOfOne,
|
||||||
|
isDrawing: self.isDrawing
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,73 +2,6 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
|
|
||||||
struct PointWeighted {
|
|
||||||
let point: CGPoint
|
|
||||||
let weight: CGFloat
|
|
||||||
|
|
||||||
static let zero = PointWeighted(point: CGPoint.zero, weight: 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LineSegment {
|
|
||||||
let start: CGPoint
|
|
||||||
let end: CGPoint
|
|
||||||
|
|
||||||
var length: CGFloat {
|
|
||||||
return start.distance(to: end)
|
|
||||||
}
|
|
||||||
|
|
||||||
func average(with line: LineSegment) -> LineSegment {
|
|
||||||
return LineSegment(start: start.average(with: line.start), end: end.average(with: line.end))
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalLine(from weightedPoint: PointWeighted) -> LineSegment {
|
|
||||||
return normalLine(withMiddle: weightedPoint.point, weight: weightedPoint.weight)
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalLine(withMiddle middle: CGPoint, weight: CGFloat) -> LineSegment {
|
|
||||||
let relativeEnd = start.diff(to: end)
|
|
||||||
|
|
||||||
guard weight != 0 && relativeEnd != CGPoint.zero else {
|
|
||||||
return LineSegment(start: middle, end: middle)
|
|
||||||
}
|
|
||||||
|
|
||||||
let moddle = weight / 2
|
|
||||||
let lengthK = moddle / length
|
|
||||||
|
|
||||||
let k = CGPoint(x: relativeEnd.x * lengthK, y: relativeEnd.y * lengthK)
|
|
||||||
|
|
||||||
var normalLineStart = CGPoint(x: k.y, y: -k.x)
|
|
||||||
var normalLineEnd = CGPoint(x: -k.y, y: k.x)
|
|
||||||
|
|
||||||
normalLineStart.x += middle.x;
|
|
||||||
normalLineStart.y += middle.y;
|
|
||||||
|
|
||||||
normalLineEnd.x += middle.x;
|
|
||||||
normalLineEnd.y += middle.y;
|
|
||||||
|
|
||||||
return LineSegment(start: normalLineStart, end: normalLineEnd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extension CGPoint {
|
|
||||||
func average(with point: CGPoint) -> CGPoint {
|
|
||||||
return CGPoint(x: (x + point.x) * 0.5, y: (y + point.y) * 0.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func diff(to point: CGPoint) -> CGPoint {
|
|
||||||
return CGPoint(x: point.x - x, y: point.y - y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func forward(to point: CGPoint, by: CGFloat) -> CGPoint {
|
|
||||||
let diff = diff(to: point)
|
|
||||||
let distance = sqrt(pow(diff.x, 2) + pow(diff.y, 2))
|
|
||||||
let k = by / distance
|
|
||||||
|
|
||||||
return CGPoint(x: point.x + diff.x * k, y: point.y + diff.y * k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class PenTool: DrawingElement {
|
final class PenTool: DrawingElement {
|
||||||
class RenderLayer: SimpleLayer, DrawingRenderLayer {
|
class RenderLayer: SimpleLayer, DrawingRenderLayer {
|
||||||
func setup(size: CGSize) {
|
func setup(size: CGSize) {
|
||||||
@ -79,15 +12,12 @@ final class PenTool: DrawingElement {
|
|||||||
self.frame = bounds
|
self.frame = bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
private var paths: [UIBezierPath] = []
|
|
||||||
private var tempPath: UIBezierPath?
|
|
||||||
|
|
||||||
private var color: UIColor?
|
private var color: UIColor?
|
||||||
fileprivate func draw(paths: [UIBezierPath], tempPath: UIBezierPath?, color: UIColor, rect: CGRect) {
|
private var line: StrokeLine?
|
||||||
self.paths = paths
|
fileprivate func draw(line: StrokeLine, color: UIColor, rect: CGRect) {
|
||||||
self.tempPath = tempPath
|
self.line = line
|
||||||
self.color = color
|
self.color = color
|
||||||
|
|
||||||
self.setNeedsDisplay(rect.insetBy(dx: -50.0, dy: -50.0))
|
self.setNeedsDisplay(rect.insetBy(dx: -50.0, dy: -50.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,21 +48,7 @@ final class PenTool: DrawingElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func draw(in ctx: CGContext) {
|
override func draw(in ctx: CGContext) {
|
||||||
guard let color = self.color else {
|
self.line?.drawInContext(ctx)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.setFillColor(color.cgColor)
|
|
||||||
|
|
||||||
for path in self.paths {
|
|
||||||
ctx.addPath(path.cgPath)
|
|
||||||
ctx.fillPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
if let tempPath = self.tempPath {
|
|
||||||
ctx.addPath(tempPath.cgPath)
|
|
||||||
ctx.fillPath()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,14 +62,12 @@ final class PenTool: DrawingElement {
|
|||||||
var path: Polyline?
|
var path: Polyline?
|
||||||
var boundingBox: CGRect?
|
var boundingBox: CGRect?
|
||||||
|
|
||||||
var didSetupArrow = false
|
private var renderLine: StrokeLine
|
||||||
let renderLineWidth: CGFloat
|
let renderLineWidth: CGFloat
|
||||||
let renderArrowLength: CGFloat
|
let renderArrowLength: CGFloat
|
||||||
let renderArrowLineWidth: CGFloat
|
let renderArrowLineWidth: CGFloat
|
||||||
|
|
||||||
var bezierPaths: [UIBezierPath] = []
|
var didSetupArrow = false
|
||||||
var tempBezierPath: UIBezierPath?
|
|
||||||
|
|
||||||
var arrowLeftPath: UIBezierPath?
|
var arrowLeftPath: UIBezierPath?
|
||||||
var arrowLeftPoint: CGPoint?
|
var arrowLeftPoint: CGPoint?
|
||||||
var arrowRightPath: UIBezierPath?
|
var arrowRightPath: UIBezierPath?
|
||||||
@ -167,37 +81,20 @@ final class PenTool: DrawingElement {
|
|||||||
return self.path?.bounds.offsetBy(dx: self.translation.x, dy: self.translation.y) ?? .zero
|
return self.path?.bounds.offsetBy(dx: self.translation.x, dy: self.translation.y) ?? .zero
|
||||||
}
|
}
|
||||||
|
|
||||||
var _points: [Polyline.Point] = []
|
|
||||||
|
|
||||||
var points: [Polyline.Point] {
|
var points: [Polyline.Point] {
|
||||||
|
guard let linePath = self.path else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
var points: [Polyline.Point] = []
|
var points: [Polyline.Point] = []
|
||||||
var lastPoint: Polyline.Point?
|
for point in linePath.points {
|
||||||
for point in self._points {
|
|
||||||
points.append(point.offsetBy(self.translation))
|
points.append(point.offsetBy(self.translation))
|
||||||
lastPoint = point
|
|
||||||
}
|
|
||||||
if let arrowLeftPoint, let lastPoint {
|
|
||||||
points.append(lastPoint.withLocation(arrowLeftPoint.offsetBy(self.translation)))
|
|
||||||
}
|
|
||||||
if let arrowRightPoint, let lastPoint {
|
|
||||||
points.append(lastPoint.withLocation(arrowRightPoint.offsetBy(self.translation)))
|
|
||||||
}
|
}
|
||||||
return points
|
return points
|
||||||
}
|
}
|
||||||
|
|
||||||
private let pointsPerLine: Int = 4
|
|
||||||
private var nextPointIndex: Int = 0
|
|
||||||
private var drawPoints = [PointWeighted](repeating: PointWeighted.zero, count: 4)
|
|
||||||
|
|
||||||
private var arrowParams: (CGPoint, CGFloat)?
|
|
||||||
|
|
||||||
func containsPoint(_ point: CGPoint) -> Bool {
|
func containsPoint(_ point: CGPoint) -> Bool {
|
||||||
for path in self.bezierPaths {
|
|
||||||
if path.contains(point.offsetBy(CGPoint(x: -self.translation.x, y: -self.translation.y))) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
|
// return self.renderPath?.contains(point.offsetBy(CGPoint(x: -self.translation.x, y: -self.translation.y))) ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasPointsInsidePath(_ path: UIBezierPath) -> Bool {
|
func hasPointsInsidePath(_ path: UIBezierPath) -> Bool {
|
||||||
@ -220,15 +117,15 @@ final class PenTool: DrawingElement {
|
|||||||
self.lineWidth = lineWidth
|
self.lineWidth = lineWidth
|
||||||
self.arrow = arrow
|
self.arrow = arrow
|
||||||
|
|
||||||
let minLineWidth = max(1.0, max(drawingSize.width, drawingSize.height) * 0.0015)
|
let minLineWidth = max(1.0, max(drawingSize.width, drawingSize.height) * 0.002)
|
||||||
let maxLineWidth = max(10.0, max(drawingSize.width, drawingSize.height) * 0.05)
|
let maxLineWidth = max(10.0, max(drawingSize.width, drawingSize.height) * 0.07)
|
||||||
let lineWidth = minLineWidth + (maxLineWidth - minLineWidth) * lineWidth
|
let lineWidth = minLineWidth + (maxLineWidth - minLineWidth) * lineWidth
|
||||||
|
|
||||||
self.renderLineWidth = lineWidth
|
self.renderLineWidth = lineWidth
|
||||||
self.renderArrowLength = lineWidth * 7.0
|
self.renderArrowLength = lineWidth * 3.0
|
||||||
self.renderArrowLineWidth = lineWidth * 2.0
|
self.renderArrowLineWidth = lineWidth * 0.8
|
||||||
|
|
||||||
self.path = Polyline(points: [])
|
self.renderLine = StrokeLine(color: color.toUIColor(), minLineWidth: minLineWidth + (lineWidth - minLineWidth) * 0.3, lineWidth: lineWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func finishArrow(_ completion: @escaping () -> Void) {
|
func finishArrow(_ completion: @escaping () -> Void) {
|
||||||
@ -248,57 +145,47 @@ final class PenTool: DrawingElement {
|
|||||||
return layer
|
return layer
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastPoint: CGPoint?
|
var previousPoint: CGPoint?
|
||||||
func updateWithLocation(_ point: CGPoint, ended: Bool = false) {
|
|
||||||
if ended {
|
|
||||||
self.lastPoint = self.drawPoints[self.nextPointIndex - 1].point
|
|
||||||
|
|
||||||
if let path = tempBezierPath {
|
|
||||||
bezierPaths.last?.append(path)
|
|
||||||
}
|
|
||||||
tempBezierPath = nil
|
|
||||||
nextPointIndex = 0
|
|
||||||
} else {
|
|
||||||
addPoint(point)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) {
|
func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) {
|
||||||
guard case let .location(point) = path else {
|
guard case let .polyline(line) = path, let point = line.points.last else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
self.path = line
|
||||||
self._points.append(point)
|
|
||||||
self.path?.points.append(point)
|
|
||||||
|
|
||||||
switch state {
|
let filterDistance: CGFloat
|
||||||
case .began:
|
if point.velocity > 1200 {
|
||||||
addPoint(point.location)
|
filterDistance = 75.0
|
||||||
case .changed:
|
} else {
|
||||||
if self._points.count > 1 {
|
filterDistance = 35.0
|
||||||
self.updateTouchPoints(point: self._points[self._points.count - 1].location, previousPoint: self._points[self._points.count - 2].location)
|
}
|
||||||
self.updateWithLocation(point.location)
|
|
||||||
}
|
if let previousPoint, point.location.distance(to: previousPoint) < filterDistance, state == .changed, self.renderLine.ready {
|
||||||
case .ended:
|
return
|
||||||
self.updateTouchPoints(point: self._points[self._points.count - 1].location, previousPoint: self._points[self._points.count - 2].location)
|
}
|
||||||
self.updateWithLocation(point.location, ended: true)
|
self.previousPoint = point.location
|
||||||
|
|
||||||
|
let rect = self.renderLine.draw(at: point)
|
||||||
|
if let currentRenderLayer = self.currentRenderLayer as? RenderLayer {
|
||||||
|
currentRenderLayer.draw(line: self.renderLine, color: self.color.toUIColor(), rect: rect)
|
||||||
|
}
|
||||||
|
|
||||||
|
if state == .ended {
|
||||||
if self.arrow {
|
if self.arrow {
|
||||||
let points = self.path?.points ?? []
|
let points = self.path?.points ?? []
|
||||||
var direction: CGFloat?
|
|
||||||
|
|
||||||
let p2 = points[points.count - 1].location
|
var direction: CGFloat?
|
||||||
for i in 1 ..< min(points.count - 2, 12) {
|
if points.count > 4 {
|
||||||
let p1 = points[points.count - 1 - i].location
|
let p2 = points[points.count - 1].location
|
||||||
if p1.distance(to: p2) > renderArrowLength * 0.5 {
|
for i in 1 ..< min(points.count - 2, 12) {
|
||||||
direction = p2.angle(to: p1)
|
let p1 = points[points.count - 1 - i].location
|
||||||
break
|
if p1.distance(to: p2) > renderArrowLength * 0.5 {
|
||||||
|
direction = p2.angle(to: p1)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let point = self.lastPoint, let direction {
|
if let point = points.last?.location, let direction {
|
||||||
self.arrowParams = (point, direction)
|
|
||||||
|
|
||||||
let arrowLeftPath = UIBezierPath()
|
let arrowLeftPath = UIBezierPath()
|
||||||
arrowLeftPath.move(to: point)
|
arrowLeftPath.move(to: point)
|
||||||
let leftPoint = point.pointAt(distance: self.renderArrowLength, angle: direction - 0.45)
|
let leftPoint = point.pointAt(distance: self.renderArrowLength, angle: direction - 0.45)
|
||||||
@ -316,127 +203,7 @@ final class PenTool: DrawingElement {
|
|||||||
self.arrowRightPoint = rightPoint
|
self.arrowRightPoint = rightPoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .cancelled:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let currentRenderLayer = self.currentRenderLayer as? RenderLayer {
|
|
||||||
currentRenderLayer.draw(paths: self.bezierPaths, tempPath: self.tempBezierPath, color: self.color.toUIColor(), rect: CGRect(origin: .zero, size: self.drawingSize))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private let minDistance: CGFloat = 2
|
|
||||||
|
|
||||||
private func addPoint(_ point: CGPoint) {
|
|
||||||
if isFirstPoint {
|
|
||||||
startNewLine(from: PointWeighted(point: point, weight: 2.0))
|
|
||||||
} else {
|
|
||||||
let previousPoint = self.drawPoints[nextPointIndex - 1].point
|
|
||||||
guard previousPoint.distance(to: point) >= minDistance else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if isStartOfNextLine {
|
|
||||||
finalizeBezier(nextLineStartPoint: point)
|
|
||||||
startNewLine(from: self.drawPoints[3])
|
|
||||||
}
|
|
||||||
|
|
||||||
let weightedPoint = PointWeighted(point: point, weight: weightForLine(between: previousPoint, and: point))
|
|
||||||
addPoint(point: weightedPoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
let newBezier = generateBezierPath(withPointIndex: nextPointIndex - 1)
|
|
||||||
self.tempBezierPath = newBezier
|
|
||||||
}
|
|
||||||
|
|
||||||
private var isFirstPoint: Bool {
|
|
||||||
return nextPointIndex == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
private var isStartOfNextLine: Bool {
|
|
||||||
return nextPointIndex >= pointsPerLine
|
|
||||||
}
|
|
||||||
|
|
||||||
private func startNewLine(from weightedPoint: PointWeighted) {
|
|
||||||
drawPoints[0] = weightedPoint
|
|
||||||
nextPointIndex = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
private func addPoint(point: PointWeighted) {
|
|
||||||
drawPoints[nextPointIndex] = point
|
|
||||||
nextPointIndex += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
private func finalizeBezier(nextLineStartPoint: CGPoint) {
|
|
||||||
let touchPoint2 = drawPoints[2].point
|
|
||||||
let newTouchPoint3 = touchPoint2.average(with: nextLineStartPoint)
|
|
||||||
drawPoints[3] = PointWeighted(point: newTouchPoint3, weight: weightForLine(between: touchPoint2, and: newTouchPoint3))
|
|
||||||
|
|
||||||
guard let bezier = generateBezierPath(withPointIndex: 3) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.bezierPaths.append(bezier)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private func generateBezierPath(withPointIndex index: Int) -> UIBezierPath? {
|
|
||||||
switch index {
|
|
||||||
case 0:
|
|
||||||
return UIBezierPath.dot(with: drawPoints[0])
|
|
||||||
case 1:
|
|
||||||
return UIBezierPath.curve(withPointA: drawPoints[0], pointB: drawPoints[1])
|
|
||||||
case 2:
|
|
||||||
return UIBezierPath.curve(withPointA: drawPoints[0], pointB: drawPoints[1], pointC: drawPoints[2])
|
|
||||||
case 3:
|
|
||||||
return UIBezierPath.curve(withPointA: drawPoints[0], pointB: drawPoints[1], pointC: drawPoints[2], pointD: drawPoints[3])
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func weightForLine(between pointA: CGPoint, and pointB: CGPoint) -> CGFloat {
|
|
||||||
let length = pointA.distance(to: pointB)
|
|
||||||
|
|
||||||
let limitRange: CGFloat = 50
|
|
||||||
|
|
||||||
var lowerer: CGFloat = 0.2
|
|
||||||
var constant: CGFloat = 2
|
|
||||||
|
|
||||||
let toolWidth = self.renderLineWidth
|
|
||||||
|
|
||||||
constant = toolWidth - 3.0
|
|
||||||
lowerer = 0.25 * toolWidth / 10.0
|
|
||||||
|
|
||||||
|
|
||||||
let r = min(limitRange, length)
|
|
||||||
|
|
||||||
return (r * lowerer) + constant
|
|
||||||
}
|
|
||||||
|
|
||||||
public var firstPoint: CGPoint = .zero
|
|
||||||
public var currentPoint: CGPoint = .zero
|
|
||||||
private var previousPoint: CGPoint = .zero
|
|
||||||
private var previousPreviousPoint: CGPoint = .zero
|
|
||||||
|
|
||||||
private func setTouchPoints(point: CGPoint, previousPoint: CGPoint) {
|
|
||||||
self.previousPoint = previousPoint
|
|
||||||
self.previousPreviousPoint = previousPoint
|
|
||||||
self.currentPoint = point
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateTouchPoints(point: CGPoint, previousPoint: CGPoint) {
|
|
||||||
self.previousPreviousPoint = self.previousPoint
|
|
||||||
self.previousPoint = previousPoint
|
|
||||||
self.currentPoint = point
|
|
||||||
}
|
|
||||||
|
|
||||||
private func calculateMidPoint(_ p1 : CGPoint, p2 : CGPoint) -> CGPoint {
|
|
||||||
return CGPoint(x: (p1.x + p2.x) * 0.5, y: (p1.y + p2.y) * 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
private func getMidPoints() -> (CGPoint, CGPoint) {
|
|
||||||
let mid1 : CGPoint = calculateMidPoint(previousPoint, p2: previousPreviousPoint)
|
|
||||||
let mid2 : CGPoint = calculateMidPoint(currentPoint, p2: previousPoint)
|
|
||||||
return (mid1, mid2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func draw(in context: CGContext, size: CGSize) {
|
func draw(in context: CGContext, size: CGSize) {
|
||||||
@ -445,12 +212,8 @@ final class PenTool: DrawingElement {
|
|||||||
context.translateBy(x: self.translation.x, y: self.translation.y)
|
context.translateBy(x: self.translation.x, y: self.translation.y)
|
||||||
|
|
||||||
context.setShouldAntialias(true)
|
context.setShouldAntialias(true)
|
||||||
|
|
||||||
context.setFillColor(self.color.toCGColor())
|
self.renderLine.drawInContext(context)
|
||||||
for path in self.bezierPaths {
|
|
||||||
context.addPath(path.cgPath)
|
|
||||||
context.fillPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
if let arrowLeftPath, let arrowRightPath {
|
if let arrowLeftPath, let arrowRightPath {
|
||||||
context.setStrokeColor(self.color.toCGColor())
|
context.setStrokeColor(self.color.toCGColor())
|
||||||
@ -468,101 +231,210 @@ final class PenTool: DrawingElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UIBezierPath {
|
private class StrokeLine {
|
||||||
|
struct Segment {
|
||||||
class func dot(with weightedPoint: PointWeighted) -> UIBezierPath {
|
let a: CGPoint
|
||||||
let path = UIBezierPath()
|
let b: CGPoint
|
||||||
path.addArc(withCenter: weightedPoint.point, radius: weightedPoint.weight / 2.0, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
|
let c: CGPoint
|
||||||
|
let d: CGPoint
|
||||||
return path
|
let abWidth: CGFloat
|
||||||
}
|
let cdWidth: CGFloat
|
||||||
|
|
||||||
class func curve(withPointA pointA: PointWeighted, pointB: PointWeighted) -> UIBezierPath {
|
|
||||||
let lines = normalToLine(from: pointA, to: pointB)
|
|
||||||
|
|
||||||
let path = UIBezierPath()
|
|
||||||
path.move(to: lines.0.start)
|
|
||||||
path.addLine(to: lines.1.start)
|
|
||||||
let arcA = lines.1.start
|
|
||||||
let arcB = lines.1.end
|
|
||||||
path.addQuadCurve(to: arcB, controlPoint: pointA.point.forward(to: pointB.point, by: arcA.distance(to: arcB) / 1.1))
|
|
||||||
path.addLine(to: lines.0.end)
|
|
||||||
path.close()
|
|
||||||
|
|
||||||
return path
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class func curve(withPointA pointA: PointWeighted, pointB: PointWeighted, pointC: PointWeighted) -> UIBezierPath {
|
struct Point {
|
||||||
let linesAB = normalToLine(from: pointA, to: pointB)
|
let position: CGPoint
|
||||||
let linesBC = normalToLine(from: pointB, to: pointC)
|
let width: CGFloat
|
||||||
|
|
||||||
let lineA = linesAB.0
|
init(position: CGPoint, width: CGFloat) {
|
||||||
let lineB = linesAB.1.average(with: linesBC.0)
|
self.position = position
|
||||||
let lineC = linesBC.1
|
self.width = width
|
||||||
|
|
||||||
let path = UIBezierPath()
|
|
||||||
path.move(to: lineA.start)
|
|
||||||
path.addQuadCurve(to: lineC.start, controlPoint: lineB.start)
|
|
||||||
let arcA = lineC.start
|
|
||||||
let arcB = lineC.end
|
|
||||||
|
|
||||||
path.addQuadCurve(to: arcB, controlPoint: pointB.point.forward(to: pointC.point, by: arcA.distance(to: arcB) / 1.1))
|
|
||||||
path.addQuadCurve(to: lineA.end, controlPoint: lineB.end)
|
|
||||||
path.close()
|
|
||||||
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
class func line(withPointA pointA: PointWeighted, pointB: PointWeighted, pointC: PointWeighted, prevLineSegment: LineSegment, roundedEnd: Bool = true) -> (UIBezierPath, LineSegment) {
|
|
||||||
let linesAB = normalToLine(from: pointA, to: pointB)
|
|
||||||
let linesBC = normalToLine(from: pointB, to: pointC)
|
|
||||||
|
|
||||||
// let lineA = linesAB.0
|
|
||||||
let lineB = linesAB.1.average(with: linesBC.0)
|
|
||||||
let lineC = linesBC.1
|
|
||||||
|
|
||||||
let path = UIBezierPath()
|
|
||||||
path.move(to: prevLineSegment.start)
|
|
||||||
path.addQuadCurve(to: lineC.start, controlPoint: lineB.start)
|
|
||||||
if roundedEnd {
|
|
||||||
let arcA = lineC.start
|
|
||||||
let arcB = lineC.end
|
|
||||||
|
|
||||||
path.addQuadCurve(to: arcB, controlPoint: pointB.point.forward(to: pointC.point, by: arcA.distance(to: arcB) / 1.1))
|
|
||||||
} else {
|
|
||||||
path.addLine(to: lineC.end)
|
|
||||||
}
|
}
|
||||||
path.addQuadCurve(to: prevLineSegment.end, controlPoint: lineB.end)
|
}
|
||||||
path.close()
|
|
||||||
|
private(set) var points: [Point] = []
|
||||||
|
private var smoothPoints: [Point] = []
|
||||||
|
private var segments: [Segment] = []
|
||||||
|
|
||||||
return (path, lineC)
|
private let minLineWidth: CGFloat
|
||||||
|
let lineWidth: CGFloat
|
||||||
|
private var lastWidth: CGFloat?
|
||||||
|
|
||||||
|
var ready = false
|
||||||
|
|
||||||
|
let color: UIColor
|
||||||
|
|
||||||
|
init(color: UIColor, minLineWidth: CGFloat, lineWidth: CGFloat) {
|
||||||
|
self.color = color
|
||||||
|
self.minLineWidth = minLineWidth
|
||||||
|
self.lineWidth = lineWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
class func curve(withPointA pointA: PointWeighted, pointB: PointWeighted, pointC: PointWeighted, pointD: PointWeighted) -> UIBezierPath {
|
func draw(at point: Polyline.Point) -> CGRect {
|
||||||
let linesAB = normalToLine(from: pointA, to: pointB)
|
var velocity = point.velocity
|
||||||
let linesBC = normalToLine(from: pointB, to: pointC)
|
if velocity.isZero {
|
||||||
let linesCD = normalToLine(from: pointC, to: pointD)
|
velocity = 600.0
|
||||||
|
}
|
||||||
|
let width = extractLineWidth(from: velocity)
|
||||||
|
self.lastWidth = width
|
||||||
|
|
||||||
let lineA = linesAB.0
|
let point = Point(position: point.location, width: width)
|
||||||
let lineB = linesAB.1.average(with: linesBC.0)
|
return appendPoint(point)
|
||||||
let lineC = linesBC.1.average(with: linesCD.0)
|
|
||||||
let lineD = linesCD.1
|
|
||||||
|
|
||||||
let path = UIBezierPath()
|
|
||||||
path.move(to: lineA.start)
|
|
||||||
path.addCurve(to: lineD.start, controlPoint1: lineB.start, controlPoint2: lineC.start)
|
|
||||||
let arcA = lineD.start
|
|
||||||
let arcB = lineD.end
|
|
||||||
path.addQuadCurve(to: arcB, controlPoint: pointC.point.forward(to: pointD.point, by: arcA.distance(to: arcB) / 1.1))
|
|
||||||
path.addCurve(to: lineA.end, controlPoint1: lineC.end, controlPoint2: lineB.end)
|
|
||||||
path.close()
|
|
||||||
|
|
||||||
return path
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class func normalToLine(from pointA: PointWeighted, to pointB: PointWeighted) -> (LineSegment, LineSegment) {
|
func drawInContext(_ context: CGContext) {
|
||||||
let line = LineSegment(start: pointA.point, end: pointB.point)
|
self.drawSegments(self.segments, inContext: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractLineWidth(from velocity: CGFloat) -> CGFloat {
|
||||||
|
let minValue = self.minLineWidth
|
||||||
|
let maxValue = self.lineWidth
|
||||||
|
|
||||||
return (line.normalLine(from: pointA), line.normalLine(from: pointB))
|
var width = max(minValue, min(maxValue + 1.0 - (velocity / 180.0), maxValue))
|
||||||
|
if let lastWidth = self.lastWidth {
|
||||||
|
width = width * 0.2 + lastWidth * 0.8
|
||||||
|
}
|
||||||
|
return width
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendPoint(_ point: Point) -> CGRect {
|
||||||
|
self.points.append(point)
|
||||||
|
|
||||||
|
guard self.points.count > 2 else { return .null }
|
||||||
|
|
||||||
|
let index = self.points.count - 1
|
||||||
|
let point0 = self.points[index - 2]
|
||||||
|
let point1 = self.points[index - 1]
|
||||||
|
let point2 = self.points[index]
|
||||||
|
|
||||||
|
let newSmoothPoints = smoothPoints(
|
||||||
|
fromPoint0: point0,
|
||||||
|
point1: point1,
|
||||||
|
point2: point2
|
||||||
|
)
|
||||||
|
|
||||||
|
let lastOldSmoothPoint = smoothPoints.last
|
||||||
|
smoothPoints.append(contentsOf: newSmoothPoints)
|
||||||
|
|
||||||
|
guard smoothPoints.count > 1 else { return .null }
|
||||||
|
|
||||||
|
let newSegments: ([Segment], CGRect) = {
|
||||||
|
guard let lastOldSmoothPoint = lastOldSmoothPoint else {
|
||||||
|
return segments(fromSmoothPoints: newSmoothPoints)
|
||||||
|
}
|
||||||
|
return segments(fromSmoothPoints: [lastOldSmoothPoint] + newSmoothPoints)
|
||||||
|
}()
|
||||||
|
segments.append(contentsOf: newSegments.0)
|
||||||
|
|
||||||
|
self.ready = true
|
||||||
|
|
||||||
|
return newSegments.1
|
||||||
|
}
|
||||||
|
|
||||||
|
func smoothPoints(fromPoint0 point0: Point, point1: Point, point2: Point) -> [Point] {
|
||||||
|
var smoothPoints = [Point]()
|
||||||
|
|
||||||
|
let midPoint1 = (point0.position + point1.position) * 0.5
|
||||||
|
let midPoint2 = (point1.position + point2.position) * 0.5
|
||||||
|
|
||||||
|
let segmentDistance = 3.0
|
||||||
|
let distance = midPoint1.distance(to: midPoint2)
|
||||||
|
let numberOfSegments = min(128, max(floor(distance / segmentDistance), 32))
|
||||||
|
|
||||||
|
let step = 1.0 / numberOfSegments
|
||||||
|
for t in stride(from: 0, to: 1, by: step) {
|
||||||
|
let position = midPoint1 * pow(1 - t, 2) + point1.position * 2 * (1 - t) * t + midPoint2 * t * t
|
||||||
|
let size = pow(1 - t, 2) * ((point0.width + point1.width) * 0.5) + 2 * (1 - t) * t * point1.width + t * t * ((point1.width + point2.width) * 0.5)
|
||||||
|
let point = Point(position: position, width: size)
|
||||||
|
smoothPoints.append(point)
|
||||||
|
}
|
||||||
|
|
||||||
|
let finalPoint = Point(position: midPoint2, width: (point1.width + point2.width) * 0.5)
|
||||||
|
smoothPoints.append(finalPoint)
|
||||||
|
|
||||||
|
return smoothPoints
|
||||||
|
}
|
||||||
|
|
||||||
|
func segments(fromSmoothPoints smoothPoints: [Point]) -> ([Segment], CGRect) {
|
||||||
|
var segments: [Segment] = []
|
||||||
|
var updateRect = CGRect.null
|
||||||
|
for i in 1 ..< smoothPoints.count {
|
||||||
|
let previousPoint = smoothPoints[i - 1].position
|
||||||
|
let previousWidth = smoothPoints[i - 1].width
|
||||||
|
let currentPoint = smoothPoints[i].position
|
||||||
|
let currentWidth = smoothPoints[i].width
|
||||||
|
let direction = currentPoint - previousPoint
|
||||||
|
|
||||||
|
guard !currentPoint.isEqual(to: previousPoint, epsilon: 0.0001) else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var perpendicular = CGPoint(x: -direction.y, y: direction.x)
|
||||||
|
let length = perpendicular.length
|
||||||
|
if length > 0.0 {
|
||||||
|
perpendicular = perpendicular / length
|
||||||
|
}
|
||||||
|
|
||||||
|
let a = previousPoint + perpendicular * previousWidth / 2
|
||||||
|
let b = previousPoint - perpendicular * previousWidth / 2
|
||||||
|
let c = currentPoint + perpendicular * currentWidth / 2
|
||||||
|
let d = currentPoint - perpendicular * currentWidth / 2
|
||||||
|
|
||||||
|
let ab: CGPoint = {
|
||||||
|
let center = (b + a) / 2
|
||||||
|
let radius = center - b
|
||||||
|
return .init(x: center.x - radius.y, y: center.y + radius.x)
|
||||||
|
}()
|
||||||
|
let cd: CGPoint = {
|
||||||
|
let center = (c + d) / 2
|
||||||
|
let radius = center - c
|
||||||
|
return .init(x: center.x + radius.y, y: center.y - radius.x)
|
||||||
|
}()
|
||||||
|
|
||||||
|
let minX = min(a.x, b.x, c.x, d.x, ab.x, cd.x)
|
||||||
|
let minY = min(a.y, b.y, c.y, d.y, ab.y, cd.y)
|
||||||
|
let maxX = max(a.x, b.x, c.x, d.x, ab.x, cd.x)
|
||||||
|
let maxY = max(a.y, b.y, c.y, d.y, ab.y, cd.y)
|
||||||
|
|
||||||
|
updateRect = updateRect.union(CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY))
|
||||||
|
|
||||||
|
segments.append(Segment(a: a, b: b, c: c, d: d, abWidth: previousWidth, cdWidth: currentWidth))
|
||||||
|
}
|
||||||
|
return (segments, updateRect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawSegments(_ segments: [Segment], inContext context: CGContext) {
|
||||||
|
for segment in segments {
|
||||||
|
context.beginPath()
|
||||||
|
|
||||||
|
//let color = [UIColor.red, UIColor.green, UIColor.blue, UIColor.yellow].randomElement()!
|
||||||
|
|
||||||
|
context.setStrokeColor(color.cgColor)
|
||||||
|
context.setFillColor(color.cgColor)
|
||||||
|
|
||||||
|
context.move(to: segment.b)
|
||||||
|
|
||||||
|
let abStartAngle = atan2(segment.b.y - segment.a.y, segment.b.x - segment.a.x)
|
||||||
|
context.addArc(
|
||||||
|
center: (segment.a + segment.b)/2,
|
||||||
|
radius: segment.abWidth/2,
|
||||||
|
startAngle: abStartAngle,
|
||||||
|
endAngle: abStartAngle + .pi,
|
||||||
|
clockwise: true
|
||||||
|
)
|
||||||
|
context.addLine(to: segment.c)
|
||||||
|
|
||||||
|
let cdStartAngle = atan2(segment.c.y - segment.d.y, segment.c.x - segment.d.x)
|
||||||
|
context.addArc(
|
||||||
|
center: (segment.c + segment.d) / 2,
|
||||||
|
radius: segment.cdWidth/2,
|
||||||
|
startAngle: cdStartAngle,
|
||||||
|
endAngle: cdStartAngle + .pi,
|
||||||
|
clockwise: true
|
||||||
|
)
|
||||||
|
context.closePath()
|
||||||
|
|
||||||
|
context.fillPath()
|
||||||
|
context.strokePath()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -156,7 +156,8 @@ private final class StickerSelectionComponent: Component {
|
|||||||
hiddenInputHeight: 0.0,
|
hiddenInputHeight: 0.0,
|
||||||
inputHeight: 0.0,
|
inputHeight: 0.0,
|
||||||
displayBottomPanel: true,
|
displayBottomPanel: true,
|
||||||
isExpanded: true
|
isExpanded: true,
|
||||||
|
clipContentToTopPanel: false
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: availableSize
|
containerSize: availableSize
|
||||||
|
|||||||
@ -181,13 +181,15 @@ final class TextFontComponent: Component {
|
|||||||
|
|
||||||
let values: [DrawingTextFont]
|
let values: [DrawingTextFont]
|
||||||
let selectedValue: DrawingTextFont
|
let selectedValue: DrawingTextFont
|
||||||
|
let tag: AnyObject?
|
||||||
let updated: (DrawingTextFont) -> Void
|
let updated: (DrawingTextFont) -> Void
|
||||||
|
|
||||||
init(styleButton: AnyComponent<Empty>, alignmentButton: AnyComponent<Empty>, values: [DrawingTextFont], selectedValue: DrawingTextFont, updated: @escaping (DrawingTextFont) -> Void) {
|
init(styleButton: AnyComponent<Empty>, alignmentButton: AnyComponent<Empty>, values: [DrawingTextFont], selectedValue: DrawingTextFont, tag: AnyObject?, updated: @escaping (DrawingTextFont) -> Void) {
|
||||||
self.styleButton = styleButton
|
self.styleButton = styleButton
|
||||||
self.alignmentButton = alignmentButton
|
self.alignmentButton = alignmentButton
|
||||||
self.values = values
|
self.values = values
|
||||||
self.selectedValue = selectedValue
|
self.selectedValue = selectedValue
|
||||||
|
self.tag = tag
|
||||||
self.updated = updated
|
self.updated = updated
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,7 +197,7 @@ final class TextFontComponent: Component {
|
|||||||
return lhs.styleButton == rhs.styleButton && lhs.alignmentButton == rhs.alignmentButton && lhs.values == rhs.values && lhs.selectedValue == rhs.selectedValue
|
return lhs.styleButton == rhs.styleButton && lhs.alignmentButton == rhs.alignmentButton && lhs.values == rhs.values && lhs.selectedValue == rhs.selectedValue
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class View: UIView {
|
final class View: UIView, ComponentTaggedView {
|
||||||
private let styleButtonHost: ComponentView<Empty>
|
private let styleButtonHost: ComponentView<Empty>
|
||||||
private let alignmentButtonHost: ComponentView<Empty>
|
private let alignmentButtonHost: ComponentView<Empty>
|
||||||
|
|
||||||
@ -206,8 +208,19 @@ final class TextFontComponent: Component {
|
|||||||
private let maskCenter = SimpleLayer()
|
private let maskCenter = SimpleLayer()
|
||||||
private let maskRight = SimpleGradientLayer()
|
private let maskRight = SimpleGradientLayer()
|
||||||
|
|
||||||
|
private var component: TextFontComponent?
|
||||||
private var updated: (DrawingTextFont) -> Void = { _ in }
|
private var updated: (DrawingTextFont) -> Void = { _ in }
|
||||||
|
|
||||||
|
public func matches(tag: Any) -> Bool {
|
||||||
|
if let component = self.component, let componentTag = component.tag {
|
||||||
|
let tag = tag as AnyObject
|
||||||
|
if componentTag === tag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
if #available(iOS 11.0, *) {
|
if #available(iOS 11.0, *) {
|
||||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||||
@ -257,8 +270,57 @@ final class TextFontComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func animateIn() {
|
||||||
|
var delay: Double = 0.0
|
||||||
|
|
||||||
|
if let view = self.styleButtonHost.view {
|
||||||
|
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||||
|
delay += 0.02
|
||||||
|
}
|
||||||
|
|
||||||
|
if let view = self.alignmentButtonHost.view {
|
||||||
|
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: delay)
|
||||||
|
view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2, delay: delay)
|
||||||
|
delay += 0.02
|
||||||
|
}
|
||||||
|
|
||||||
|
if let component = self.component {
|
||||||
|
for value in component.values {
|
||||||
|
if let view = self.buttons[value] {
|
||||||
|
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: delay)
|
||||||
|
view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2, delay: delay)
|
||||||
|
delay += 0.02
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateOut(completion: @escaping () -> Void) {
|
||||||
|
if let view = self.styleButtonHost.view {
|
||||||
|
view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let view = self.alignmentButtonHost.view {
|
||||||
|
view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let component = self.component {
|
||||||
|
for value in component.values {
|
||||||
|
if let view = self.buttons[value] {
|
||||||
|
view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||||
|
completion()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private var previousValue: DrawingTextFont?
|
private var previousValue: DrawingTextFont?
|
||||||
func update(component: TextFontComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(component: TextFontComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
self.component = component
|
||||||
self.updated = component.updated
|
self.updated = component.updated
|
||||||
|
|
||||||
var contentWidth: CGFloat = 10.0
|
var contentWidth: CGFloat = 10.0
|
||||||
@ -362,6 +424,7 @@ final class TextSettingsComponent: CombinedComponent {
|
|||||||
let alignment: DrawingTextAlignment
|
let alignment: DrawingTextAlignment
|
||||||
let font: DrawingTextFont
|
let font: DrawingTextFont
|
||||||
let isEmojiKeyboard: Bool
|
let isEmojiKeyboard: Bool
|
||||||
|
let tag: AnyObject?
|
||||||
|
|
||||||
let presentColorPicker: () -> Void
|
let presentColorPicker: () -> Void
|
||||||
let presentFastColorPicker: (GenericComponentViewTag) -> Void
|
let presentFastColorPicker: (GenericComponentViewTag) -> Void
|
||||||
@ -378,6 +441,7 @@ final class TextSettingsComponent: CombinedComponent {
|
|||||||
alignment: DrawingTextAlignment,
|
alignment: DrawingTextAlignment,
|
||||||
font: DrawingTextFont,
|
font: DrawingTextFont,
|
||||||
isEmojiKeyboard: Bool,
|
isEmojiKeyboard: Bool,
|
||||||
|
tag: AnyObject?,
|
||||||
presentColorPicker: @escaping () -> Void = {},
|
presentColorPicker: @escaping () -> Void = {},
|
||||||
presentFastColorPicker: @escaping (GenericComponentViewTag) -> Void = { _ in },
|
presentFastColorPicker: @escaping (GenericComponentViewTag) -> Void = { _ in },
|
||||||
updateFastColorPickerPan: @escaping (CGPoint) -> Void = { _ in },
|
updateFastColorPickerPan: @escaping (CGPoint) -> Void = { _ in },
|
||||||
@ -392,6 +456,7 @@ final class TextSettingsComponent: CombinedComponent {
|
|||||||
self.alignment = alignment
|
self.alignment = alignment
|
||||||
self.font = font
|
self.font = font
|
||||||
self.isEmojiKeyboard = isEmojiKeyboard
|
self.isEmojiKeyboard = isEmojiKeyboard
|
||||||
|
self.tag = tag
|
||||||
self.presentColorPicker = presentColorPicker
|
self.presentColorPicker = presentColorPicker
|
||||||
self.presentFastColorPicker = presentFastColorPicker
|
self.presentFastColorPicker = presentFastColorPicker
|
||||||
self.updateFastColorPickerPan = updateFastColorPickerPan
|
self.updateFastColorPickerPan = updateFastColorPickerPan
|
||||||
@ -459,7 +524,7 @@ final class TextSettingsComponent: CombinedComponent {
|
|||||||
func makeState() -> State {
|
func makeState() -> State {
|
||||||
State()
|
State()
|
||||||
}
|
}
|
||||||
|
|
||||||
static var body: Body {
|
static var body: Body {
|
||||||
let colorButton = Child(ColorSwatchComponent.self)
|
let colorButton = Child(ColorSwatchComponent.self)
|
||||||
let colorButtonTag = GenericComponentViewTag()
|
let colorButtonTag = GenericComponentViewTag()
|
||||||
@ -554,6 +619,7 @@ final class TextSettingsComponent: CombinedComponent {
|
|||||||
),
|
),
|
||||||
values: DrawingTextFont.allCases,
|
values: DrawingTextFont.allCases,
|
||||||
selectedValue: component.font,
|
selectedValue: component.font,
|
||||||
|
tag: component.tag,
|
||||||
updated: { font in
|
updated: { font in
|
||||||
updateFont(font)
|
updateFont(font)
|
||||||
}
|
}
|
||||||
@ -723,6 +789,7 @@ final class TextSizeSliderComponent: Component {
|
|||||||
if let size = self.validSize, let component = self.component {
|
if let size = self.validSize, let component = self.component {
|
||||||
let _ = self.updateLayout(size: size, component: component, transition: .easeInOut(duration: 0.2))
|
let _ = self.updateLayout(size: size, component: component, transition: .easeInOut(duration: 0.2))
|
||||||
}
|
}
|
||||||
|
self.released()
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1567,7 +1567,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
if !items.isEmpty {
|
if !items.isEmpty {
|
||||||
items.append(.separator)
|
items.append(.separator)
|
||||||
}
|
}
|
||||||
items.append(.action(ContextMenuActionItem(text: hasSpoilers ? "Disable Spoiler Effect" : "Spoiler Effect", icon: { _ in return nil }, animationName: "anim_spoiler", action: { [weak self] _, f in
|
items.append(.action(ContextMenuActionItem(text: hasSpoilers ? strings.Attachment_DisableSpoiler : strings.Attachment_EnableSpoiler, icon: { _ in return nil }, animationName: "anim_spoiler", action: { [weak self] _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -32,15 +32,20 @@ public func peerInfoProfilePhotos(context: AccountContext, peerId: EnginePeer.Id
|
|||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|> mapToSignal { entries -> Signal<(Bool, [AvatarGalleryEntry])?, NoError> in
|
|> mapToSignal { entries -> Signal<(Bool, [AvatarGalleryEntry])?, NoError> in
|
||||||
if let entries = entries {
|
if let entries = entries {
|
||||||
if let firstEntry = entries.first {
|
if var firstEntry = entries.first {
|
||||||
return context.account.postbox.peerView(id: peerId)
|
return context.account.postbox.peerView(id: peerId)
|
||||||
|> mapToSignal { peerView -> Signal<(Bool, [AvatarGalleryEntry])?, NoError>in
|
|> mapToSignal { peerView -> Signal<(Bool, [AvatarGalleryEntry])?, NoError>in
|
||||||
if let peer = peerViewMainPeer(peerView) {
|
if let peer = peerViewMainPeer(peerView) {
|
||||||
var secondEntry: TelegramMediaImage?
|
var secondEntry: TelegramMediaImage?
|
||||||
var lastEntry: TelegramMediaImage?
|
var lastEntry: TelegramMediaImage?
|
||||||
if let cachedData = peerView.cachedData as? CachedUserData {
|
if let cachedData = peerView.cachedData as? CachedUserData {
|
||||||
if firstEntry.representations.first?.representation.isPersonal == true, case let .known(photo) = cachedData.photo {
|
if let firstRepresentation = firstEntry.representations.first, firstRepresentation.representation.isPersonal {
|
||||||
secondEntry = photo
|
if firstRepresentation.representation.hasVideo, case let .known(photo) = cachedData.personalPhoto, let peerReference = PeerReference(peer) {
|
||||||
|
firstEntry = .topImage(firstEntry.representations, photo?.videoRepresentations.map { VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) } ?? [], firstEntry.peer, firstEntry.indexData, firstEntry.immediateThumbnailData, nil)
|
||||||
|
}
|
||||||
|
if case let .known(photo) = cachedData.photo {
|
||||||
|
secondEntry = photo
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if case let .known(photo) = cachedData.fallbackPhoto {
|
if case let .known(photo) = cachedData.fallbackPhoto {
|
||||||
lastEntry = photo
|
lastEntry = photo
|
||||||
@ -329,7 +334,7 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account
|
|||||||
} else {
|
} else {
|
||||||
var photos = photos
|
var photos = photos
|
||||||
if let secondEntry {
|
if let secondEntry {
|
||||||
photos.insert(TelegramPeerPhoto(image: secondEntry, reference: secondEntry.reference, date: 0, index: 1, totalCount: 0, messageId: nil), at: 1)
|
photos.insert(TelegramPeerPhoto(image: secondEntry, reference: secondEntry.reference, date: photos.first?.date ?? 0, index: 1, totalCount: 0, messageId: nil), at: 1)
|
||||||
}
|
}
|
||||||
if let lastEntry {
|
if let lastEntry {
|
||||||
photos.append(TelegramPeerPhoto(image: lastEntry, reference: lastEntry.reference, date: 0, index: photos.count, totalCount: 0, messageId: nil))
|
photos.append(TelegramPeerPhoto(image: lastEntry, reference: lastEntry.reference, date: 0, index: photos.count, totalCount: 0, messageId: nil))
|
||||||
@ -337,7 +342,11 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account
|
|||||||
for photo in photos {
|
for photo in photos {
|
||||||
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count))
|
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count))
|
||||||
if result.isEmpty, let first = initialEntries.first {
|
if result.isEmpty, let first = initialEntries.first {
|
||||||
result.append(.image(photo.image.imageId, photo.image.reference, first.representations, photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil, false))
|
var videoRepresentations: [VideoRepresentationWithReference] = photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) })
|
||||||
|
if videoRepresentations.isEmpty {
|
||||||
|
videoRepresentations = first.videoRepresentations
|
||||||
|
}
|
||||||
|
result.append(.image(photo.image.imageId, photo.image.reference, first.representations, videoRepresentations, peer, secondEntry != nil ? 0 : photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil, false))
|
||||||
} else {
|
} else {
|
||||||
result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil, photo.image.id == lastEntry?.id))
|
result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil, photo.image.id == lastEntry?.id))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -108,8 +108,10 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
|
|||||||
var canShare = true
|
var canShare = true
|
||||||
switch entry {
|
switch entry {
|
||||||
case let .image(_, _, _, videoRepresentations, peer, date, _, _, _, _, _):
|
case let .image(_, _, _, videoRepresentations, peer, date, _, _, _, _, _):
|
||||||
nameText = peer.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""
|
if date != 0 {
|
||||||
if let date = date {
|
nameText = peer.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""
|
||||||
|
}
|
||||||
|
if let date = date, date != 0 {
|
||||||
dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: date).string
|
dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: date).string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
@ -21,6 +22,8 @@ import ItemListAddressItem
|
|||||||
import LocalizedPeerData
|
import LocalizedPeerData
|
||||||
import PhoneNumberFormat
|
import PhoneNumberFormat
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
import GalleryUI
|
||||||
|
import PeerAvatarGalleryUI
|
||||||
|
|
||||||
private enum DeviceContactInfoAction {
|
private enum DeviceContactInfoAction {
|
||||||
case sendMessage
|
case sendMessage
|
||||||
@ -45,8 +48,9 @@ private final class DeviceContactInfoControllerArguments {
|
|||||||
let openAddress: (DeviceContactAddressData) -> Void
|
let openAddress: (DeviceContactAddressData) -> Void
|
||||||
let displayCopyContextMenu: (DeviceContactInfoEntryTag, String) -> Void
|
let displayCopyContextMenu: (DeviceContactInfoEntryTag, String) -> Void
|
||||||
let updateShareViaException: (Bool) -> Void
|
let updateShareViaException: (Bool) -> Void
|
||||||
|
let openAvatar: (Peer) -> Void
|
||||||
|
|
||||||
init(context: AccountContext, isPlain: Bool, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updatePhone: @escaping (Int64, String) -> Void, updatePhoneLabel: @escaping (Int64, String) -> Void, deletePhone: @escaping (Int64) -> Void, setPhoneIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, addPhoneNumber: @escaping () -> Void, performAction: @escaping (DeviceContactInfoAction) -> Void, toggleSelection: @escaping (DeviceContactInfoDataId) -> Void, callPhone: @escaping (String) -> Void, openUrl: @escaping (String) -> Void, openAddress: @escaping (DeviceContactAddressData) -> Void, displayCopyContextMenu: @escaping (DeviceContactInfoEntryTag, String) -> Void, updateShareViaException: @escaping (Bool) -> Void) {
|
init(context: AccountContext, isPlain: Bool, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updatePhone: @escaping (Int64, String) -> Void, updatePhoneLabel: @escaping (Int64, String) -> Void, deletePhone: @escaping (Int64) -> Void, setPhoneIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, addPhoneNumber: @escaping () -> Void, performAction: @escaping (DeviceContactInfoAction) -> Void, toggleSelection: @escaping (DeviceContactInfoDataId) -> Void, callPhone: @escaping (String) -> Void, openUrl: @escaping (String) -> Void, openAddress: @escaping (DeviceContactAddressData) -> Void, displayCopyContextMenu: @escaping (DeviceContactInfoEntryTag, String) -> Void, updateShareViaException: @escaping (Bool) -> Void, openAvatar: @escaping (Peer) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.isPlain = isPlain
|
self.isPlain = isPlain
|
||||||
self.updateEditingName = updateEditingName
|
self.updateEditingName = updateEditingName
|
||||||
@ -62,6 +66,7 @@ private final class DeviceContactInfoControllerArguments {
|
|||||||
self.openAddress = openAddress
|
self.openAddress = openAddress
|
||||||
self.displayCopyContextMenu = displayCopyContextMenu
|
self.displayCopyContextMenu = displayCopyContextMenu
|
||||||
self.updateShareViaException = updateShareViaException
|
self.updateShareViaException = updateShareViaException
|
||||||
|
self.openAvatar = openAvatar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +127,7 @@ private enum DeviceContactInfoEntryId: Hashable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private enum DeviceContactInfoEntry: ItemListNodeEntry {
|
private enum DeviceContactInfoEntry: ItemListNodeEntry {
|
||||||
case info(Int, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, peer: Peer, state: ItemListAvatarAndNameInfoItemState, job: String?, isPlain: Bool)
|
case info(Int, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, peer: Peer, state: ItemListAvatarAndNameInfoItemState, job: String?, isPlain: Bool, hiddenAvatar: TelegramMediaImageRepresentation?)
|
||||||
|
|
||||||
case invite(Int, PresentationTheme, String)
|
case invite(Int, PresentationTheme, String)
|
||||||
case sendMessage(Int, PresentationTheme, String)
|
case sendMessage(Int, PresentationTheme, String)
|
||||||
@ -204,8 +209,8 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
static func ==(lhs: DeviceContactInfoEntry, rhs: DeviceContactInfoEntry) -> Bool {
|
static func ==(lhs: DeviceContactInfoEntry, rhs: DeviceContactInfoEntry) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
case let .info(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsState, lhsJobSummary, lhsIsPlain):
|
case let .info(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsState, lhsJobSummary, lhsIsPlain, lhsHiddenAvatar):
|
||||||
if case let .info(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsState, rhsJobSummary, rhsIsPlain) = rhs {
|
if case let .info(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsState, rhsJobSummary, rhsIsPlain, rhsHiddenAvatar) = rhs {
|
||||||
if lhsIndex != rhsIndex {
|
if lhsIndex != rhsIndex {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -230,6 +235,9 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry {
|
|||||||
if lhsIsPlain != rhsIsPlain {
|
if lhsIsPlain != rhsIsPlain {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhsHiddenAvatar != rhsHiddenAvatar {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -347,7 +355,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
private var sortIndex: Int {
|
private var sortIndex: Int {
|
||||||
switch self {
|
switch self {
|
||||||
case let .info(index, _, _, _, _, _, _, _):
|
case let .info(index, _, _, _, _, _, _, _, _):
|
||||||
return index
|
return index
|
||||||
case let .sendMessage(index, _, _):
|
case let .sendMessage(index, _, _):
|
||||||
return index
|
return index
|
||||||
@ -395,11 +403,14 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry {
|
|||||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||||
let arguments = arguments as! DeviceContactInfoControllerArguments
|
let arguments = arguments as! DeviceContactInfoControllerArguments
|
||||||
switch self {
|
switch self {
|
||||||
case let .info(_, _, _, dateTimeFormat, peer, state, jobSummary, _):
|
case let .info(_, _, _, dateTimeFormat, peer, state, jobSummary, _, hiddenAvatar):
|
||||||
return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .contact, peer: EnginePeer(peer), presence: nil, label: jobSummary, memberCount: nil, state: state, sectionId: self.section, style: arguments.isPlain ? .plain : .blocks(withTopInset: false, withExtendedBottomInset: true), editingNameUpdated: { editingName in
|
return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .contact, peer: EnginePeer(peer), presence: nil, label: jobSummary, memberCount: nil, state: state, sectionId: self.section, style: arguments.isPlain ? .plain : .blocks(withTopInset: false, withExtendedBottomInset: true), editingNameUpdated: { editingName in
|
||||||
arguments.updateEditingName(editingName)
|
arguments.updateEditingName(editingName)
|
||||||
}, avatarTapped: {
|
}, avatarTapped: {
|
||||||
}, context: nil, call: nil)
|
if peer.smallProfileImage != nil {
|
||||||
|
arguments.openAvatar(peer)
|
||||||
|
}
|
||||||
|
}, context: ItemListAvatarAndNameInfoItemContext(hiddenAvatarRepresentation: hiddenAvatar), call: nil)
|
||||||
case let .sendMessage(_, _, title):
|
case let .sendMessage(_, _, title):
|
||||||
return ItemListActionItem(presentationData: presentationData, title: title, kind: .generic, alignment: .natural, sectionId: self.section, style: arguments.isPlain ? .plain : .blocks, action: {
|
return ItemListActionItem(presentationData: presentationData, title: title, kind: .generic, alignment: .natural, sectionId: self.section, style: arguments.isPlain ? .plain : .blocks, action: {
|
||||||
arguments.performAction(.sendMessage)
|
arguments.performAction(.sendMessage)
|
||||||
@ -614,7 +625,7 @@ private func filteredContactData(contactData: DeviceContactExtendedData, exclude
|
|||||||
return DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumbers: phoneNumbers), middleName: contactData.middleName, prefix: contactData.prefix, suffix: contactData.suffix, organization: includeJob ? contactData.organization : "", jobTitle: includeJob ? contactData.jobTitle : "", department: includeJob ? contactData.department : "", emailAddresses: emailAddresses, urls: urls, addresses: addresses, birthdayDate: includeBirthday ? contactData.birthdayDate : nil, socialProfiles: socialProfiles, instantMessagingProfiles: instantMessagingProfiles, note: includeNote ? contactData.note : "")
|
return DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumbers: phoneNumbers), middleName: contactData.middleName, prefix: contactData.prefix, suffix: contactData.suffix, organization: includeJob ? contactData.organization : "", jobTitle: includeJob ? contactData.jobTitle : "", department: includeJob ? contactData.department : "", emailAddresses: emailAddresses, urls: urls, addresses: addresses, birthdayDate: includeBirthday ? contactData.birthdayDate : nil, socialProfiles: socialProfiles, instantMessagingProfiles: instantMessagingProfiles, note: includeNote ? contactData.note : "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func deviceContactInfoEntries(account: Account, engine: TelegramEngine, presentationData: PresentationData, peer: Peer?, isShare: Bool, shareViaException: Bool, contactData: DeviceContactExtendedData, isContact: Bool, state: DeviceContactInfoState, selecting: Bool, editingPhoneNumbers: Bool) -> [DeviceContactInfoEntry] {
|
private func deviceContactInfoEntries(account: Account, engine: TelegramEngine, presentationData: PresentationData, peer: Peer?, isShare: Bool, shareViaException: Bool, contactData: DeviceContactExtendedData, isContact: Bool, state: DeviceContactInfoState, selecting: Bool, editingPhoneNumbers: Bool, hiddenAvatar: TelegramMediaImageRepresentation?) -> [DeviceContactInfoEntry] {
|
||||||
var entries: [DeviceContactInfoEntry] = []
|
var entries: [DeviceContactInfoEntry] = []
|
||||||
|
|
||||||
var editingName: ItemListAvatarAndNameInfoItemName?
|
var editingName: ItemListAvatarAndNameInfoItemName?
|
||||||
@ -652,7 +663,7 @@ private func deviceContactInfoEntries(account: Account, engine: TelegramEngine,
|
|||||||
firstName = presentationData.strings.Message_Contact
|
firstName = presentationData.strings.Message_Contact
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.info(entries.count, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer: peer ?? TelegramUser(id: PeerId(namespace: .max, id: PeerId.Id._internalFromInt64Value(0)), accessHash: nil, firstName: firstName, lastName: isOrganization ? nil : personName.1, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []), state: ItemListAvatarAndNameInfoItemState(editingName: editingName, updatingName: nil), job: isOrganization ? nil : jobSummary, isPlain: !isShare))
|
entries.append(.info(entries.count, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer: peer ?? TelegramUser(id: PeerId(namespace: .max, id: PeerId.Id._internalFromInt64Value(0)), accessHash: nil, firstName: firstName, lastName: isOrganization ? nil : personName.1, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []), state: ItemListAvatarAndNameInfoItemState(editingName: editingName, updatingName: nil), job: isOrganization ? nil : jobSummary, isPlain: !isShare, hiddenAvatar: hiddenAvatar))
|
||||||
|
|
||||||
if !selecting {
|
if !selecting {
|
||||||
if let _ = peer {
|
if let _ = peer {
|
||||||
@ -856,6 +867,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
|
|||||||
var openAddressImpl: ((DeviceContactAddressData) -> Void)?
|
var openAddressImpl: ((DeviceContactAddressData) -> Void)?
|
||||||
var inviteImpl: (([String]) -> Void)?
|
var inviteImpl: (([String]) -> Void)?
|
||||||
var dismissImpl: ((Bool) -> Void)?
|
var dismissImpl: ((Bool) -> Void)?
|
||||||
|
var openAvatarImpl: ((Peer) -> Void)?
|
||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
let actionsDisposable = DisposableSet()
|
||||||
|
|
||||||
@ -1042,12 +1054,15 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
|
|||||||
state.addToPrivacyExceptions = value
|
state.addToPrivacyExceptions = value
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
}, openAvatar: { peer in
|
||||||
|
openAvatarImpl?(peer)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let hiddenAvatarPromise = Promise<TelegramMediaImageRepresentation?>(nil)
|
||||||
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||||||
let previousEditingPhoneIds = Atomic<Set<Int64>?>(value: nil)
|
let previousEditingPhoneIds = Atomic<Set<Int64>?>(value: nil)
|
||||||
let signal = combineLatest(presentationData, statePromise.get(), contactData)
|
let signal = combineLatest(presentationData, statePromise.get(), contactData, hiddenAvatarPromise.get())
|
||||||
|> map { presentationData, state, peerAndContactData -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|> map { presentationData, state, peerAndContactData, hiddenAvatar -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||||
var leftNavigationButton: ItemListNavigationButton?
|
var leftNavigationButton: ItemListNavigationButton?
|
||||||
switch subject {
|
switch subject {
|
||||||
case .vcard:
|
case .vcard:
|
||||||
@ -1230,7 +1245,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
|
|||||||
focusItemTag = DeviceContactInfoEntryTag.editingPhone(insertedPhoneId)
|
focusItemTag = DeviceContactInfoEntryTag.editingPhone(insertedPhoneId)
|
||||||
}
|
}
|
||||||
|
|
||||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: deviceContactInfoEntries(account: context.account, engine: context.engine, presentationData: presentationData, peer: peerAndContactData.0, isShare: isShare, shareViaException: shareViaException, contactData: peerAndContactData.2, isContact: peerAndContactData.1 != nil, state: state, selecting: selecting, editingPhoneNumbers: editingPhones), style: isShare ? .blocks : .plain, focusItemTag: focusItemTag)
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: deviceContactInfoEntries(account: context.account, engine: context.engine, presentationData: presentationData, peer: peerAndContactData.0, isShare: isShare, shareViaException: shareViaException, contactData: peerAndContactData.2, isContact: peerAndContactData.1 != nil, state: state, selecting: selecting, editingPhoneNumbers: editingPhones, hiddenAvatar: hiddenAvatar), style: isShare ? .blocks : .plain, focusItemTag: focusItemTag)
|
||||||
|
|
||||||
return (controllerState, (listState, arguments))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
@ -1329,6 +1344,32 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
openAvatarImpl = { [weak controller] peer in
|
||||||
|
let avatarController = AvatarGalleryController(context: context, peer: peer, replaceRootController: { _, _ in
|
||||||
|
})
|
||||||
|
hiddenAvatarPromise.set(
|
||||||
|
avatarController.hiddenMedia
|
||||||
|
|> map { entry -> TelegramMediaImageRepresentation? in
|
||||||
|
return entry?.representations.first?.representation
|
||||||
|
}
|
||||||
|
)
|
||||||
|
presentControllerImpl?(avatarController, AvatarGalleryControllerPresentationArguments(transitionArguments: { [weak controller] entry in
|
||||||
|
var transitionNode: ((ASDisplayNode, CGRect, () -> (UIView?, UIView?)), CGRect)?
|
||||||
|
controller?.forEachItemNode({ itemNode in
|
||||||
|
if let itemNode = itemNode as? ItemListAvatarAndNameInfoItemNode {
|
||||||
|
transitionNode = itemNode.avatarTransitionNode()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if let transitionNode = transitionNode {
|
||||||
|
return GalleryTransitionArguments(transitionNode: transitionNode.0, addToTransitionSurface: { [weak controller] view in
|
||||||
|
controller?.view.addSubview(view)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|||||||
@ -256,6 +256,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileprivate var clipContentToTopPanel: Bool = false
|
||||||
|
|
||||||
var externalTopPanelContainerImpl: PagerExternalTopPanelContainer?
|
var externalTopPanelContainerImpl: PagerExternalTopPanelContainer?
|
||||||
public override var externalTopPanelContainer: UIView? {
|
public override var externalTopPanelContainer: UIView? {
|
||||||
return self.externalTopPanelContainerImpl
|
return self.externalTopPanelContainerImpl
|
||||||
@ -1417,7 +1419,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
hiddenInputHeight: hiddenInputHeight,
|
hiddenInputHeight: hiddenInputHeight,
|
||||||
inputHeight: inputHeight,
|
inputHeight: inputHeight,
|
||||||
displayBottomPanel: true,
|
displayBottomPanel: true,
|
||||||
isExpanded: isExpanded && !self.isEmojiSearchActive
|
isExpanded: isExpanded && !self.isEmojiSearchActive,
|
||||||
|
clipContentToTopPanel: self.clipContentToTopPanel
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: width, height: expandedHeight)
|
containerSize: CGSize(width: width, height: expandedHeight)
|
||||||
@ -1895,6 +1898,7 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi
|
|||||||
chatPeerId: nil
|
chatPeerId: nil
|
||||||
)
|
)
|
||||||
self.inputNode = inputNode
|
self.inputNode = inputNode
|
||||||
|
inputNode.clipContentToTopPanel = hideBackground
|
||||||
inputNode.emojiInputInteraction = inputInteraction
|
inputNode.emojiInputInteraction = inputInteraction
|
||||||
inputNode.externalTopPanelContainerImpl = nil
|
inputNode.externalTopPanelContainerImpl = nil
|
||||||
inputNode.switchToTextInput = { [weak self] in
|
inputNode.switchToTextInput = { [weak self] in
|
||||||
|
|||||||
@ -189,7 +189,8 @@ public final class EmojiStatusSelectionComponent: Component {
|
|||||||
hiddenInputHeight: 0.0,
|
hiddenInputHeight: 0.0,
|
||||||
inputHeight: 0.0,
|
inputHeight: 0.0,
|
||||||
displayBottomPanel: false,
|
displayBottomPanel: false,
|
||||||
isExpanded: false
|
isExpanded: false,
|
||||||
|
clipContentToTopPanel: false
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: availableSize
|
containerSize: availableSize
|
||||||
|
|||||||
@ -113,6 +113,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
public let inputHeight: CGFloat
|
public let inputHeight: CGFloat
|
||||||
public let displayBottomPanel: Bool
|
public let displayBottomPanel: Bool
|
||||||
public let isExpanded: Bool
|
public let isExpanded: Bool
|
||||||
|
public let clipContentToTopPanel: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
@ -141,7 +142,8 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
hiddenInputHeight: CGFloat,
|
hiddenInputHeight: CGFloat,
|
||||||
inputHeight: CGFloat,
|
inputHeight: CGFloat,
|
||||||
displayBottomPanel: Bool,
|
displayBottomPanel: Bool,
|
||||||
isExpanded: Bool
|
isExpanded: Bool,
|
||||||
|
clipContentToTopPanel: Bool
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
@ -170,6 +172,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
self.inputHeight = inputHeight
|
self.inputHeight = inputHeight
|
||||||
self.displayBottomPanel = displayBottomPanel
|
self.displayBottomPanel = displayBottomPanel
|
||||||
self.isExpanded = isExpanded
|
self.isExpanded = isExpanded
|
||||||
|
self.clipContentToTopPanel = clipContentToTopPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: EntityKeyboardComponent, rhs: EntityKeyboardComponent) -> Bool {
|
public static func ==(lhs: EntityKeyboardComponent, rhs: EntityKeyboardComponent) -> Bool {
|
||||||
@ -230,6 +233,9 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
if lhs.isExpanded != rhs.isExpanded {
|
if lhs.isExpanded != rhs.isExpanded {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.clipContentToTopPanel != rhs.clipContentToTopPanel {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -738,7 +744,8 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
}
|
}
|
||||||
strongSelf.isTopPanelHiddenUpdated(isTopPanelHidden: isTopPanelHidden, transition: transition)
|
strongSelf.isTopPanelHiddenUpdated(isTopPanelHidden: isTopPanelHidden, transition: transition)
|
||||||
},
|
},
|
||||||
panelHideBehavior: panelHideBehavior
|
panelHideBehavior: panelHideBehavior,
|
||||||
|
clipContentToTopPanel: component.clipContentToTopPanel
|
||||||
)),
|
)),
|
||||||
environment: {
|
environment: {
|
||||||
EntityKeyboardChildEnvironment(
|
EntityKeyboardChildEnvironment(
|
||||||
|
|||||||
@ -420,7 +420,8 @@ private final class TopicIconSelectionComponent: Component {
|
|||||||
hiddenInputHeight: 0.0,
|
hiddenInputHeight: 0.0,
|
||||||
inputHeight: 0.0,
|
inputHeight: 0.0,
|
||||||
displayBottomPanel: false,
|
displayBottomPanel: false,
|
||||||
isExpanded: true
|
isExpanded: true,
|
||||||
|
clipContentToTopPanel: false
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: availableSize
|
containerSize: availableSize
|
||||||
|
|||||||
21
submodules/TelegramUI/Images.xcassets/Media Editor/Grayscale.imageset/Contents.json
vendored
Normal file
21
submodules/TelegramUI/Images.xcassets/Media Editor/Grayscale.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "grayscale.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
submodules/TelegramUI/Images.xcassets/Media Editor/Grayscale.imageset/grayscale.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Media Editor/Grayscale.imageset/grayscale.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 787 B |
File diff suppressed because one or more lines are too long
@ -1372,27 +1372,28 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
|||||||
let ItemInfo = 3
|
let ItemInfo = 3
|
||||||
let ItemDelete = 4
|
let ItemDelete = 4
|
||||||
|
|
||||||
let compactName = EnginePeer(user).compactDisplayTitle
|
if !user.flags.contains(.isSupport) {
|
||||||
|
let compactName = EnginePeer(user).compactDisplayTitle
|
||||||
items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemSuggest, text: presentationData.strings.UserInfo_SuggestPhoto(compactName).string, color: .accent, icon: UIImage(bundleImageName: "Peer Info/SuggestAvatar"), action: {
|
items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemSuggest, text: presentationData.strings.UserInfo_SuggestPhoto(compactName).string, color: .accent, icon: UIImage(bundleImageName: "Peer Info/SuggestAvatar"), action: {
|
||||||
interaction.suggestPhoto()
|
interaction.suggestPhoto()
|
||||||
}))
|
|
||||||
|
|
||||||
items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemCustom, text: presentationData.strings.UserInfo_SetCustomPhoto(compactName).string, color: .accent, icon: UIImage(bundleImageName: "Settings/SetAvatar"), action: {
|
|
||||||
interaction.setCustomPhoto()
|
|
||||||
}))
|
|
||||||
|
|
||||||
if user.photo.first?.isPersonal == true || state.updatingAvatar != nil {
|
|
||||||
var representation: TelegramMediaImageRepresentation?
|
|
||||||
if let cachedData = data.cachedData as? CachedUserData, case let .known(photo) = cachedData.photo {
|
|
||||||
representation = photo?.representationForDisplayAtSize(PixelDimensions(width: 28, height: 28))
|
|
||||||
}
|
|
||||||
|
|
||||||
items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemReset, text: presentationData.strings.UserInfo_ResetCustomPhoto, color: .accent, icon: nil, iconSignal: peerAvatarCompleteImage(account: context.account, peer: EnginePeer(user), forceProvidedRepresentation: true, representation: representation, size: CGSize(width: 28.0, height: 28.0)), action: {
|
|
||||||
interaction.resetCustomPhoto()
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemCustom, text: presentationData.strings.UserInfo_SetCustomPhoto(compactName).string, color: .accent, icon: UIImage(bundleImageName: "Settings/SetAvatar"), action: {
|
||||||
|
interaction.setCustomPhoto()
|
||||||
|
}))
|
||||||
|
|
||||||
|
if user.photo.first?.isPersonal == true || state.updatingAvatar != nil {
|
||||||
|
var representation: TelegramMediaImageRepresentation?
|
||||||
|
if let cachedData = data.cachedData as? CachedUserData, case let .known(photo) = cachedData.photo {
|
||||||
|
representation = photo?.representationForDisplayAtSize(PixelDimensions(width: 28, height: 28))
|
||||||
|
}
|
||||||
|
|
||||||
|
items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemReset, text: presentationData.strings.UserInfo_ResetCustomPhoto, color: .accent, icon: nil, iconSignal: peerAvatarCompleteImage(account: context.account, peer: EnginePeer(user), forceProvidedRepresentation: true, representation: representation, size: CGSize(width: 28.0, height: 28.0)), action: {
|
||||||
|
interaction.resetCustomPhoto()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
items[.peerDataSettings]!.append(PeerInfoScreenCommentItem(id: ItemInfo, text: presentationData.strings.UserInfo_CustomPhotoInfo(compactName).string))
|
||||||
}
|
}
|
||||||
items[.peerDataSettings]!.append(PeerInfoScreenCommentItem(id: ItemInfo, text: presentationData.strings.UserInfo_CustomPhotoInfo(compactName).string))
|
|
||||||
|
|
||||||
if data.isContact {
|
if data.isContact {
|
||||||
items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemDelete, text: presentationData.strings.UserInfo_DeleteContact, color: .destructive, action: {
|
items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemDelete, text: presentationData.strings.UserInfo_DeleteContact, color: .destructive, action: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user