mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 17:30:12 +00:00
Camera and editor improvements
This commit is contained in:
parent
8335936796
commit
155b8786a1
@ -9357,3 +9357,5 @@ Sorry for the inconvenience.";
|
|||||||
"Privacy.Bio.NeverShareWith.Title" = "Never Share With";
|
"Privacy.Bio.NeverShareWith.Title" = "Never Share With";
|
||||||
|
|
||||||
"Conversation.OpenLink" = "OPEN LINK";
|
"Conversation.OpenLink" = "OPEN LINK";
|
||||||
|
|
||||||
|
"Paint.Flip" = "Flip";
|
||||||
|
|||||||
@ -540,7 +540,15 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
controller.setIgnoreAppearanceMethodInvocations(false)
|
controller.setIgnoreAppearanceMethodInvocations(false)
|
||||||
controller.viewDidDisappear(transition.isAnimated)
|
controller.viewDidDisappear(transition.isAnimated)
|
||||||
}
|
}
|
||||||
|
if let (layout, _, coveredByModalTransition) = self.validLayout {
|
||||||
|
self.update(layout: layout, controllers: [], coveredByModalTransition: coveredByModalTransition, transition: .immediate)
|
||||||
|
}
|
||||||
completion()
|
completion()
|
||||||
|
|
||||||
|
var bounds = self.bounds
|
||||||
|
bounds.origin.y = 0.0
|
||||||
|
self.bounds = bounds
|
||||||
|
|
||||||
return transition
|
return transition
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -207,7 +207,7 @@ public class AttachmentController: ViewController {
|
|||||||
private weak var controller: AttachmentController?
|
private weak var controller: AttachmentController?
|
||||||
private let dim: ASDisplayNode
|
private let dim: ASDisplayNode
|
||||||
private let shadowNode: ASImageNode
|
private let shadowNode: ASImageNode
|
||||||
private let container: AttachmentContainer
|
fileprivate let container: AttachmentContainer
|
||||||
private let makeEntityInputView: () -> AttachmentTextInputPanelInputView?
|
private let makeEntityInputView: () -> AttachmentTextInputPanelInputView?
|
||||||
let panel: AttachmentPanel
|
let panel: AttachmentPanel
|
||||||
|
|
||||||
@ -216,7 +216,7 @@ public class AttachmentController: ViewController {
|
|||||||
|
|
||||||
private var validLayout: ContainerViewLayout?
|
private var validLayout: ContainerViewLayout?
|
||||||
private var modalProgress: CGFloat = 0.0
|
private var modalProgress: CGFloat = 0.0
|
||||||
private var isDismissing = false
|
fileprivate var isDismissing = false
|
||||||
|
|
||||||
private let captionDisposable = MetaDisposable()
|
private let captionDisposable = MetaDisposable()
|
||||||
private let mediaSelectionCountDisposable = MetaDisposable()
|
private let mediaSelectionCountDisposable = MetaDisposable()
|
||||||
@ -313,6 +313,10 @@ public class AttachmentController: ViewController {
|
|||||||
|
|
||||||
self.container.updateModalProgress = { [weak self] progress, transition in
|
self.container.updateModalProgress = { [weak self] progress, transition in
|
||||||
if let strongSelf = self, let layout = strongSelf.validLayout, !strongSelf.isDismissing {
|
if let strongSelf = self, let layout = strongSelf.validLayout, !strongSelf.isDismissing {
|
||||||
|
var transition = transition
|
||||||
|
if strongSelf.container.supernode == nil {
|
||||||
|
transition = .animated(duration: 0.4, curve: .spring)
|
||||||
|
}
|
||||||
strongSelf.controller?.updateModalStyleOverlayTransitionFactor(progress, transition: transition)
|
strongSelf.controller?.updateModalStyleOverlayTransitionFactor(progress, transition: transition)
|
||||||
|
|
||||||
strongSelf.modalProgress = progress
|
strongSelf.modalProgress = progress
|
||||||
@ -645,7 +649,7 @@ public class AttachmentController: ViewController {
|
|||||||
} else {
|
} else {
|
||||||
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0)
|
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0)
|
||||||
|
|
||||||
let targetPosition = self.container.position
|
let targetPosition = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
|
||||||
let startPosition = targetPosition.offsetBy(dx: 0.0, dy: layout.size.height)
|
let startPosition = targetPosition.offsetBy(dx: 0.0, dy: layout.size.height)
|
||||||
|
|
||||||
self.container.position = startPosition
|
self.container.position = startPosition
|
||||||
@ -877,7 +881,7 @@ public class AttachmentController: ViewController {
|
|||||||
|
|
||||||
self.container.update(layout: containerLayout, controllers: controllers, coveredByModalTransition: 0.0, transition: self.switchingController ? .immediate : transition)
|
self.container.update(layout: containerLayout, controllers: controllers, coveredByModalTransition: 0.0, transition: self.switchingController ? .immediate : transition)
|
||||||
|
|
||||||
if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady {
|
if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady && !self.isDismissing {
|
||||||
self.wrapperNode.addSubnode(self.container)
|
self.wrapperNode.addSubnode(self.container)
|
||||||
|
|
||||||
if fromMenu, let _ = controller.getInputContainerNode() {
|
if fromMenu, let _ = controller.getInputContainerNode() {
|
||||||
@ -965,12 +969,17 @@ public class AttachmentController: ViewController {
|
|||||||
self?.didDismiss()
|
self?.didDismiss()
|
||||||
self?._dismiss()
|
self?._dismiss()
|
||||||
completion?()
|
completion?()
|
||||||
|
self?.dismissedFlag = false
|
||||||
|
self?.node.isDismissing = false
|
||||||
|
self?.node.container.removeFromSupernode()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.didDismiss()
|
self.didDismiss()
|
||||||
self._dismiss()
|
self._dismiss()
|
||||||
completion?()
|
completion?()
|
||||||
|
self.node.isDismissing = false
|
||||||
|
self.node.container.removeFromSupernode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -179,7 +179,7 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel
|
|||||||
|
|
||||||
self.addPhotoButton.addTarget(self, action: #selector(self.addPhotoPressed), forControlEvents: .touchUpInside)
|
self.addPhotoButton.addTarget(self, action: #selector(self.addPhotoPressed), forControlEvents: .touchUpInside)
|
||||||
|
|
||||||
self.termsNode.linkHighlightColor = self.theme.list.itemAccentColor.withAlphaComponent(0.5)
|
self.termsNode.linkHighlightColor = self.theme.list.itemAccentColor.withAlphaComponent(0.2)
|
||||||
self.termsNode.highlightAttributeAction = { attributes in
|
self.termsNode.highlightAttributeAction = { attributes in
|
||||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||||
|
|||||||
@ -577,7 +577,7 @@ private final class RecurrentConfirmationNode: ASDisplayNode {
|
|||||||
|
|
||||||
let checkSize = CGSize(width: 22.0, height: 22.0)
|
let checkSize = CGSize(width: 22.0, height: 22.0)
|
||||||
|
|
||||||
self.textNode.linkHighlightColor = presentationData.theme.list.itemAccentColor.withAlphaComponent(0.3)
|
self.textNode.linkHighlightColor = presentationData.theme.list.itemAccentColor.withAlphaComponent(0.2)
|
||||||
|
|
||||||
let attributedText = parseMarkdownIntoAttributedString(
|
let attributedText = parseMarkdownIntoAttributedString(
|
||||||
presentationData.strings.Bot_AccepRecurrentInfo(botName).string,
|
presentationData.strings.Bot_AccepRecurrentInfo(botName).string,
|
||||||
|
|||||||
@ -157,6 +157,12 @@ private final class CameraContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var _positionPromise = ValuePromise<Camera.Position>(.unspecified)
|
||||||
|
var position: Signal<Camera.Position, NoError> {
|
||||||
|
return self._positionPromise.get()
|
||||||
|
}
|
||||||
|
|
||||||
func togglePosition() {
|
func togglePosition() {
|
||||||
self.configure {
|
self.configure {
|
||||||
self.input.invalidate(for: self.session)
|
self.input.invalidate(for: self.session)
|
||||||
@ -166,6 +172,7 @@ private final class CameraContext {
|
|||||||
} else {
|
} else {
|
||||||
targetPosition = .back
|
targetPosition = .back
|
||||||
}
|
}
|
||||||
|
self._positionPromise.set(targetPosition)
|
||||||
self.changingPosition = true
|
self.changingPosition = true
|
||||||
self.device.configure(for: self.session, position: targetPosition)
|
self.device.configure(for: self.session, position: targetPosition)
|
||||||
self.input.configure(for: self.session, device: self.device, audio: self.initialConfiguration.audio)
|
self.input.configure(for: self.session, device: self.device, audio: self.initialConfiguration.audio)
|
||||||
@ -179,6 +186,7 @@ private final class CameraContext {
|
|||||||
|
|
||||||
public func setPosition(_ position: Camera.Position) {
|
public func setPosition(_ position: Camera.Position) {
|
||||||
self.configure {
|
self.configure {
|
||||||
|
self._positionPromise.set(position)
|
||||||
self.input.invalidate(for: self.session)
|
self.input.invalidate(for: self.session)
|
||||||
self.device.configure(for: self.session, position: position)
|
self.device.configure(for: self.session, position: position)
|
||||||
self.input.configure(for: self.session, device: self.device, audio: self.initialConfiguration.audio)
|
self.input.configure(for: self.session, device: self.device, audio: self.initialConfiguration.audio)
|
||||||
@ -322,6 +330,22 @@ public final class Camera {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var position: Signal<Camera.Position, NoError> {
|
||||||
|
return Signal { subscriber in
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
self.queue.async {
|
||||||
|
if let context = self.contextRef?.takeUnretainedValue() {
|
||||||
|
disposable.set(context.position.start(next: { flashMode in
|
||||||
|
subscriber.putNext(flashMode)
|
||||||
|
}, completed: {
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return disposable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func togglePosition() {
|
public func togglePosition() {
|
||||||
self.queue.async {
|
self.queue.async {
|
||||||
if let context = self.contextRef?.takeUnretainedValue() {
|
if let context = self.contextRef?.takeUnretainedValue() {
|
||||||
|
|||||||
@ -1255,6 +1255,10 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hasStoryCameraTransition = self.controller?.hasStoryCameraTransition ?? false
|
||||||
|
if hasStoryCameraTransition {
|
||||||
|
self.controller?.storyCameraPanGestureEnded(transitionFraction: translation.x / layout.size.width, velocity: velocity.x)
|
||||||
|
} else {
|
||||||
var applyNodeAsCurrent: ChatListFilterTabEntryId?
|
var applyNodeAsCurrent: ChatListFilterTabEntryId?
|
||||||
|
|
||||||
if let directionIsToRight = directionIsToRight {
|
if let directionIsToRight = directionIsToRight {
|
||||||
@ -1282,13 +1286,12 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.controller?.storyCameraPanGestureEnded(transitionFraction: translation.x / layout.size.width, velocity: velocity.x)
|
|
||||||
|
|
||||||
if let switchToId = applyNodeAsCurrent, let itemNode = self.itemNodes[switchToId] {
|
if let switchToId = applyNodeAsCurrent, let itemNode = self.itemNodes[switchToId] {
|
||||||
self.applyItemNodeAsCurrent(id: switchToId, itemNode: itemNode)
|
self.applyItemNodeAsCurrent(id: switchToId, itemNode: itemNode)
|
||||||
}
|
}
|
||||||
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false)
|
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ public final class MultilineTextComponent: Component {
|
|||||||
public let cutout: TextNodeCutout?
|
public let cutout: TextNodeCutout?
|
||||||
public let insets: UIEdgeInsets
|
public let insets: UIEdgeInsets
|
||||||
public let textShadowColor: UIColor?
|
public let textShadowColor: UIColor?
|
||||||
|
public let textShadowBlur: CGFloat?
|
||||||
public let textStroke: (UIColor, CGFloat)?
|
public let textStroke: (UIColor, CGFloat)?
|
||||||
public let highlightColor: UIColor?
|
public let highlightColor: UIColor?
|
||||||
public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)?
|
public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)?
|
||||||
@ -35,6 +36,7 @@ public final class MultilineTextComponent: Component {
|
|||||||
cutout: TextNodeCutout? = nil,
|
cutout: TextNodeCutout? = nil,
|
||||||
insets: UIEdgeInsets = UIEdgeInsets(),
|
insets: UIEdgeInsets = UIEdgeInsets(),
|
||||||
textShadowColor: UIColor? = nil,
|
textShadowColor: UIColor? = nil,
|
||||||
|
textShadowBlur: CGFloat? = nil,
|
||||||
textStroke: (UIColor, CGFloat)? = nil,
|
textStroke: (UIColor, CGFloat)? = nil,
|
||||||
highlightColor: UIColor? = nil,
|
highlightColor: UIColor? = nil,
|
||||||
highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil,
|
highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil,
|
||||||
@ -50,6 +52,7 @@ public final class MultilineTextComponent: Component {
|
|||||||
self.cutout = cutout
|
self.cutout = cutout
|
||||||
self.insets = insets
|
self.insets = insets
|
||||||
self.textShadowColor = textShadowColor
|
self.textShadowColor = textShadowColor
|
||||||
|
self.textShadowBlur = textShadowBlur
|
||||||
self.textStroke = textStroke
|
self.textStroke = textStroke
|
||||||
self.highlightColor = highlightColor
|
self.highlightColor = highlightColor
|
||||||
self.highlightAction = highlightAction
|
self.highlightAction = highlightAction
|
||||||
@ -90,6 +93,9 @@ public final class MultilineTextComponent: Component {
|
|||||||
} else if (lhs.textShadowColor != nil) != (rhs.textShadowColor != nil) {
|
} else if (lhs.textShadowColor != nil) != (rhs.textShadowColor != nil) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.textShadowBlur != rhs.textShadowBlur {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if let lhsTextStroke = lhs.textStroke, let rhsTextStroke = rhs.textStroke {
|
if let lhsTextStroke = lhs.textStroke, let rhsTextStroke = rhs.textStroke {
|
||||||
if !lhsTextStroke.0.isEqual(rhsTextStroke.0) {
|
if !lhsTextStroke.0.isEqual(rhsTextStroke.0) {
|
||||||
@ -134,6 +140,7 @@ public final class MultilineTextComponent: Component {
|
|||||||
self.cutout = component.cutout
|
self.cutout = component.cutout
|
||||||
self.insets = component.insets
|
self.insets = component.insets
|
||||||
self.textShadowColor = component.textShadowColor
|
self.textShadowColor = component.textShadowColor
|
||||||
|
self.textShadowBlur = component.textShadowBlur
|
||||||
self.textStroke = component.textStroke
|
self.textStroke = component.textStroke
|
||||||
self.linkHighlightColor = component.highlightColor
|
self.linkHighlightColor = component.highlightColor
|
||||||
self.highlightAttributeAction = component.highlightAction
|
self.highlightAttributeAction = component.highlightAction
|
||||||
|
|||||||
@ -22,6 +22,7 @@ public class ImmediateTextNode: TextNode {
|
|||||||
public var lineSpacing: CGFloat = 0.0
|
public var lineSpacing: CGFloat = 0.0
|
||||||
public var insets: UIEdgeInsets = UIEdgeInsets()
|
public var insets: UIEdgeInsets = UIEdgeInsets()
|
||||||
public var textShadowColor: UIColor?
|
public var textShadowColor: UIColor?
|
||||||
|
public var textShadowBlur: CGFloat?
|
||||||
public var textStroke: (UIColor, CGFloat)?
|
public var textStroke: (UIColor, CGFloat)?
|
||||||
public var cutout: TextNodeCutout?
|
public var cutout: TextNodeCutout?
|
||||||
public var displaySpoilers = false
|
public var displaySpoilers = false
|
||||||
@ -97,7 +98,7 @@ public class ImmediateTextNode: TextNode {
|
|||||||
self.constrainedSize = constrainedSize
|
self.constrainedSize = constrainedSize
|
||||||
|
|
||||||
let makeLayout = TextNode.asyncLayout(self)
|
let makeLayout = TextNode.asyncLayout(self)
|
||||||
let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, verticalAlignment: self.verticalAlignment, lineSpacing: self.lineSpacing, cutout: self.cutout, insets: self.insets, textShadowColor: self.textShadowColor, textStroke: self.textStroke, displaySpoilers: self.displaySpoilers))
|
let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, verticalAlignment: self.verticalAlignment, lineSpacing: self.lineSpacing, cutout: self.cutout, insets: self.insets, textShadowColor: self.textShadowColor, textShadowBlur: self.textShadowBlur, textStroke: self.textStroke, displaySpoilers: self.displaySpoilers))
|
||||||
let _ = apply()
|
let _ = apply()
|
||||||
if layout.numberOfLines > 1 {
|
if layout.numberOfLines > 1 {
|
||||||
self.trailingLineWidth = layout.trailingLineWidth
|
self.trailingLineWidth = layout.trailingLineWidth
|
||||||
@ -241,6 +242,7 @@ open class ImmediateTextView: TextView {
|
|||||||
public var lineSpacing: CGFloat = 0.0
|
public var lineSpacing: CGFloat = 0.0
|
||||||
public var insets: UIEdgeInsets = UIEdgeInsets()
|
public var insets: UIEdgeInsets = UIEdgeInsets()
|
||||||
public var textShadowColor: UIColor?
|
public var textShadowColor: UIColor?
|
||||||
|
public var textShadowBlur: CGFloat?
|
||||||
public var textStroke: (UIColor, CGFloat)?
|
public var textStroke: (UIColor, CGFloat)?
|
||||||
public var cutout: TextNodeCutout?
|
public var cutout: TextNodeCutout?
|
||||||
public var displaySpoilers = false
|
public var displaySpoilers = false
|
||||||
@ -293,7 +295,7 @@ open class ImmediateTextView: TextView {
|
|||||||
self.constrainedSize = constrainedSize
|
self.constrainedSize = constrainedSize
|
||||||
|
|
||||||
let makeLayout = TextView.asyncLayout(self)
|
let makeLayout = TextView.asyncLayout(self)
|
||||||
let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, verticalAlignment: self.verticalAlignment, lineSpacing: self.lineSpacing, cutout: self.cutout, insets: self.insets, textShadowColor: self.textShadowColor, textStroke: self.textStroke, displaySpoilers: self.displaySpoilers))
|
let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, verticalAlignment: self.verticalAlignment, lineSpacing: self.lineSpacing, cutout: self.cutout, insets: self.insets, textShadowColor: self.textShadowColor, textShadowBlur: self.textShadowBlur, textStroke: self.textStroke, displaySpoilers: self.displaySpoilers))
|
||||||
let _ = apply()
|
let _ = apply()
|
||||||
if layout.numberOfLines > 1 {
|
if layout.numberOfLines > 1 {
|
||||||
self.trailingLineWidth = layout.trailingLineWidth
|
self.trailingLineWidth = layout.trailingLineWidth
|
||||||
|
|||||||
@ -145,6 +145,7 @@ public final class TextNodeLayoutArguments {
|
|||||||
public let insets: UIEdgeInsets
|
public let insets: UIEdgeInsets
|
||||||
public let lineColor: UIColor?
|
public let lineColor: UIColor?
|
||||||
public let textShadowColor: UIColor?
|
public let textShadowColor: UIColor?
|
||||||
|
public let textShadowBlur: CGFloat?
|
||||||
public let textStroke: (UIColor, CGFloat)?
|
public let textStroke: (UIColor, CGFloat)?
|
||||||
public let displaySpoilers: Bool
|
public let displaySpoilers: Bool
|
||||||
public let displayEmbeddedItemsUnderSpoilers: Bool
|
public let displayEmbeddedItemsUnderSpoilers: Bool
|
||||||
@ -163,6 +164,7 @@ public final class TextNodeLayoutArguments {
|
|||||||
insets: UIEdgeInsets = UIEdgeInsets(),
|
insets: UIEdgeInsets = UIEdgeInsets(),
|
||||||
lineColor: UIColor? = nil,
|
lineColor: UIColor? = nil,
|
||||||
textShadowColor: UIColor? = nil,
|
textShadowColor: UIColor? = nil,
|
||||||
|
textShadowBlur: CGFloat? = nil,
|
||||||
textStroke: (UIColor, CGFloat)? = nil,
|
textStroke: (UIColor, CGFloat)? = nil,
|
||||||
displaySpoilers: Bool = false,
|
displaySpoilers: Bool = false,
|
||||||
displayEmbeddedItemsUnderSpoilers: Bool = false
|
displayEmbeddedItemsUnderSpoilers: Bool = false
|
||||||
@ -180,6 +182,7 @@ public final class TextNodeLayoutArguments {
|
|||||||
self.insets = insets
|
self.insets = insets
|
||||||
self.lineColor = lineColor
|
self.lineColor = lineColor
|
||||||
self.textShadowColor = textShadowColor
|
self.textShadowColor = textShadowColor
|
||||||
|
self.textShadowBlur = textShadowBlur
|
||||||
self.textStroke = textStroke
|
self.textStroke = textStroke
|
||||||
self.displaySpoilers = displaySpoilers
|
self.displaySpoilers = displaySpoilers
|
||||||
self.displayEmbeddedItemsUnderSpoilers = displayEmbeddedItemsUnderSpoilers
|
self.displayEmbeddedItemsUnderSpoilers = displayEmbeddedItemsUnderSpoilers
|
||||||
@ -200,6 +203,7 @@ public final class TextNodeLayoutArguments {
|
|||||||
insets: self.insets,
|
insets: self.insets,
|
||||||
lineColor: self.lineColor,
|
lineColor: self.lineColor,
|
||||||
textShadowColor: self.textShadowColor,
|
textShadowColor: self.textShadowColor,
|
||||||
|
textShadowBlur: self.textShadowBlur,
|
||||||
textStroke: self.textStroke,
|
textStroke: self.textStroke,
|
||||||
displaySpoilers: self.displaySpoilers,
|
displaySpoilers: self.displaySpoilers,
|
||||||
displayEmbeddedItemsUnderSpoilers: self.displayEmbeddedItemsUnderSpoilers
|
displayEmbeddedItemsUnderSpoilers: self.displayEmbeddedItemsUnderSpoilers
|
||||||
@ -257,6 +261,7 @@ public final class TextNodeLayout: NSObject {
|
|||||||
fileprivate let blockQuotes: [TextNodeBlockQuote]
|
fileprivate let blockQuotes: [TextNodeBlockQuote]
|
||||||
fileprivate let lineColor: UIColor?
|
fileprivate let lineColor: UIColor?
|
||||||
fileprivate let textShadowColor: UIColor?
|
fileprivate let textShadowColor: UIColor?
|
||||||
|
fileprivate let textShadowBlur: CGFloat?
|
||||||
fileprivate let textStroke: (UIColor, CGFloat)?
|
fileprivate let textStroke: (UIColor, CGFloat)?
|
||||||
fileprivate let displaySpoilers: Bool
|
fileprivate let displaySpoilers: Bool
|
||||||
public let hasRTL: Bool
|
public let hasRTL: Bool
|
||||||
@ -264,7 +269,7 @@ public final class TextNodeLayout: NSObject {
|
|||||||
public let spoilerWords: [(NSRange, CGRect)]
|
public let spoilerWords: [(NSRange, CGRect)]
|
||||||
public let embeddedItems: [TextNodeLayout.EmbeddedItem]
|
public let embeddedItems: [TextNodeLayout.EmbeddedItem]
|
||||||
|
|
||||||
fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, explicitAlignment: NSTextAlignment, resolvedAlignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, rawTextSize: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?, displaySpoilers: Bool) {
|
fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, explicitAlignment: NSTextAlignment, resolvedAlignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, rawTextSize: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?, textShadowBlur: CGFloat?, textStroke: (UIColor, CGFloat)?, displaySpoilers: Bool) {
|
||||||
self.attributedString = attributedString
|
self.attributedString = attributedString
|
||||||
self.maximumNumberOfLines = maximumNumberOfLines
|
self.maximumNumberOfLines = maximumNumberOfLines
|
||||||
self.truncationType = truncationType
|
self.truncationType = truncationType
|
||||||
@ -284,6 +289,7 @@ public final class TextNodeLayout: NSObject {
|
|||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
self.lineColor = lineColor
|
self.lineColor = lineColor
|
||||||
self.textShadowColor = textShadowColor
|
self.textShadowColor = textShadowColor
|
||||||
|
self.textShadowBlur = textShadowBlur
|
||||||
self.textStroke = textStroke
|
self.textStroke = textStroke
|
||||||
self.displaySpoilers = displaySpoilers
|
self.displaySpoilers = displaySpoilers
|
||||||
var hasRTL = false
|
var hasRTL = false
|
||||||
@ -992,7 +998,7 @@ open class TextNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?, displaySpoilers: Bool, displayEmbeddedItemsUnderSpoilers: Bool) -> TextNodeLayout {
|
static func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?, textShadowBlur: CGFloat?, textStroke: (UIColor, CGFloat)?, displaySpoilers: Bool, displayEmbeddedItemsUnderSpoilers: Bool) -> TextNodeLayout {
|
||||||
if let attributedString = attributedString {
|
if let attributedString = attributedString {
|
||||||
let stringLength = attributedString.length
|
let stringLength = attributedString.length
|
||||||
|
|
||||||
@ -1030,7 +1036,7 @@ open class TextNode: ASDisplayNode {
|
|||||||
var maybeTypesetter: CTTypesetter?
|
var maybeTypesetter: CTTypesetter?
|
||||||
maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString)
|
maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString)
|
||||||
if maybeTypesetter == nil {
|
if maybeTypesetter == nil {
|
||||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: resolvedAlignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke, displaySpoilers: displaySpoilers)
|
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: resolvedAlignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displaySpoilers: displaySpoilers)
|
||||||
}
|
}
|
||||||
|
|
||||||
let typesetter = maybeTypesetter!
|
let typesetter = maybeTypesetter!
|
||||||
@ -1399,9 +1405,9 @@ open class TextNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: resolvedAlignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), rawTextSize: CGSize(width: ceil(rawLayoutSize.width) + insets.left + insets.right, height: ceil(rawLayoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke, displaySpoilers: displaySpoilers)
|
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: resolvedAlignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), rawTextSize: CGSize(width: ceil(rawLayoutSize.width) + insets.left + insets.right, height: ceil(rawLayoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displaySpoilers: displaySpoilers)
|
||||||
} else {
|
} else {
|
||||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: alignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke, displaySpoilers: displaySpoilers)
|
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: alignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displaySpoilers: displaySpoilers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1441,7 +1447,7 @@ open class TextNode: ASDisplayNode {
|
|||||||
|
|
||||||
if let textShadowColor = layout.textShadowColor {
|
if let textShadowColor = layout.textShadowColor {
|
||||||
context.setTextDrawingMode(.fill)
|
context.setTextDrawingMode(.fill)
|
||||||
context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 0.0, color: textShadowColor.cgColor)
|
context.setShadow(offset: layout.textShadowBlur != nil ? .zero : CGSize(width: 0.0, height: 1.0), blur: layout.textShadowBlur ?? 0.0, color: textShadowColor.cgColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (textStrokeColor, textStrokeWidth) = layout.textStroke {
|
if let (textStrokeColor, textStrokeWidth) = layout.textStroke {
|
||||||
@ -1641,11 +1647,11 @@ open class TextNode: ASDisplayNode {
|
|||||||
if stringMatch {
|
if stringMatch {
|
||||||
layout = existingLayout
|
layout = existingLayout
|
||||||
} else {
|
} else {
|
||||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers)
|
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers)
|
||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers)
|
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers)
|
||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1721,7 +1727,7 @@ open class TextView: UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?, displaySpoilers: Bool) -> TextNodeLayout {
|
private class func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?, textShadowBlur: CGFloat?, textStroke: (UIColor, CGFloat)?, displaySpoilers: Bool) -> TextNodeLayout {
|
||||||
if let attributedString = attributedString {
|
if let attributedString = attributedString {
|
||||||
let stringLength = attributedString.length
|
let stringLength = attributedString.length
|
||||||
|
|
||||||
@ -1759,7 +1765,7 @@ open class TextView: UIView {
|
|||||||
var maybeTypesetter: CTTypesetter?
|
var maybeTypesetter: CTTypesetter?
|
||||||
maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString)
|
maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString)
|
||||||
if maybeTypesetter == nil {
|
if maybeTypesetter == nil {
|
||||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: resolvedAlignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke, displaySpoilers: displaySpoilers)
|
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: resolvedAlignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displaySpoilers: displaySpoilers)
|
||||||
}
|
}
|
||||||
|
|
||||||
let typesetter = maybeTypesetter!
|
let typesetter = maybeTypesetter!
|
||||||
@ -2090,9 +2096,9 @@ open class TextView: UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: resolvedAlignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), rawTextSize: CGSize(width: ceil(rawLayoutSize.width) + insets.left + insets.right, height: ceil(rawLayoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke, displaySpoilers: displaySpoilers)
|
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: resolvedAlignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), rawTextSize: CGSize(width: ceil(rawLayoutSize.width) + insets.left + insets.right, height: ceil(rawLayoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displaySpoilers: displaySpoilers)
|
||||||
} else {
|
} else {
|
||||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: alignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke, displaySpoilers: displaySpoilers)
|
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: alignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displaySpoilers: displaySpoilers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2123,7 +2129,7 @@ open class TextView: UIView {
|
|||||||
|
|
||||||
if let textShadowColor = layout.textShadowColor {
|
if let textShadowColor = layout.textShadowColor {
|
||||||
context.setTextDrawingMode(.fill)
|
context.setTextDrawingMode(.fill)
|
||||||
context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 0.0, color: textShadowColor.cgColor)
|
context.setShadow(offset: layout.textShadowBlur != nil ? .zero : CGSize(width: 0.0, height: 1.0), blur: layout.textShadowBlur ?? 0.0, color: textShadowColor.cgColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (textStrokeColor, textStrokeWidth) = layout.textStroke {
|
if let (textStrokeColor, textStrokeWidth) = layout.textStroke {
|
||||||
@ -2286,11 +2292,11 @@ open class TextView: UIView {
|
|||||||
if stringMatch {
|
if stringMatch {
|
||||||
layout = existingLayout
|
layout = existingLayout
|
||||||
} else {
|
} else {
|
||||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers)
|
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers)
|
||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers)
|
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers)
|
||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -496,6 +496,15 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getView(where f: (DrawingEntityView) -> Bool) -> DrawingEntityView? {
|
||||||
|
for case let view as DrawingEntityView in self.subviews {
|
||||||
|
if f(view) {
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
public func play() {
|
public func play() {
|
||||||
for case let view as DrawingEntityView in self.subviews {
|
for case let view as DrawingEntityView in self.subviews {
|
||||||
view.play()
|
view.play()
|
||||||
|
|||||||
@ -12,18 +12,18 @@ public final class DrawingMediaEntityView: DrawingEntityView, DrawingEntityMedia
|
|||||||
return self.entity as! DrawingMediaEntity
|
return self.entity as! DrawingMediaEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
var started: ((Double) -> Void)?
|
|
||||||
|
|
||||||
private var currentSize: CGSize?
|
private var currentSize: CGSize?
|
||||||
private var isVisible = true
|
private var isVisible = true
|
||||||
private var isPlaying = false
|
private var isPlaying = false
|
||||||
|
|
||||||
public var previewView: MediaEditorPreviewView? {
|
public weak var previewView: MediaEditorPreviewView? {
|
||||||
didSet {
|
didSet {
|
||||||
if let previewView = self.previewView {
|
if let previewView = self.previewView {
|
||||||
previewView.isUserInteractionEnabled = false
|
previewView.isUserInteractionEnabled = false
|
||||||
previewView.layer.allowsEdgeAntialiasing = true
|
previewView.layer.allowsEdgeAntialiasing = true
|
||||||
self.addSubview(previewView)
|
self.addSubview(previewView)
|
||||||
|
} else {
|
||||||
|
oldValue?.removeFromSuperview()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,7 +37,10 @@ public final class DrawingMediaEntityView: DrawingEntityView, DrawingEntityMedia
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
if let previewView = self.previewView {
|
||||||
|
previewView.removeFromSuperview()
|
||||||
|
self.previewView = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func play() {
|
override func play() {
|
||||||
|
|||||||
@ -494,6 +494,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
let existingStickerPickerInputData: Promise<StickerPickerInputData>?
|
let existingStickerPickerInputData: Promise<StickerPickerInputData>?
|
||||||
let isVideo: Bool
|
let isVideo: Bool
|
||||||
let isAvatar: Bool
|
let isAvatar: Bool
|
||||||
|
let isInteractingWithEntities: Bool
|
||||||
let present: (ViewController) -> Void
|
let present: (ViewController) -> Void
|
||||||
let updateState: ActionSlot<DrawingView.NavigationState>
|
let updateState: ActionSlot<DrawingView.NavigationState>
|
||||||
let updateColor: ActionSlot<DrawingColor>
|
let updateColor: ActionSlot<DrawingColor>
|
||||||
@ -528,6 +529,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
existingStickerPickerInputData: Promise<StickerPickerInputData>?,
|
existingStickerPickerInputData: Promise<StickerPickerInputData>?,
|
||||||
isVideo: Bool,
|
isVideo: Bool,
|
||||||
isAvatar: Bool,
|
isAvatar: Bool,
|
||||||
|
isInteractingWithEntities: Bool,
|
||||||
present: @escaping (ViewController) -> Void,
|
present: @escaping (ViewController) -> Void,
|
||||||
updateState: ActionSlot<DrawingView.NavigationState>,
|
updateState: ActionSlot<DrawingView.NavigationState>,
|
||||||
updateColor: ActionSlot<DrawingColor>,
|
updateColor: ActionSlot<DrawingColor>,
|
||||||
@ -560,6 +562,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
self.existingStickerPickerInputData = existingStickerPickerInputData
|
self.existingStickerPickerInputData = existingStickerPickerInputData
|
||||||
self.isVideo = isVideo
|
self.isVideo = isVideo
|
||||||
self.isAvatar = isAvatar
|
self.isAvatar = isAvatar
|
||||||
|
self.isInteractingWithEntities = isInteractingWithEntities
|
||||||
self.present = present
|
self.present = present
|
||||||
self.updateState = updateState
|
self.updateState = updateState
|
||||||
self.updateColor = updateColor
|
self.updateColor = updateColor
|
||||||
@ -592,9 +595,15 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
if lhs.context !== rhs.context {
|
if lhs.context !== rhs.context {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isVideo != rhs.isVideo {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.isAvatar != rhs.isAvatar {
|
if lhs.isAvatar != rhs.isAvatar {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isInteractingWithEntities != rhs.isInteractingWithEntities {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1138,9 +1147,9 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var controlsVisible = true
|
var controlsAreVisible = true
|
||||||
if state.drawingViewState.isDrawing {
|
if state.drawingViewState.isDrawing || component.isInteractingWithEntities {
|
||||||
controlsVisible = false
|
controlsAreVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
let previewSize = CGSize(width: context.availableSize.width, height: floorToScreenPixels(context.availableSize.width * 1.77778))
|
let previewSize = CGSize(width: context.availableSize.width, height: floorToScreenPixels(context.availableSize.width * 1.77778))
|
||||||
@ -1187,7 +1196,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
context.add(bottomGradient
|
context.add(bottomGradient
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomGradient.size.height / 2.0))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomGradient.size.height / 2.0))
|
||||||
.opacity(controlsVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
if let textEntity = state.selectedEntity as? DrawingTextEntity {
|
if let textEntity = state.selectedEntity as? DrawingTextEntity {
|
||||||
@ -1328,7 +1337,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.opacity(controlsVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
offsetX += delta
|
offsetX += delta
|
||||||
|
|
||||||
@ -1356,7 +1365,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.opacity(controlsVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
offsetX += delta
|
offsetX += delta
|
||||||
|
|
||||||
@ -1384,7 +1393,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.opacity(controlsVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
offsetX += delta
|
offsetX += delta
|
||||||
|
|
||||||
@ -1412,7 +1421,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.opacity(controlsVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
offsetX += delta
|
offsetX += delta
|
||||||
|
|
||||||
@ -1440,7 +1449,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.opacity(controlsVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
offsetX += delta
|
offsetX += delta
|
||||||
delay += 0.025
|
delay += 0.025
|
||||||
@ -1469,7 +1478,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.opacity(controlsVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
offsetX += delta
|
offsetX += delta
|
||||||
|
|
||||||
@ -1497,7 +1506,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.opacity(controlsVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
offsetX += delta
|
offsetX += delta
|
||||||
|
|
||||||
@ -1525,7 +1534,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.opacity(controlsVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
if state.selectedEntity is DrawingStickerEntity || state.selectedEntity is DrawingTextEntity {
|
if state.selectedEntity is DrawingStickerEntity || state.selectedEntity is DrawingTextEntity {
|
||||||
@ -1569,7 +1578,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.opacity(controlsVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1744,7 +1753,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
context.add(textSize
|
context.add(textSize
|
||||||
.position(CGPoint(x: textSize.size.width / 2.0, y: topInset + (context.availableSize.height - topInset - bottomInset) / 2.0))
|
.position(CGPoint(x: textSize.size.width / 2.0, y: topInset + (context.availableSize.height - topInset - bottomInset) / 2.0))
|
||||||
.opacity(sizeSliderVisible && controlsVisible ? 1.0 : 0.0)
|
.opacity(sizeSliderVisible && controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
let undoButton = undoButton.update(
|
let undoButton = undoButton.update(
|
||||||
@ -1764,7 +1773,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
context.add(undoButton
|
context.add(undoButton
|
||||||
.position(CGPoint(x: environment.safeInsets.left + undoButton.size.width / 2.0 + 2.0, y: topInset))
|
.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 || !controlsVisible ? 0.0 : 1.0)
|
.opacity(isEditingText || !controlsAreVisible ? 0.0 : 1.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1784,7 +1793,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
context.add(redoButton
|
context.add(redoButton
|
||||||
.position(CGPoint(x: environment.safeInsets.left + undoButton.size.width + 2.0 + redoButton.size.width / 2.0, y: topInset))
|
.position(CGPoint(x: environment.safeInsets.left + undoButton.size.width + 2.0 + redoButton.size.width / 2.0, y: topInset))
|
||||||
.scale(state.drawingViewState.canRedo && !isEditingText ? 1.0 : 0.01)
|
.scale(state.drawingViewState.canRedo && !isEditingText ? 1.0 : 0.01)
|
||||||
.opacity(state.drawingViewState.canRedo && !isEditingText && controlsVisible ? 1.0 : 0.0)
|
.opacity(state.drawingViewState.canRedo && !isEditingText && controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
let clearAllButton = clearAllButton.update(
|
let clearAllButton = clearAllButton.update(
|
||||||
@ -1804,7 +1813,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
context.add(clearAllButton
|
context.add(clearAllButton
|
||||||
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - clearAllButton.size.width / 2.0 - 13.0, y: topInset))
|
.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 || !controlsVisible ? 0.0 : 1.0)
|
.opacity(isEditingText || !controlsAreVisible ? 0.0 : 1.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
let textButtonTopInset: CGFloat
|
let textButtonTopInset: CGFloat
|
||||||
@ -1896,7 +1905,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
.position(CGPoint(x: leftEdge + colorButton.size.width / 2.0 + 2.0, y: context.availableSize.height - environment.safeInsets.bottom - colorButton.size.height / 2.0 - 89.0))
|
.position(CGPoint(x: leftEdge + 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))
|
.appear(.default(scale: true))
|
||||||
.disappear(.default(scale: true))
|
.disappear(.default(scale: true))
|
||||||
.opacity(controlsVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
let modeRightInset: CGFloat = 57.0
|
let modeRightInset: CGFloat = 57.0
|
||||||
@ -1946,7 +1955,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
.appear(.default(scale: true))
|
.appear(.default(scale: true))
|
||||||
.disappear(.default(scale: true))
|
.disappear(.default(scale: true))
|
||||||
.cornerRadius(12.0)
|
.cornerRadius(12.0)
|
||||||
.opacity(controlsVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
let doneButton = doneButton.update(
|
let doneButton = doneButton.update(
|
||||||
@ -1984,7 +1993,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
})
|
})
|
||||||
transition.animatePosition(view: view, from: CGPoint(), to: CGPoint(x: 12.0, y: 0.0), additive: true)
|
transition.animatePosition(view: view, from: CGPoint(), to: CGPoint(x: 12.0, y: 0.0), additive: true)
|
||||||
})
|
})
|
||||||
.opacity(controlsVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
let selectedIndex: Int
|
let selectedIndex: Int
|
||||||
@ -2049,7 +2058,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
context.add(modeAndSize
|
context.add(modeAndSize
|
||||||
.position(modeAndSizePosition)
|
.position(modeAndSizePosition)
|
||||||
.opacity(controlsVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
var animatingOut = false
|
var animatingOut = false
|
||||||
@ -2088,7 +2097,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
context.add(backButton
|
context.add(backButton
|
||||||
.position(backButtonPosition)
|
.position(backButtonPosition)
|
||||||
.opacity(controlsVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
return context.availableSize
|
return context.availableSize
|
||||||
@ -2119,6 +2128,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
|
|||||||
fileprivate let insertText: ActionSlot<Void>
|
fileprivate let insertText: ActionSlot<Void>
|
||||||
private let updateEntityView: ActionSlot<(UUID, Bool)>
|
private let updateEntityView: ActionSlot<(UUID, Bool)>
|
||||||
private let endEditingTextEntityView: ActionSlot<(UUID, Bool)>
|
private let endEditingTextEntityView: ActionSlot<(UUID, Bool)>
|
||||||
|
private var isInteractingWithEntities = false
|
||||||
|
|
||||||
private let apply: ActionSlot<Void>
|
private let apply: ActionSlot<Void>
|
||||||
private let dismiss: ActionSlot<Void>
|
private let dismiss: ActionSlot<Void>
|
||||||
@ -2408,6 +2418,12 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
|
|||||||
self.updateColor.invoke(color)
|
self.updateColor.invoke(color)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onInteractionUpdated: { [weak self] isInteracting in
|
||||||
|
if let self {
|
||||||
|
self.isInteractingWithEntities = isInteracting
|
||||||
|
self.requestUpdate(transition: .easeInOut(duration: 0.2))
|
||||||
|
}
|
||||||
|
},
|
||||||
getCurrentImage: { [weak controller] in
|
getCurrentImage: { [weak controller] in
|
||||||
return controller?.getCurrentImage()
|
return controller?.getCurrentImage()
|
||||||
},
|
},
|
||||||
@ -2549,6 +2565,12 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func requestUpdate(transition: Transition = .immediate) {
|
||||||
|
if let (layout, orientation) = self.validLayout {
|
||||||
|
self.containerLayoutUpdated(layout: layout, orientation: orientation, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func containerLayoutUpdated(layout: ContainerViewLayout, orientation: UIInterfaceOrientation?, forceUpdate: Bool = false, animateOut: Bool = false, transition: Transition) {
|
func containerLayoutUpdated(layout: ContainerViewLayout, orientation: UIInterfaceOrientation?, forceUpdate: Bool = false, animateOut: Bool = false, transition: Transition) {
|
||||||
guard let controller = self.controller else {
|
guard let controller = self.controller else {
|
||||||
return
|
return
|
||||||
@ -2594,6 +2616,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
|
|||||||
existingStickerPickerInputData: controller.existingStickerPickerInputData,
|
existingStickerPickerInputData: controller.existingStickerPickerInputData,
|
||||||
isVideo: controller.isVideo,
|
isVideo: controller.isVideo,
|
||||||
isAvatar: controller.isAvatar,
|
isAvatar: controller.isAvatar,
|
||||||
|
isInteractingWithEntities: self.isInteractingWithEntities,
|
||||||
present: { [weak self] c in
|
present: { [weak self] c in
|
||||||
self?.controller?.present(c, in: .window(.root))
|
self?.controller?.present(c, in: .window(.root))
|
||||||
},
|
},
|
||||||
@ -2873,8 +2896,6 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
|
|||||||
|
|
||||||
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
||||||
public func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
|
public func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
|
||||||
//self.chatDisplayNode.updateDropInteraction(isActive: true)
|
|
||||||
|
|
||||||
let operation: UIDropOperation
|
let operation: UIDropOperation
|
||||||
operation = .copy
|
operation = .copy
|
||||||
return UIDropProposal(operation: operation)
|
return UIDropProposal(operation: operation)
|
||||||
@ -2887,8 +2908,6 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let images = imageItems as! [UIImage]
|
let images = imageItems as! [UIImage]
|
||||||
|
|
||||||
//strongSelf.chatDisplayNode.updateDropInteraction(isActive: false)
|
|
||||||
if images.count == 1, let image = images.first, max(image.size.width, image.size.height) > 1.0 {
|
if images.count == 1, let image = images.first, max(image.size.width, image.size.height) > 1.0 {
|
||||||
let entity = DrawingStickerEntity(content: .image(image))
|
let entity = DrawingStickerEntity(content: .image(image))
|
||||||
strongSelf.node.insertEntity.invoke(entity)
|
strongSelf.node.insertEntity.invoke(entity)
|
||||||
@ -2898,12 +2917,10 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
|
|||||||
|
|
||||||
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
||||||
public func dropInteraction(_ interaction: UIDropInteraction, sessionDidExit session: UIDropSession) {
|
public func dropInteraction(_ interaction: UIDropInteraction, sessionDidExit session: UIDropSession) {
|
||||||
//self.chatDisplayNode.updateDropInteraction(isActive: false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
||||||
public func dropInteraction(_ interaction: UIDropInteraction, sessionDidEnd session: UIDropSession) {
|
public func dropInteraction(_ interaction: UIDropInteraction, sessionDidEnd session: UIDropSession) {
|
||||||
//self.chatDisplayNode.updateDropInteraction(isActive: false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2917,6 +2934,7 @@ public final class DrawingToolsInteraction {
|
|||||||
private let updateVideoPlayback: (Bool) -> Void
|
private let updateVideoPlayback: (Bool) -> Void
|
||||||
private let updateColor: (DrawingColor) -> Void
|
private let updateColor: (DrawingColor) -> Void
|
||||||
|
|
||||||
|
private let onInteractionUpdated: (Bool) -> Void
|
||||||
private let getCurrentImage: () -> UIImage?
|
private let getCurrentImage: () -> UIImage?
|
||||||
private let getControllerNode: () -> ASDisplayNode?
|
private let getControllerNode: () -> ASDisplayNode?
|
||||||
private let present: (ViewController, PresentationContextType, Any?) -> Void
|
private let present: (ViewController, PresentationContextType, Any?) -> Void
|
||||||
@ -2942,6 +2960,7 @@ public final class DrawingToolsInteraction {
|
|||||||
updateSelectedEntity: @escaping (DrawingEntity?) -> Void,
|
updateSelectedEntity: @escaping (DrawingEntity?) -> Void,
|
||||||
updateVideoPlayback: @escaping (Bool) -> Void,
|
updateVideoPlayback: @escaping (Bool) -> Void,
|
||||||
updateColor: @escaping (DrawingColor) -> Void,
|
updateColor: @escaping (DrawingColor) -> Void,
|
||||||
|
onInteractionUpdated: @escaping (Bool) -> Void,
|
||||||
getCurrentImage: @escaping () -> UIImage?,
|
getCurrentImage: @escaping () -> UIImage?,
|
||||||
getControllerNode: @escaping () -> ASDisplayNode?,
|
getControllerNode: @escaping () -> ASDisplayNode?,
|
||||||
present: @escaping (ViewController, PresentationContextType, Any?) -> Void,
|
present: @escaping (ViewController, PresentationContextType, Any?) -> Void,
|
||||||
@ -2955,6 +2974,7 @@ public final class DrawingToolsInteraction {
|
|||||||
self.updateSelectedEntity = updateSelectedEntity
|
self.updateSelectedEntity = updateSelectedEntity
|
||||||
self.updateVideoPlayback = updateVideoPlayback
|
self.updateVideoPlayback = updateVideoPlayback
|
||||||
self.updateColor = updateColor
|
self.updateColor = updateColor
|
||||||
|
self.onInteractionUpdated = onInteractionUpdated
|
||||||
self.getCurrentImage = getCurrentImage
|
self.getCurrentImage = getCurrentImage
|
||||||
self.getControllerNode = getControllerNode
|
self.getControllerNode = getControllerNode
|
||||||
self.present = present
|
self.present = present
|
||||||
@ -2975,7 +2995,11 @@ public final class DrawingToolsInteraction {
|
|||||||
self.updateSelectedEntity(entity)
|
self.updateSelectedEntity(entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.entitiesView.onInteractionUpdated = { [weak self] isInteracting in
|
||||||
|
if let self {
|
||||||
|
self.onInteractionUpdated(isInteracting)
|
||||||
|
}
|
||||||
|
}
|
||||||
self.entitiesView.requestedMenuForEntityView = { [weak self] entityView, isTopmost in
|
self.entitiesView.requestedMenuForEntityView = { [weak self] entityView, isTopmost in
|
||||||
guard let self, let node = self.getControllerNode() else {
|
guard let self, let node = self.getControllerNode() else {
|
||||||
return
|
return
|
||||||
@ -3000,6 +3024,12 @@ public final class DrawingToolsInteraction {
|
|||||||
self.entitiesView.selectEntity(entityView.entity)
|
self.entitiesView.selectEntity(entityView.entity)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
} else if entityView is DrawingStickerEntityView || entityView is DrawingBubbleEntityView {
|
||||||
|
actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Paint_Flip, accessibilityLabel: presentationData.strings.Paint_Flip), action: { [weak self] in
|
||||||
|
if let self {
|
||||||
|
self.flipSelectedEntity()
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
if !isTopmost {
|
if !isTopmost {
|
||||||
actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Paint_MoveForward, accessibilityLabel: presentationData.strings.Paint_MoveForward), action: { [weak self, weak entityView] in
|
actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Paint_MoveForward, accessibilityLabel: presentationData.strings.Paint_MoveForward), action: { [weak self, weak entityView] in
|
||||||
@ -3072,6 +3102,21 @@ public final class DrawingToolsInteraction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func flipSelectedEntity() {
|
||||||
|
if let selectedEntityView = self.entitiesView.selectedEntityView {
|
||||||
|
let selectedEntity = selectedEntityView.entity
|
||||||
|
if let entity = selectedEntity as? DrawingBubbleEntity {
|
||||||
|
var updatedTailPosition = entity.tailPosition
|
||||||
|
updatedTailPosition.x = 1.0 - updatedTailPosition.x
|
||||||
|
entity.tailPosition = updatedTailPosition
|
||||||
|
selectedEntityView.update(animated: false)
|
||||||
|
} else if let entity = selectedEntity as? DrawingStickerEntity {
|
||||||
|
entity.mirrored = !entity.mirrored
|
||||||
|
selectedEntityView.update(animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func presentEyedropper(retryLaterForVideo: Bool = true, dismissed: @escaping () -> Void) {
|
func presentEyedropper(retryLaterForVideo: Bool = true, dismissed: @escaping () -> Void) {
|
||||||
// self.entitiesView.pause()
|
// self.entitiesView.pause()
|
||||||
//
|
//
|
||||||
|
|||||||
@ -414,7 +414,7 @@ public class ItemListMultilineTextItemNode: ListViewItemNode {
|
|||||||
if let current = self.linkHighlightingNode {
|
if let current = self.linkHighlightingNode {
|
||||||
linkHighlightingNode = current
|
linkHighlightingNode = current
|
||||||
} else {
|
} else {
|
||||||
linkHighlightingNode = LinkHighlightingNode(color: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.5))
|
linkHighlightingNode = LinkHighlightingNode(color: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.2))
|
||||||
self.linkHighlightingNode = linkHighlightingNode
|
self.linkHighlightingNode = linkHighlightingNode
|
||||||
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
|
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -458,7 +458,7 @@ public class ItemListTextWithLabelItemNode: ListViewItemNode {
|
|||||||
if let current = self.linkHighlightingNode {
|
if let current = self.linkHighlightingNode {
|
||||||
linkHighlightingNode = current
|
linkHighlightingNode = current
|
||||||
} else {
|
} else {
|
||||||
linkHighlightingNode = LinkHighlightingNode(color: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.5))
|
linkHighlightingNode = LinkHighlightingNode(color: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.2))
|
||||||
self.linkHighlightingNode = linkHighlightingNode
|
self.linkHighlightingNode = linkHighlightingNode
|
||||||
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
|
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,7 +68,7 @@ final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode,
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.textNode.linkHighlightColor = theme.list.itemAccentColor.withAlphaComponent(0.5)
|
self.textNode.linkHighlightColor = theme.list.itemAccentColor.withAlphaComponent(0.2)
|
||||||
self.textNode.highlightAttributeAction = { attributes in
|
self.textNode.highlightAttributeAction = { attributes in
|
||||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||||
|
|||||||
@ -1900,7 +1900,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
horizontalAlignment: .natural,
|
horizontalAlignment: .natural,
|
||||||
maximumNumberOfLines: 0,
|
maximumNumberOfLines: 0,
|
||||||
lineSpacing: 0.0,
|
lineSpacing: 0.0,
|
||||||
highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.3),
|
highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.2),
|
||||||
highlightAction: { attributes in
|
highlightAction: { attributes in
|
||||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||||
|
|||||||
@ -119,7 +119,7 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode {
|
|||||||
self.leftActionNode.addTarget(self, action: #selector(self.leftActionPressed), forControlEvents: .touchUpInside)
|
self.leftActionNode.addTarget(self, action: #selector(self.leftActionPressed), forControlEvents: .touchUpInside)
|
||||||
self.rightActionNode.addTarget(self, action: #selector(self.rightActionPressed), forControlEvents: .touchUpInside)
|
self.rightActionNode.addTarget(self, action: #selector(self.rightActionPressed), forControlEvents: .touchUpInside)
|
||||||
|
|
||||||
self.contentTextNode.linkHighlightColor = self.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.5)
|
self.contentTextNode.linkHighlightColor = self.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.2)
|
||||||
self.contentTextNode.highlightAttributeAction = { attributes in
|
self.contentTextNode.highlightAttributeAction = { attributes in
|
||||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||||
|
|||||||
@ -43,6 +43,7 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
struct WebPage: Equatable, Codable {
|
struct WebPage: Equatable, Codable {
|
||||||
var title: String
|
var title: String
|
||||||
var url: String
|
var url: String
|
||||||
|
var photo: TelegramMediaImage?
|
||||||
}
|
}
|
||||||
|
|
||||||
case peer(PeerId)
|
case peer(PeerId)
|
||||||
@ -270,7 +271,7 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
accessHash: nil,
|
accessHash: nil,
|
||||||
title: webPage.title,
|
title: webPage.title,
|
||||||
username: nil,
|
username: nil,
|
||||||
photo: [],
|
photo: webPage.photo?.representations ?? [],
|
||||||
creationDate: 0,
|
creationDate: 0,
|
||||||
version: 0,
|
version: 0,
|
||||||
participationStatus: .left,
|
participationStatus: .left,
|
||||||
@ -514,9 +515,10 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
if let fromId = fromId {
|
if let fromId = fromId {
|
||||||
target = .peer(fromId.peerId)
|
target = .peer(fromId.peerId)
|
||||||
} else if let webPage = webPage {
|
} else if let webPage = webPage {
|
||||||
if case let .sponsoredWebPage(_, url, siteName, photo) = webPage {
|
switch webPage {
|
||||||
let _ = photo
|
case let .sponsoredWebPage(_, url, siteName, photo):
|
||||||
target = .webPage(CachedMessage.Target.WebPage(title: siteName, url: url))
|
let photo = photo.flatMap { telegramMediaImageFromApiPhoto($0) }
|
||||||
|
target = .webPage(CachedMessage.Target.WebPage(title: siteName, url: url, photo: photo))
|
||||||
}
|
}
|
||||||
} else if let chatInvite = chatInvite, let chatInviteHash = chatInviteHash {
|
} else if let chatInvite = chatInvite, let chatInviteHash = chatInviteHash {
|
||||||
switch chatInvite {
|
switch chatInvite {
|
||||||
|
|||||||
@ -33,25 +33,30 @@ private struct CameraState {
|
|||||||
case handsFree
|
case handsFree
|
||||||
}
|
}
|
||||||
let mode: CameraMode
|
let mode: CameraMode
|
||||||
|
let position: Camera.Position
|
||||||
let flashMode: Camera.FlashMode
|
let flashMode: Camera.FlashMode
|
||||||
let flashModeDidChange: Bool
|
let flashModeDidChange: Bool
|
||||||
let recording: Recording
|
let recording: Recording
|
||||||
let duration: Double
|
let duration: Double
|
||||||
|
|
||||||
func updatedMode(_ mode: CameraMode) -> CameraState {
|
func updatedMode(_ mode: CameraMode) -> CameraState {
|
||||||
return CameraState(mode: mode, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, recording: self.recording, duration: self.duration)
|
return CameraState(mode: mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, recording: self.recording, duration: self.duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updatedPosition(_ mode: Camera.Position) -> CameraState {
|
||||||
|
return CameraState(mode: self.mode, position: position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, recording: self.recording, duration: self.duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatedFlashMode(_ flashMode: Camera.FlashMode) -> CameraState {
|
func updatedFlashMode(_ flashMode: Camera.FlashMode) -> CameraState {
|
||||||
return CameraState(mode: self.mode, flashMode: flashMode, flashModeDidChange: self.flashMode != flashMode, recording: self.recording, duration: self.duration)
|
return CameraState(mode: self.mode, position: self.position, flashMode: flashMode, flashModeDidChange: self.flashMode != flashMode, recording: self.recording, duration: self.duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatedRecording(_ recording: Recording) -> CameraState {
|
func updatedRecording(_ recording: Recording) -> CameraState {
|
||||||
return CameraState(mode: self.mode, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, recording: recording, duration: self.duration)
|
return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, recording: recording, duration: self.duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatedDuration(_ duration: Double) -> CameraState {
|
func updatedDuration(_ duration: Double) -> CameraState {
|
||||||
return CameraState(mode: self.mode, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, recording: self.recording, duration: duration)
|
return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, recording: self.recording, duration: duration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +146,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
fileprivate var lastGalleryAsset: PHAsset?
|
fileprivate var lastGalleryAsset: PHAsset?
|
||||||
private var lastGalleryAssetsDisposable: Disposable?
|
private var lastGalleryAssetsDisposable: Disposable?
|
||||||
|
|
||||||
var cameraState = CameraState(mode: .photo, flashMode: .off, flashModeDidChange: false, recording: .none, duration: 0.0)
|
var cameraState = CameraState(mode: .photo, position: .unspecified, flashMode: .off, flashModeDidChange: false, recording: .none, duration: 0.0)
|
||||||
var swipeHint: CaptureControlsComponent.SwipeHint = .none
|
var swipeHint: CaptureControlsComponent.SwipeHint = .none
|
||||||
|
|
||||||
private let hapticFeedback = HapticFeedback()
|
private let hapticFeedback = HapticFeedback()
|
||||||
@ -154,13 +159,18 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.cameraStateDisposable = (camera.flashMode
|
self.cameraStateDisposable = combineLatest(queue: Queue.mainQueue(), camera.flashMode, camera.position)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] flashMode in
|
.start(next: { [weak self] flashMode, position in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.cameraState = self.cameraState.updatedFlashMode(flashMode)
|
let previousState = self.cameraState
|
||||||
|
self.cameraState = self.cameraState.updatedPosition(position).updatedFlashMode(flashMode)
|
||||||
self.updated(transition: .easeInOut(duration: 0.2))
|
self.updated(transition: .easeInOut(duration: 0.2))
|
||||||
|
|
||||||
|
if previousState.position != self.cameraState.position {
|
||||||
|
UserDefaults.standard.set((self.cameraState.position == .front) as NSNumber, forKey: "TelegramStoryCameraUseFrontPosition")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
@ -206,7 +216,13 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
self.hapticFeedback.impact(.light)
|
self.hapticFeedback.impact(.light)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var lastFlipTimestamp: Double?
|
||||||
func togglePosition() {
|
func togglePosition() {
|
||||||
|
let currentTimestamp = CACurrentMediaTime()
|
||||||
|
if let lastFlipTimestamp = self.lastFlipTimestamp, currentTimestamp - lastFlipTimestamp < 2.0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.lastFlipTimestamp = currentTimestamp
|
||||||
self.camera.togglePosition()
|
self.camera.togglePosition()
|
||||||
self.hapticFeedback.impact(.light)
|
self.hapticFeedback.impact(.light)
|
||||||
}
|
}
|
||||||
@ -752,7 +768,13 @@ public class CameraScreen: ViewController {
|
|||||||
self.previewView = CameraPreviewView(test: false)!
|
self.previewView = CameraPreviewView(test: false)!
|
||||||
self.simplePreviewView = nil
|
self.simplePreviewView = nil
|
||||||
}
|
}
|
||||||
self.camera = Camera(configuration: Camera.Configuration(preset: .hd1920x1080, position: .back, audio: true, photo: true, metadata: false, preferredFps: 60.0), previewView: self.simplePreviewView)
|
|
||||||
|
var cameraFrontPosition = false
|
||||||
|
if let useCameraFrontPosition = UserDefaults.standard.object(forKey: "TelegramStoryCameraUseFrontPosition") as? NSNumber, useCameraFrontPosition.boolValue {
|
||||||
|
cameraFrontPosition = true
|
||||||
|
}
|
||||||
|
|
||||||
|
self.camera = Camera(configuration: Camera.Configuration(preset: .hd1920x1080, position: cameraFrontPosition ? .front : .back, audio: true, photo: true, metadata: false, preferredFps: 60.0), previewView: self.simplePreviewView)
|
||||||
if !useSimplePreviewView {
|
if !useSimplePreviewView {
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
#else
|
#else
|
||||||
@ -1079,12 +1101,11 @@ public class CameraScreen: ViewController {
|
|||||||
let maxScale = (layout.size.width - 16.0 * 2.0) / layout.size.width
|
let maxScale = (layout.size.width - 16.0 * 2.0) / layout.size.width
|
||||||
|
|
||||||
let topInset: CGFloat = (layout.statusBarHeight ?? 0.0) + 12.0
|
let topInset: CGFloat = (layout.statusBarHeight ?? 0.0) + 12.0
|
||||||
|
let targetTopInset = ceil((layout.statusBarHeight ?? 0.0) - (layout.size.height - layout.size.height * maxScale) / 2.0)
|
||||||
let maxOffset = (topInset - (layout.size.height - layout.size.height * maxScale) / 2.0)
|
let deltaOffset = (targetTopInset - topInset)
|
||||||
//let maxOffset = -56.0
|
|
||||||
|
|
||||||
let scale = 1.0 * progress + (1.0 - progress) * maxScale
|
let scale = 1.0 * progress + (1.0 - progress) * maxScale
|
||||||
let offset = (1.0 - progress) * maxOffset
|
let offset = (1.0 - progress) * deltaOffset
|
||||||
transition.updateSublayerTransformScaleAndOffset(layer: self.containerView.layer, scale: scale, offset: CGPoint(x: 0.0, y: offset), beginWithCurrentState: true)
|
transition.updateSublayerTransformScaleAndOffset(layer: self.containerView.layer, scale: scale, offset: CGPoint(x: 0.0, y: offset), beginWithCurrentState: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1117,9 +1138,9 @@ public class CameraScreen: ViewController {
|
|||||||
|
|
||||||
if let view = self.componentHost.findTaggedView(tag: flashButtonTag) {
|
if let view = self.componentHost.findTaggedView(tag: flashButtonTag) {
|
||||||
view.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
view.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
||||||
view.layer.shadowRadius = 4.0
|
view.layer.shadowRadius = 3.0
|
||||||
view.layer.shadowColor = UIColor.black.cgColor
|
view.layer.shadowColor = UIColor.black.cgColor
|
||||||
view.layer.shadowOpacity = 0.2
|
view.layer.shadowOpacity = 0.35
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1311,7 +1332,7 @@ public class CameraScreen: ViewController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private weak var galleryController: ViewController?
|
private var galleryController: ViewController?
|
||||||
public func returnFromEditor() {
|
public func returnFromEditor() {
|
||||||
self.node.animateInFromEditor(toGallery: self.galleryController != nil)
|
self.node.animateInFromEditor(toGallery: self.galleryController != nil)
|
||||||
}
|
}
|
||||||
@ -1331,7 +1352,11 @@ public class CameraScreen: ViewController {
|
|||||||
self.node.pauseCameraCapture()
|
self.node.pauseCameraCapture()
|
||||||
}
|
}
|
||||||
|
|
||||||
let controller = self.context.sharedContext.makeMediaPickerScreen(context: self.context, completion: { [weak self] result, transitionView, transitionRect, transitionImage, transitionOut, dismissed in
|
let controller: ViewController
|
||||||
|
if let current = self.galleryController {
|
||||||
|
controller = current
|
||||||
|
} else {
|
||||||
|
controller = self.context.sharedContext.makeMediaPickerScreen(context: self.context, completion: { [weak self] result, transitionView, transitionRect, transitionImage, transitionOut, dismissed in
|
||||||
if let self {
|
if let self {
|
||||||
stopCameraCapture()
|
stopCameraCapture()
|
||||||
|
|
||||||
@ -1361,8 +1386,8 @@ public class CameraScreen: ViewController {
|
|||||||
self.node.updateModalTransitionFactor(transitionFactor, transition: transition)
|
self.node.updateModalTransitionFactor(transitionFactor, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.galleryController = controller
|
self.galleryController = controller
|
||||||
|
}
|
||||||
self.push(controller)
|
self.push(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -397,15 +397,18 @@ final class CaptureControlsComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handlePress(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
@objc private func handlePress(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
let location = gestureRecognizer.location(in: self)
|
let location = gestureRecognizer.location(in: self)
|
||||||
switch gestureRecognizer.state {
|
switch gestureRecognizer.state {
|
||||||
case .began:
|
case .began:
|
||||||
self.component?.shutterPressed()
|
component.shutterPressed()
|
||||||
self.component?.swipeHintUpdated(.zoom)
|
component.swipeHintUpdated(.zoom)
|
||||||
self.shutterUpdateOffset.invoke((0.0, .immediate))
|
self.shutterUpdateOffset.invoke((0.0, .immediate))
|
||||||
case .ended, .cancelled:
|
case .ended, .cancelled:
|
||||||
if location.x < self.frame.width / 2.0 - 60.0 {
|
if location.x < self.frame.width / 2.0 - 60.0 {
|
||||||
self.component?.lockRecording()
|
component.lockRecording()
|
||||||
|
|
||||||
var blobOffset: CGFloat = 0.0
|
var blobOffset: CGFloat = 0.0
|
||||||
if let galleryButton = self.galleryButtonView.view {
|
if let galleryButton = self.galleryButtonView.view {
|
||||||
@ -414,7 +417,7 @@ final class CaptureControlsComponent: Component {
|
|||||||
self.shutterUpdateOffset.invoke((blobOffset, .spring(duration: 0.35)))
|
self.shutterUpdateOffset.invoke((blobOffset, .spring(duration: 0.35)))
|
||||||
} else {
|
} else {
|
||||||
self.hapticFeedback.impact(.light)
|
self.hapticFeedback.impact(.light)
|
||||||
self.component?.shutterReleased()
|
component.shutterReleased()
|
||||||
self.shutterUpdateOffset.invoke((0.0, .spring(duration: 0.25)))
|
self.shutterUpdateOffset.invoke((0.0, .spring(duration: 0.25)))
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -426,6 +429,9 @@ final class CaptureControlsComponent: Component {
|
|||||||
private var wasBanding: Bool?
|
private var wasBanding: Bool?
|
||||||
private var panBlobState: ShutterBlobView.BlobState?
|
private var panBlobState: ShutterBlobView.BlobState?
|
||||||
@objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
|
@objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
func rubberBandingOffset(offset: CGFloat, bandingStart: CGFloat) -> CGFloat {
|
func rubberBandingOffset(offset: CGFloat, bandingStart: CGFloat) -> CGFloat {
|
||||||
let bandedOffset = offset - bandingStart
|
let bandedOffset = offset - bandingStart
|
||||||
let range: CGFloat = 60.0
|
let range: CGFloat = 60.0
|
||||||
@ -437,6 +443,9 @@ final class CaptureControlsComponent: Component {
|
|||||||
let location = gestureRecognizer.location(in: self)
|
let location = gestureRecognizer.location(in: self)
|
||||||
switch gestureRecognizer.state {
|
switch gestureRecognizer.state {
|
||||||
case .changed:
|
case .changed:
|
||||||
|
guard case .holdRecording = component.shutterState else {
|
||||||
|
return
|
||||||
|
}
|
||||||
var blobOffset: CGFloat = 0.0
|
var blobOffset: CGFloat = 0.0
|
||||||
if let galleryButton = self.galleryButtonView.view, let flipButton = self.flipButtonView.view {
|
if let galleryButton = self.galleryButtonView.view, let flipButton = self.flipButtonView.view {
|
||||||
blobOffset = max(galleryButton.center.x, min(flipButton.center.x, location.x))
|
blobOffset = max(galleryButton.center.x, min(flipButton.center.x, location.x))
|
||||||
@ -445,21 +454,21 @@ final class CaptureControlsComponent: Component {
|
|||||||
var isBanding = false
|
var isBanding = false
|
||||||
if location.y < -10.0 {
|
if location.y < -10.0 {
|
||||||
let fraction = 1.0 + min(8.0, ((abs(location.y) - 10.0) / 60.0))
|
let fraction = 1.0 + min(8.0, ((abs(location.y) - 10.0) / 60.0))
|
||||||
self.component?.zoomUpdated(fraction)
|
component.zoomUpdated(fraction)
|
||||||
} else {
|
} else {
|
||||||
self.component?.zoomUpdated(1.0)
|
component.zoomUpdated(1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if location.x < self.frame.width / 2.0 - 30.0 {
|
if location.x < self.frame.width / 2.0 - 30.0 {
|
||||||
if location.x < self.frame.width / 2.0 - 60.0 {
|
if location.x < self.frame.width / 2.0 - 60.0 {
|
||||||
self.component?.swipeHintUpdated(.releaseLock)
|
component.swipeHintUpdated(.releaseLock)
|
||||||
if location.x < 75.0 {
|
if location.x < 75.0 {
|
||||||
self.panBlobState = .lock
|
self.panBlobState = .lock
|
||||||
} else {
|
} else {
|
||||||
self.panBlobState = .transientToLock
|
self.panBlobState = .transientToLock
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.component?.swipeHintUpdated(.lock)
|
component.swipeHintUpdated(.lock)
|
||||||
self.panBlobState = .video
|
self.panBlobState = .video
|
||||||
blobOffset = rubberBandingOffset(offset: blobOffset, bandingStart: 0.0)
|
blobOffset = rubberBandingOffset(offset: blobOffset, bandingStart: 0.0)
|
||||||
isBanding = true
|
isBanding = true
|
||||||
@ -472,7 +481,7 @@ final class CaptureControlsComponent: Component {
|
|||||||
self.didFlip = true
|
self.didFlip = true
|
||||||
self.hapticFeedback.impact(.light)
|
self.hapticFeedback.impact(.light)
|
||||||
self.flipAnimationAction.invoke(Void())
|
self.flipAnimationAction.invoke(Void())
|
||||||
self.component?.flipTapped()
|
component.flipTapped()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.didFlip = false
|
self.didFlip = false
|
||||||
@ -482,7 +491,7 @@ final class CaptureControlsComponent: Component {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
blobOffset = rubberBandingOffset(offset: blobOffset, bandingStart: 0.0)
|
blobOffset = rubberBandingOffset(offset: blobOffset, bandingStart: 0.0)
|
||||||
self.component?.swipeHintUpdated(.zoom)
|
component.swipeHintUpdated(.zoom)
|
||||||
self.panBlobState = .video
|
self.panBlobState = .video
|
||||||
isBanding = true
|
isBanding = true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,6 +56,10 @@ final class ImageTextureSource: TextureSource {
|
|||||||
self.output?.consumeTexture(texture, render: false)
|
self.output?.consumeTexture(texture, render: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func invalidate() {
|
||||||
|
self.texture = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func pixelBufferToMTLTexture(pixelBuffer: CVPixelBuffer, textureCache: CVMetalTextureCache) -> MTLTexture? {
|
func pixelBufferToMTLTexture(pixelBuffer: CVPixelBuffer, textureCache: CVMetalTextureCache) -> MTLTexture? {
|
||||||
|
|||||||
@ -52,8 +52,10 @@ public final class MediaEditor {
|
|||||||
self.updateRenderChain()
|
self.updateRenderChain()
|
||||||
}
|
}
|
||||||
self.valuesPromise.set(.single(self.values))
|
self.valuesPromise.set(.single(self.values))
|
||||||
|
self.valuesUpdated(self.values)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public var valuesUpdated: (MediaEditorValues) -> Void = { _ in }
|
||||||
private var valuesPromise = Promise<MediaEditorValues>()
|
private var valuesPromise = Promise<MediaEditorValues>()
|
||||||
|
|
||||||
private let renderer = MediaEditorRenderer()
|
private let renderer = MediaEditorRenderer()
|
||||||
@ -329,6 +331,7 @@ public final class MediaEditor {
|
|||||||
if asset.mediaType == .video {
|
if asset.mediaType == .video {
|
||||||
let options = PHImageRequestOptions()
|
let options = PHImageRequestOptions()
|
||||||
options.deliveryMode = .fastFormat
|
options.deliveryMode = .fastFormat
|
||||||
|
options.isNetworkAccessAllowed = true
|
||||||
let requestId = PHImageManager.default().requestImage(for: asset, targetSize: CGSize(width: 128.0, height: 128.0), contentMode: .aspectFit, options: options, resultHandler: { image, info in
|
let requestId = PHImageManager.default().requestImage(for: asset, targetSize: CGSize(width: 128.0, height: 128.0), contentMode: .aspectFit, options: options, resultHandler: { image, info in
|
||||||
if let image {
|
if let image {
|
||||||
if let info {
|
if let info {
|
||||||
@ -353,6 +356,7 @@ public final class MediaEditor {
|
|||||||
} else {
|
} else {
|
||||||
let options = PHImageRequestOptions()
|
let options = PHImageRequestOptions()
|
||||||
options.deliveryMode = .highQualityFormat
|
options.deliveryMode = .highQualityFormat
|
||||||
|
options.isNetworkAccessAllowed = true
|
||||||
let requestId = PHImageManager.default().requestImage(for: asset, targetSize: CGSize(width: 1920.0, height: 1920.0), contentMode: .aspectFit, options: options, resultHandler: { image, info in
|
let requestId = PHImageManager.default().requestImage(for: asset, targetSize: CGSize(width: 1920.0, height: 1920.0), contentMode: .aspectFit, options: options, resultHandler: { image, info in
|
||||||
if let image {
|
if let image {
|
||||||
var degraded = false
|
var degraded = false
|
||||||
@ -498,6 +502,11 @@ public final class MediaEditor {
|
|||||||
self.player?.pause()
|
self.player?.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func invalidate() {
|
||||||
|
self.player?.pause()
|
||||||
|
self.renderer.textureSource?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
private func updateVideoTimePosition() {
|
private func updateVideoTimePosition() {
|
||||||
guard let (targetPosition, _) = self.targetTimePosition else {
|
guard let (targetPosition, _) = self.targetTimePosition else {
|
||||||
return
|
return
|
||||||
@ -616,7 +625,7 @@ final class MediaEditorRenderChain {
|
|||||||
switch key {
|
switch key {
|
||||||
case .enhance:
|
case .enhance:
|
||||||
if let value = value as? Float {
|
if let value = value as? Float {
|
||||||
self.enhancePass.value = value
|
self.enhancePass.value = abs(value)
|
||||||
} else {
|
} else {
|
||||||
self.enhancePass.value = 0.0
|
self.enhancePass.value = 0.0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,11 @@ import TelegramUIPreferences
|
|||||||
import PersistentStringHash
|
import PersistentStringHash
|
||||||
import Postbox
|
import Postbox
|
||||||
|
|
||||||
|
public enum MediaEditorResultPrivacy: Equatable {
|
||||||
|
case story(privacy: EngineStoryPrivacy, timeout: Int, archive: Bool)
|
||||||
|
case message(peers: [EnginePeer.Id], timeout: Int?)
|
||||||
|
}
|
||||||
|
|
||||||
public final class MediaEditorDraft: Codable, Equatable {
|
public final class MediaEditorDraft: Codable, Equatable {
|
||||||
public static func == (lhs: MediaEditorDraft, rhs: MediaEditorDraft) -> Bool {
|
public static func == (lhs: MediaEditorDraft, rhs: MediaEditorDraft) -> Bool {
|
||||||
return lhs.path == rhs.path
|
return lhs.path == rhs.path
|
||||||
@ -25,13 +30,15 @@ public final class MediaEditorDraft: Codable, Equatable {
|
|||||||
public let thumbnail: UIImage
|
public let thumbnail: UIImage
|
||||||
public let dimensions: PixelDimensions
|
public let dimensions: PixelDimensions
|
||||||
public let values: MediaEditorValues
|
public let values: MediaEditorValues
|
||||||
|
public let privacy: MediaEditorResultPrivacy?
|
||||||
|
|
||||||
public init(path: String, isVideo: Bool, thumbnail: UIImage, dimensions: PixelDimensions, values: MediaEditorValues) {
|
public init(path: String, isVideo: Bool, thumbnail: UIImage, dimensions: PixelDimensions, values: MediaEditorValues, privacy: MediaEditorResultPrivacy?) {
|
||||||
self.path = path
|
self.path = path
|
||||||
self.isVideo = isVideo
|
self.isVideo = isVideo
|
||||||
self.thumbnail = thumbnail
|
self.thumbnail = thumbnail
|
||||||
self.dimensions = dimensions
|
self.dimensions = dimensions
|
||||||
self.values = values
|
self.values = values
|
||||||
|
self.privacy = privacy
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
@ -55,6 +62,7 @@ public final class MediaEditorDraft: Codable, Equatable {
|
|||||||
} else {
|
} else {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
self.privacy = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import MetalKit
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
public final class MediaEditorPreviewView: MTKView, MTKViewDelegate, RenderTarget {
|
public final class MediaEditorPreviewView: MTKView, MTKViewDelegate, RenderTarget {
|
||||||
var renderer: MediaEditorRenderer? {
|
weak var renderer: MediaEditorRenderer? {
|
||||||
didSet {
|
didSet {
|
||||||
if let renderer = self.renderer {
|
if let renderer = self.renderer {
|
||||||
renderer.renderTargetDidChange(self)
|
renderer.renderTargetDidChange(self)
|
||||||
|
|||||||
@ -30,6 +30,7 @@ protocol RenderPass: AnyObject {
|
|||||||
|
|
||||||
protocol TextureSource {
|
protocol TextureSource {
|
||||||
func connect(to: TextureConsumer)
|
func connect(to: TextureConsumer)
|
||||||
|
func invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol RenderTarget: AnyObject {
|
protocol RenderTarget: AnyObject {
|
||||||
|
|||||||
@ -26,8 +26,8 @@ func textureRotatonForAVAsset(_ asset: AVAsset) -> TextureRotation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class VideoTextureSource: NSObject, TextureSource, AVPlayerItemOutputPullDelegate {
|
final class VideoTextureSource: NSObject, TextureSource, AVPlayerItemOutputPullDelegate {
|
||||||
private let player: AVPlayer
|
private weak var player: AVPlayer?
|
||||||
private var playerItem: AVPlayerItem?
|
private weak var playerItem: AVPlayerItem?
|
||||||
private var playerItemOutput: AVPlayerItemVideoOutput?
|
private var playerItemOutput: AVPlayerItemVideoOutput?
|
||||||
|
|
||||||
private var playerItemStatusObservation: NSKeyValueObservation?
|
private var playerItemStatusObservation: NSKeyValueObservation?
|
||||||
@ -57,11 +57,11 @@ final class VideoTextureSource: NSObject, TextureSource, AVPlayerItemOutputPullD
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.playerItemObservation = self.player.observe(\.currentItem, options: [.initial, .new], changeHandler: { [weak self] (player, change) in
|
self.playerItemObservation = player.observe(\.currentItem, options: [.initial, .new], changeHandler: { [weak self] (player, change) in
|
||||||
guard let strongSelf = self, strongSelf.player == player else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.updatePlayerItem(strongSelf.player.currentItem)
|
strongSelf.updatePlayerItem(player.currentItem)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +70,15 @@ final class VideoTextureSource: NSObject, TextureSource, AVPlayerItemOutputPullD
|
|||||||
self.playerItemStatusObservation?.invalidate()
|
self.playerItemStatusObservation?.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func invalidate() {
|
||||||
|
self.playerItemOutput?.setDelegate(nil, queue: self.queue)
|
||||||
|
self.playerItemOutput = nil
|
||||||
|
self.playerItemObservation?.invalidate()
|
||||||
|
self.playerItemStatusObservation?.invalidate()
|
||||||
|
self.displayLink?.invalidate()
|
||||||
|
self.displayLink = nil
|
||||||
|
}
|
||||||
|
|
||||||
private func updatePlayerItem(_ playerItem: AVPlayerItem?) {
|
private func updatePlayerItem(_ playerItem: AVPlayerItem?) {
|
||||||
self.displayLink?.invalidate()
|
self.displayLink?.invalidate()
|
||||||
self.displayLink = nil
|
self.displayLink = nil
|
||||||
@ -160,7 +169,10 @@ final class VideoTextureSource: NSObject, TextureSource, AVPlayerItemOutputPullD
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func handleUpdate() {
|
private func handleUpdate() {
|
||||||
if self.player.rate != 0 {
|
guard let player = self.player else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if player.rate != 0 {
|
||||||
self.forceUpdate = true
|
self.forceUpdate = true
|
||||||
}
|
}
|
||||||
self.update(forced: self.forceUpdate)
|
self.update(forced: self.forceUpdate)
|
||||||
|
|||||||
@ -313,12 +313,21 @@ final class AdjustmentsComponent: Component {
|
|||||||
componentView = self.toolViews[i]
|
componentView = self.toolViews[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var valueIsNegative = false
|
||||||
|
var value = tool.value
|
||||||
|
if case .enhance = tool.key {
|
||||||
|
if value < 0.0 {
|
||||||
|
valueIsNegative = true
|
||||||
|
}
|
||||||
|
value = abs(value)
|
||||||
|
}
|
||||||
|
|
||||||
let size = componentView.update(
|
let size = componentView.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
AdjustmentSliderComponent(
|
AdjustmentSliderComponent(
|
||||||
title: tool.title,
|
title: tool.title,
|
||||||
value: tool.value,
|
value: value,
|
||||||
minValue: tool.minValue,
|
minValue: tool.minValue,
|
||||||
maxValue: tool.maxValue,
|
maxValue: tool.maxValue,
|
||||||
startValue: tool.startValue,
|
startValue: tool.startValue,
|
||||||
@ -326,7 +335,11 @@ final class AdjustmentsComponent: Component {
|
|||||||
trackColor: nil,
|
trackColor: nil,
|
||||||
displayValue: true,
|
displayValue: true,
|
||||||
valueUpdated: { value in
|
valueUpdated: { value in
|
||||||
valueUpdated(tool.key, value)
|
var updatedValue = value
|
||||||
|
if valueIsNegative {
|
||||||
|
updatedValue *= -1.0
|
||||||
|
}
|
||||||
|
valueUpdated(tool.key, updatedValue)
|
||||||
},
|
},
|
||||||
isTrackingUpdated: { isTracking in
|
isTrackingUpdated: { isTracking in
|
||||||
isTrackingUpdated(tool.key, isTracking)
|
isTrackingUpdated(tool.key, isTracking)
|
||||||
|
|||||||
@ -118,15 +118,18 @@ final class BlurComponent: Component {
|
|||||||
let value: BlurValue
|
let value: BlurValue
|
||||||
let hasPortrait: Bool
|
let hasPortrait: Bool
|
||||||
let valueUpdated: (BlurValue) -> Void
|
let valueUpdated: (BlurValue) -> Void
|
||||||
|
let isTrackingUpdated: (Bool) -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
value: BlurValue,
|
value: BlurValue,
|
||||||
hasPortrait: Bool,
|
hasPortrait: Bool,
|
||||||
valueUpdated: @escaping (BlurValue) -> Void
|
valueUpdated: @escaping (BlurValue) -> Void,
|
||||||
|
isTrackingUpdated: @escaping (Bool) -> Void
|
||||||
) {
|
) {
|
||||||
self.value = value
|
self.value = value
|
||||||
self.hasPortrait = hasPortrait
|
self.hasPortrait = hasPortrait
|
||||||
self.valueUpdated = valueUpdated
|
self.valueUpdated = valueUpdated
|
||||||
|
self.isTrackingUpdated = isTrackingUpdated
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: BlurComponent, rhs: BlurComponent) -> Bool {
|
static func ==(lhs: BlurComponent, rhs: BlurComponent) -> Bool {
|
||||||
@ -288,6 +291,36 @@ final class BlurComponent: Component {
|
|||||||
containerSize: availableSize
|
containerSize: availableSize
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let isTrackingUpdated: (Bool) -> Void = { [weak self] isTracking in
|
||||||
|
component.isTrackingUpdated(isTracking)
|
||||||
|
|
||||||
|
if let self {
|
||||||
|
let transition: Transition
|
||||||
|
if isTracking {
|
||||||
|
transition = .immediate
|
||||||
|
} else {
|
||||||
|
transition = .easeInOut(duration: 0.25)
|
||||||
|
}
|
||||||
|
|
||||||
|
let alpha: CGFloat = isTracking ? 0.0 : 1.0
|
||||||
|
if let view = self.title.view {
|
||||||
|
transition.setAlpha(view: view, alpha: alpha)
|
||||||
|
}
|
||||||
|
if let view = self.offButton.view {
|
||||||
|
transition.setAlpha(view: view, alpha: alpha)
|
||||||
|
}
|
||||||
|
if let view = self.radialButton.view {
|
||||||
|
transition.setAlpha(view: view, alpha: alpha)
|
||||||
|
}
|
||||||
|
if let view = self.linearButton.view {
|
||||||
|
transition.setAlpha(view: view, alpha: alpha)
|
||||||
|
}
|
||||||
|
if let view = self.portraitButton.view {
|
||||||
|
transition.setAlpha(view: view, alpha: alpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let sliderSize = self.slider.update(
|
let sliderSize = self.slider.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
@ -304,6 +337,9 @@ final class BlurComponent: Component {
|
|||||||
if let state {
|
if let state {
|
||||||
valueUpdated(state.value.withUpdatedIntensity(value))
|
valueUpdated(state.value.withUpdatedIntensity(value))
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
isTrackingUpdated: { isTracking in
|
||||||
|
isTrackingUpdated(isTracking)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -364,14 +400,17 @@ final class BlurScreenComponent: Component {
|
|||||||
|
|
||||||
let value: BlurValue
|
let value: BlurValue
|
||||||
let valueUpdated: (BlurValue) -> Void
|
let valueUpdated: (BlurValue) -> Void
|
||||||
|
let isTrackingUpdated: (Bool) -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
value: BlurValue,
|
value: BlurValue,
|
||||||
valueUpdated: @escaping (BlurValue) -> Void
|
valueUpdated: @escaping (BlurValue) -> Void,
|
||||||
|
isTrackingUpdated: @escaping (Bool) -> Void
|
||||||
|
|
||||||
) {
|
) {
|
||||||
self.value = value
|
self.value = value
|
||||||
self.valueUpdated = valueUpdated
|
self.valueUpdated = valueUpdated
|
||||||
|
self.isTrackingUpdated = isTrackingUpdated
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: BlurScreenComponent, rhs: BlurScreenComponent) -> Bool {
|
static func ==(lhs: BlurScreenComponent, rhs: BlurScreenComponent) -> Bool {
|
||||||
@ -435,6 +474,7 @@ final class BlurScreenComponent: Component {
|
|||||||
case .began:
|
case .began:
|
||||||
switch component.value.mode {
|
switch component.value.mode {
|
||||||
case .radial:
|
case .radial:
|
||||||
|
component.isTrackingUpdated(true)
|
||||||
let distance = sqrt(delta.x * delta.x + delta.y * delta.y)
|
let distance = sqrt(delta.x * delta.x + delta.y * delta.y)
|
||||||
|
|
||||||
let close = abs(outerRadius - innerRadius) < blurInsetProximity
|
let close = abs(outerRadius - innerRadius) < blurInsetProximity
|
||||||
@ -455,6 +495,7 @@ final class BlurScreenComponent: Component {
|
|||||||
self.startRadius = outerRadius
|
self.startRadius = outerRadius
|
||||||
}
|
}
|
||||||
case .linear:
|
case .linear:
|
||||||
|
component.isTrackingUpdated(true)
|
||||||
let radialDistance = sqrt(delta.x * delta.x + delta.y * delta.y)
|
let radialDistance = sqrt(delta.x * delta.x + delta.y * delta.y)
|
||||||
let distance = abs(delta.x * cos(CGFloat(component.value.rotation) + .pi / 2.0) + delta.y * sin(CGFloat(component.value.rotation) + .pi / 2.0))
|
let distance = abs(delta.x * cos(CGFloat(component.value.rotation) + .pi / 2.0) + delta.y * sin(CGFloat(component.value.rotation) + .pi / 2.0))
|
||||||
|
|
||||||
@ -617,6 +658,7 @@ final class BlurScreenComponent: Component {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
component.isTrackingUpdated(false)
|
||||||
self.activeControl = nil
|
self.activeControl = nil
|
||||||
self.startCenterPoint = nil
|
self.startCenterPoint = nil
|
||||||
self.startDistance = nil
|
self.startDistance = nil
|
||||||
@ -630,6 +672,7 @@ final class BlurScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
switch gestureRecognizer.state {
|
switch gestureRecognizer.state {
|
||||||
case .began:
|
case .began:
|
||||||
|
component.isTrackingUpdated(true)
|
||||||
self.activeControl = .wholeArea
|
self.activeControl = .wholeArea
|
||||||
case .changed:
|
case .changed:
|
||||||
let scale = Float(gestureRecognizer.scale)
|
let scale = Float(gestureRecognizer.scale)
|
||||||
@ -640,6 +683,7 @@ final class BlurScreenComponent: Component {
|
|||||||
|
|
||||||
gestureRecognizer.scale = 1.0
|
gestureRecognizer.scale = 1.0
|
||||||
default:
|
default:
|
||||||
|
component.isTrackingUpdated(false)
|
||||||
self.activeControl = nil
|
self.activeControl = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import Display
|
|||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
import MediaEditor
|
import MediaEditor
|
||||||
|
import MultilineTextComponent
|
||||||
|
|
||||||
private class HistogramView: UIView {
|
private class HistogramView: UIView {
|
||||||
private var size: CGSize?
|
private var size: CGSize?
|
||||||
@ -332,6 +333,7 @@ final class CurvesScreenComponent: Component {
|
|||||||
private let line3 = SimpleLayer()
|
private let line3 = SimpleLayer()
|
||||||
private let line4 = SimpleLayer()
|
private let line4 = SimpleLayer()
|
||||||
|
|
||||||
|
private let curveContainer = SimpleLayer()
|
||||||
private let guideLayer = SimpleShapeLayer()
|
private let guideLayer = SimpleShapeLayer()
|
||||||
private let curveLayer = SimpleShapeLayer()
|
private let curveLayer = SimpleShapeLayer()
|
||||||
|
|
||||||
@ -346,8 +348,15 @@ final class CurvesScreenComponent: Component {
|
|||||||
self.layer.addSublayer(self.line3)
|
self.layer.addSublayer(self.line3)
|
||||||
self.layer.addSublayer(self.line4)
|
self.layer.addSublayer(self.line4)
|
||||||
|
|
||||||
self.layer.addSublayer(self.guideLayer)
|
self.layer.addSublayer(self.curveContainer)
|
||||||
self.layer.addSublayer(self.curveLayer)
|
self.curveContainer.addSublayer(self.guideLayer)
|
||||||
|
self.curveContainer.addSublayer(self.curveLayer)
|
||||||
|
|
||||||
|
self.curveContainer.masksToBounds = true
|
||||||
|
self.curveContainer.cornerRadius = 12.0
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.curveContainer.cornerCurve = .continuous
|
||||||
|
}
|
||||||
|
|
||||||
self.line1.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.5).cgColor
|
self.line1.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.5).cgColor
|
||||||
self.line2.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.5).cgColor
|
self.line2.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.5).cgColor
|
||||||
@ -361,6 +370,26 @@ final class CurvesScreenComponent: Component {
|
|||||||
self.curveLayer.lineWidth = 2.0
|
self.curveLayer.lineWidth = 2.0
|
||||||
self.curveLayer.fillColor = UIColor.clear.cgColor
|
self.curveLayer.fillColor = UIColor.clear.cgColor
|
||||||
|
|
||||||
|
let allLayers = [
|
||||||
|
self.line1,
|
||||||
|
self.line2,
|
||||||
|
self.line3,
|
||||||
|
self.line4,
|
||||||
|
self.guideLayer,
|
||||||
|
]
|
||||||
|
|
||||||
|
for layer in allLayers {
|
||||||
|
layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
||||||
|
layer.shadowRadius = 1.5
|
||||||
|
layer.shadowColor = UIColor.black.cgColor
|
||||||
|
layer.shadowOpacity = 0.3
|
||||||
|
}
|
||||||
|
|
||||||
|
self.curveLayer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
||||||
|
self.curveLayer.shadowRadius = 2.0
|
||||||
|
self.curveLayer.shadowColor = UIColor.black.cgColor
|
||||||
|
self.curveLayer.shadowOpacity = 0.16
|
||||||
|
|
||||||
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:)))
|
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:)))
|
||||||
self.addGestureRecognizer(panGestureRecognizer)
|
self.addGestureRecognizer(panGestureRecognizer)
|
||||||
|
|
||||||
@ -495,10 +524,16 @@ final class CurvesScreenComponent: Component {
|
|||||||
let blacksSize = self.blacks.update(
|
let blacksSize = self.blacks.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
Text(
|
MultilineTextComponent(
|
||||||
text: String(format: "%.2f", value.blacks),
|
text: .plain(
|
||||||
|
NSAttributedString(
|
||||||
|
string: String(format: "%.2f", value.blacks),
|
||||||
font: Font.regular(14.0),
|
font: Font.regular(14.0),
|
||||||
color: UIColor(rgb: 0xffffff, alpha: 0.75)
|
textColor: UIColor(rgb: 0xffffff)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
textShadowColor: UIColor(rgb: 0x000000, alpha: 0.3),
|
||||||
|
textShadowBlur: 1.5
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -509,16 +544,23 @@ final class CurvesScreenComponent: Component {
|
|||||||
if view.superview == nil {
|
if view.superview == nil {
|
||||||
self.addSubview(view)
|
self.addSubview(view)
|
||||||
}
|
}
|
||||||
|
view.alpha = 0.75
|
||||||
transition.setFrame(view: view, frame: blacksFrame)
|
transition.setFrame(view: view, frame: blacksFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
let shadowsSize = self.shadows.update(
|
let shadowsSize = self.shadows.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
Text(
|
MultilineTextComponent(
|
||||||
text: String(format: "%.2f", value.shadows),
|
text: .plain(
|
||||||
|
NSAttributedString(
|
||||||
|
string: String(format: "%.2f", value.shadows),
|
||||||
font: Font.regular(14.0),
|
font: Font.regular(14.0),
|
||||||
color: UIColor(rgb: 0xffffff, alpha: 0.75)
|
textColor: UIColor(rgb: 0xffffff)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
textShadowColor: UIColor(rgb: 0x000000, alpha: 0.3),
|
||||||
|
textShadowBlur: 1.5
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -529,16 +571,23 @@ final class CurvesScreenComponent: Component {
|
|||||||
if view.superview == nil {
|
if view.superview == nil {
|
||||||
self.addSubview(view)
|
self.addSubview(view)
|
||||||
}
|
}
|
||||||
|
view.alpha = 0.75
|
||||||
transition.setFrame(view: view, frame: shadowsFrame)
|
transition.setFrame(view: view, frame: shadowsFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
let midtonesSize = self.midtones.update(
|
let midtonesSize = self.midtones.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
Text(
|
MultilineTextComponent(
|
||||||
text: String(format: "%.2f", value.midtones),
|
text: .plain(
|
||||||
|
NSAttributedString(
|
||||||
|
string: String(format: "%.2f", value.midtones),
|
||||||
font: Font.regular(14.0),
|
font: Font.regular(14.0),
|
||||||
color: UIColor(rgb: 0xffffff, alpha: 0.75)
|
textColor: UIColor(rgb: 0xffffff)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
textShadowColor: UIColor(rgb: 0x000000, alpha: 0.3),
|
||||||
|
textShadowBlur: 1.5
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -549,16 +598,23 @@ final class CurvesScreenComponent: Component {
|
|||||||
if view.superview == nil {
|
if view.superview == nil {
|
||||||
self.addSubview(view)
|
self.addSubview(view)
|
||||||
}
|
}
|
||||||
|
view.alpha = 0.75
|
||||||
transition.setFrame(view: view, frame: midtonesFrame)
|
transition.setFrame(view: view, frame: midtonesFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
let highlightsSize = self.highlights.update(
|
let highlightsSize = self.highlights.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
Text(
|
MultilineTextComponent(
|
||||||
text: String(format: "%.2f", value.highlights),
|
text: .plain(
|
||||||
|
NSAttributedString(
|
||||||
|
string: String(format: "%.2f", value.highlights),
|
||||||
font: Font.regular(14.0),
|
font: Font.regular(14.0),
|
||||||
color: UIColor(rgb: 0xffffff, alpha: 0.75)
|
textColor: UIColor(rgb: 0xffffff)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
textShadowColor: UIColor(rgb: 0x000000, alpha: 0.3),
|
||||||
|
textShadowBlur: 1.5
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -569,16 +625,23 @@ final class CurvesScreenComponent: Component {
|
|||||||
if view.superview == nil {
|
if view.superview == nil {
|
||||||
self.addSubview(view)
|
self.addSubview(view)
|
||||||
}
|
}
|
||||||
|
view.alpha = 0.75
|
||||||
transition.setFrame(view: view, frame: highlightsFrame)
|
transition.setFrame(view: view, frame: highlightsFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
let whitesSize = self.whites.update(
|
let whitesSize = self.whites.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
Text(
|
MultilineTextComponent(
|
||||||
text: String(format: "%.2f", value.whites),
|
text: .plain(
|
||||||
|
NSAttributedString(
|
||||||
|
string: String(format: "%.2f", value.whites),
|
||||||
font: Font.regular(14.0),
|
font: Font.regular(14.0),
|
||||||
color: UIColor(rgb: 0xffffff, alpha: 0.75)
|
textColor: UIColor(rgb: 0xffffff)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
textShadowColor: UIColor(rgb: 0x000000, alpha: 0.3),
|
||||||
|
textShadowBlur: 1.5
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -589,9 +652,12 @@ final class CurvesScreenComponent: Component {
|
|||||||
if view.superview == nil {
|
if view.superview == nil {
|
||||||
self.addSubview(view)
|
self.addSubview(view)
|
||||||
}
|
}
|
||||||
|
view.alpha = 0.75
|
||||||
transition.setFrame(view: view, frame: whitesFrame)
|
transition.setFrame(view: view, frame: whitesFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.curveContainer.frame = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: availableSize.height + 12.0))
|
||||||
|
|
||||||
let lineWidth: CGFloat = 1.0 - UIScreenPixel
|
let lineWidth: CGFloat = 1.0 - UIScreenPixel
|
||||||
self.line1.frame = CGRect(x: fieldWidth, y: 0.0, width: lineWidth, height: availableSize.height)
|
self.line1.frame = CGRect(x: fieldWidth, y: 0.0, width: lineWidth, height: availableSize.height)
|
||||||
self.line2.frame = CGRect(x: fieldWidth * 2.0, y: 0.0, width: lineWidth, height: availableSize.height)
|
self.line2.frame = CGRect(x: fieldWidth * 2.0, y: 0.0, width: lineWidth, height: availableSize.height)
|
||||||
|
|||||||
@ -40,6 +40,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let isDisplayingTool: Bool
|
let isDisplayingTool: Bool
|
||||||
let isInteractingWithEntities: Bool
|
let isInteractingWithEntities: Bool
|
||||||
|
let isSavingAvailable: Bool
|
||||||
let isDismissing: Bool
|
let isDismissing: Bool
|
||||||
let mediaEditor: MediaEditor?
|
let mediaEditor: MediaEditor?
|
||||||
let privacy: MediaEditorResultPrivacy
|
let privacy: MediaEditorResultPrivacy
|
||||||
@ -52,6 +53,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
isDisplayingTool: Bool,
|
isDisplayingTool: Bool,
|
||||||
isInteractingWithEntities: Bool,
|
isInteractingWithEntities: Bool,
|
||||||
|
isSavingAvailable: Bool,
|
||||||
isDismissing: Bool,
|
isDismissing: Bool,
|
||||||
mediaEditor: MediaEditor?,
|
mediaEditor: MediaEditor?,
|
||||||
privacy: MediaEditorResultPrivacy,
|
privacy: MediaEditorResultPrivacy,
|
||||||
@ -63,6 +65,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
self.context = context
|
self.context = context
|
||||||
self.isDisplayingTool = isDisplayingTool
|
self.isDisplayingTool = isDisplayingTool
|
||||||
self.isInteractingWithEntities = isInteractingWithEntities
|
self.isInteractingWithEntities = isInteractingWithEntities
|
||||||
|
self.isSavingAvailable = isSavingAvailable
|
||||||
self.isDismissing = isDismissing
|
self.isDismissing = isDismissing
|
||||||
self.mediaEditor = mediaEditor
|
self.mediaEditor = mediaEditor
|
||||||
self.privacy = privacy
|
self.privacy = privacy
|
||||||
@ -82,6 +85,9 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
if lhs.isInteractingWithEntities != rhs.isInteractingWithEntities {
|
if lhs.isInteractingWithEntities != rhs.isInteractingWithEntities {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isSavingAvailable != rhs.isSavingAvailable {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.isDismissing != rhs.isDismissing {
|
if lhs.isDismissing != rhs.isDismissing {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -186,12 +192,15 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
private let toolsButton = ComponentView<Empty>()
|
private let toolsButton = ComponentView<Empty>()
|
||||||
private let doneButton = ComponentView<Empty>()
|
private let doneButton = ComponentView<Empty>()
|
||||||
|
|
||||||
|
private let fadeView = UIButton()
|
||||||
|
|
||||||
private let inputPanel = ComponentView<Empty>()
|
private let inputPanel = ComponentView<Empty>()
|
||||||
private let inputPanelExternalState = MessageInputPanelComponent.ExternalState()
|
private let inputPanelExternalState = MessageInputPanelComponent.ExternalState()
|
||||||
|
|
||||||
private let scrubber = ComponentView<Empty>()
|
private let scrubber = ComponentView<Empty>()
|
||||||
|
|
||||||
private let privacyButton = ComponentView<Empty>()
|
private let privacyButton = ComponentView<Empty>()
|
||||||
|
private let flipStickerButton = ComponentView<Empty>()
|
||||||
private let muteButton = ComponentView<Empty>()
|
private let muteButton = ComponentView<Empty>()
|
||||||
private let saveButton = ComponentView<Empty>()
|
private let saveButton = ComponentView<Empty>()
|
||||||
private let settingsButton = ComponentView<Empty>()
|
private let settingsButton = ComponentView<Empty>()
|
||||||
@ -210,22 +219,27 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
self.backgroundColor = .clear
|
self.backgroundColor = .clear
|
||||||
|
|
||||||
|
self.fadeView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.4)
|
||||||
|
self.fadeView.addTarget(self, action: #selector(self.fadePressed), for: .touchUpInside)
|
||||||
|
self.fadeView.alpha = 0.0
|
||||||
|
|
||||||
|
self.addSubview(self.fadeView)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func fadePressed() {
|
||||||
|
self.endEditing(true)
|
||||||
|
}
|
||||||
|
|
||||||
enum TransitionAnimationSource {
|
enum TransitionAnimationSource {
|
||||||
case camera
|
case camera
|
||||||
case gallery
|
case gallery
|
||||||
}
|
}
|
||||||
func animateIn(from source: TransitionAnimationSource) {
|
func animateIn(from source: TransitionAnimationSource) {
|
||||||
if let view = self.cancelButton.view {
|
|
||||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
|
||||||
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
|
||||||
}
|
|
||||||
|
|
||||||
let buttons = [
|
let buttons = [
|
||||||
self.drawButton,
|
self.drawButton,
|
||||||
self.textButton,
|
self.textButton,
|
||||||
@ -233,6 +247,11 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
self.toolsButton
|
self.toolsButton
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if let view = self.cancelButton.view {
|
||||||
|
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
var delay: Double = 0.0
|
var delay: Double = 0.0
|
||||||
for button in buttons {
|
for button in buttons {
|
||||||
if let view = button.view {
|
if let view = button.view {
|
||||||
@ -248,12 +267,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let view = self.inputPanel.view {
|
if case .camera = source {
|
||||||
view.layer.animatePosition(from: CGPoint(x: 0.0, y: 44.0), to: .zero, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
|
||||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
|
||||||
view.layer.animateScale(from: 0.6, to: 1.0, duration: 0.2)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let view = self.saveButton.view {
|
if let view = self.saveButton.view {
|
||||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
||||||
@ -275,8 +289,18 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let view = self.inputPanel.view {
|
||||||
|
if case .camera = source {
|
||||||
|
view.layer.animatePosition(from: CGPoint(x: 0.0, y: 44.0), to: .zero, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
|
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
view.layer.animateScale(from: 0.6, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func animateOut(to source: TransitionAnimationSource) {
|
func animateOut(to source: TransitionAnimationSource) {
|
||||||
self.isDismissed = true
|
self.isDismissed = true
|
||||||
|
|
||||||
let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
|
let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
|
||||||
if let view = self.cancelButton.view {
|
if let view = self.cancelButton.view {
|
||||||
transition.setAlpha(view: view, alpha: 0.0)
|
transition.setAlpha(view: view, alpha: 0.0)
|
||||||
@ -304,10 +328,12 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let view = self.inputPanel.view {
|
if let view = self.inputPanel.view {
|
||||||
|
if case .camera = source {
|
||||||
view.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 44.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
view.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 44.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||||
view.layer.animateAlpha(from: view.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
view.layer.animateAlpha(from: view.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
|
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let view = self.saveButton.view {
|
if let view = self.saveButton.view {
|
||||||
transition.setAlpha(view: view, alpha: 0.0)
|
transition.setAlpha(view: view, alpha: 0.0)
|
||||||
@ -717,13 +743,23 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
wasRecordingDismissed: false,
|
wasRecordingDismissed: false,
|
||||||
timeoutValue: timeoutValue,
|
timeoutValue: timeoutValue,
|
||||||
timeoutSelected: timeoutSelected,
|
timeoutSelected: timeoutSelected,
|
||||||
displayGradient: false,//component.inputHeight != 0.0,
|
displayGradient: false,
|
||||||
bottomInset: 0.0 //component.inputHeight != 0.0 ? 0.0 : bottomContentInset
|
bottomInset: 0.0
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width, height: 200.0)
|
containerSize: CGSize(width: availableSize.width, height: 200.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let fadeTransition = Transition(animation: .curve(duration: 0.3, curve: .easeInOut))
|
||||||
|
if self.inputPanelExternalState.isEditing {
|
||||||
|
component.mediaEditor?.stop()
|
||||||
|
fadeTransition.setAlpha(view: self.fadeView, alpha: 1.0)
|
||||||
|
} else {
|
||||||
|
component.mediaEditor?.play()
|
||||||
|
fadeTransition.setAlpha(view: self.fadeView, alpha: 0.0)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: self.fadeView, frame: CGRect(origin: .zero, size: availableSize))
|
||||||
|
|
||||||
var isEditingTextEntity = false
|
var isEditingTextEntity = false
|
||||||
var sizeSliderVisible = false
|
var sizeSliderVisible = false
|
||||||
var sizeValue: CGFloat?
|
var sizeValue: CGFloat?
|
||||||
@ -836,15 +872,19 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
if let saveButtonView = self.saveButton.view {
|
if let saveButtonView = self.saveButton.view {
|
||||||
if saveButtonView.superview == nil {
|
if saveButtonView.superview == nil {
|
||||||
saveButtonView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
saveButtonView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
||||||
saveButtonView.layer.shadowRadius = 4.0
|
saveButtonView.layer.shadowRadius = 3.0
|
||||||
saveButtonView.layer.shadowColor = UIColor.black.cgColor
|
saveButtonView.layer.shadowColor = UIColor.black.cgColor
|
||||||
saveButtonView.layer.shadowOpacity = 0.2
|
saveButtonView.layer.shadowOpacity = 0.35
|
||||||
self.addSubview(saveButtonView)
|
self.addSubview(saveButtonView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let saveButtonAlpha = component.isSavingAvailable ? 1.0 : 0.3
|
||||||
|
saveButtonView.isUserInteractionEnabled = component.isSavingAvailable
|
||||||
|
|
||||||
transition.setPosition(view: saveButtonView, position: saveButtonFrame.center)
|
transition.setPosition(view: saveButtonView, position: saveButtonFrame.center)
|
||||||
transition.setBounds(view: saveButtonView, bounds: CGRect(origin: .zero, size: saveButtonFrame.size))
|
transition.setBounds(view: saveButtonView, bounds: CGRect(origin: .zero, size: saveButtonFrame.size))
|
||||||
transition.setScale(view: saveButtonView, scale: displayTopButtons ? 1.0 : 0.01)
|
transition.setScale(view: saveButtonView, scale: displayTopButtons ? 1.0 : 0.01)
|
||||||
transition.setAlpha(view: saveButtonView, alpha: displayTopButtons && !component.isDismissing && !component.isInteractingWithEntities ? 1.0 : 0.0)
|
transition.setAlpha(view: saveButtonView, alpha: displayTopButtons && !component.isDismissing && !component.isInteractingWithEntities ? saveButtonAlpha : 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let playerState = state.playerState, playerState.hasAudio {
|
if let playerState = state.playerState, playerState.hasAudio {
|
||||||
@ -880,9 +920,9 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
if let muteButtonView = self.muteButton.view {
|
if let muteButtonView = self.muteButton.view {
|
||||||
if muteButtonView.superview == nil {
|
if muteButtonView.superview == nil {
|
||||||
muteButtonView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
muteButtonView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
||||||
muteButtonView.layer.shadowRadius = 4.0
|
muteButtonView.layer.shadowRadius = 3.0
|
||||||
muteButtonView.layer.shadowColor = UIColor.black.cgColor
|
muteButtonView.layer.shadowColor = UIColor.black.cgColor
|
||||||
muteButtonView.layer.shadowOpacity = 0.2
|
muteButtonView.layer.shadowOpacity = 0.35
|
||||||
self.addSubview(muteButtonView)
|
self.addSubview(muteButtonView)
|
||||||
}
|
}
|
||||||
transition.setPosition(view: muteButtonView, position: muteButtonFrame.center)
|
transition.setPosition(view: muteButtonView, position: muteButtonFrame.center)
|
||||||
@ -918,9 +958,9 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
if let settingsButtonView = self.settingsButton.view {
|
if let settingsButtonView = self.settingsButton.view {
|
||||||
if settingsButtonView.superview == nil {
|
if settingsButtonView.superview == nil {
|
||||||
settingsButtonView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
settingsButtonView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
||||||
settingsButtonView.layer.shadowRadius = 4.0
|
settingsButtonView.layer.shadowRadius = 3.0
|
||||||
settingsButtonView.layer.shadowColor = UIColor.black.cgColor
|
settingsButtonView.layer.shadowColor = UIColor.black.cgColor
|
||||||
settingsButtonView.layer.shadowOpacity = 0.2
|
settingsButtonView.layer.shadowOpacity = 0.35
|
||||||
//self.addSubview(settingsButtonView)
|
//self.addSubview(settingsButtonView)
|
||||||
}
|
}
|
||||||
transition.setPosition(view: settingsButtonView, position: settingsButtonFrame.center)
|
transition.setPosition(view: settingsButtonView, position: settingsButtonFrame.center)
|
||||||
@ -1034,11 +1074,6 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
private let storyDimensions = CGSize(width: 1080.0, height: 1920.0)
|
private let storyDimensions = CGSize(width: 1080.0, height: 1920.0)
|
||||||
private let storyMaxVideoDuration: Double = 60.0
|
private let storyMaxVideoDuration: Double = 60.0
|
||||||
|
|
||||||
public enum MediaEditorResultPrivacy: Equatable {
|
|
||||||
case story(privacy: EngineStoryPrivacy, timeout: Int, archive: Bool)
|
|
||||||
case message(peers: [EnginePeer.Id], timeout: Int?)
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class MediaEditorScreen: ViewController {
|
public final class MediaEditorScreen: ViewController {
|
||||||
public enum TransitionIn {
|
public enum TransitionIn {
|
||||||
public final class GalleryTransitionIn {
|
public final class GalleryTransitionIn {
|
||||||
@ -1254,13 +1289,6 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.entitiesView.onInteractionUpdated = { [weak self] interacting in
|
|
||||||
if let self {
|
|
||||||
self.isInteractingWithEntities = interacting
|
|
||||||
self.requestUpdate(transition: .easeInOut(duration: 0.2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -1270,10 +1298,20 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
|
|
||||||
private func setup(with subject: MediaEditorScreen.Subject) {
|
private func setup(with subject: MediaEditorScreen.Subject) {
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
guard let _ = self.controller else {
|
guard let controller = self.controller else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isSavingAvailable: Bool
|
||||||
|
switch subject {
|
||||||
|
case .image, .video:
|
||||||
|
isSavingAvailable = true
|
||||||
|
default:
|
||||||
|
isSavingAvailable = false
|
||||||
|
}
|
||||||
|
controller.isSavingAvailable = isSavingAvailable
|
||||||
|
controller.requestLayout(transition: .immediate)
|
||||||
|
|
||||||
let mediaDimensions = subject.dimensions
|
let mediaDimensions = subject.dimensions
|
||||||
let maxSide: CGFloat = 1920.0 / UIScreen.main.scale
|
let maxSide: CGFloat = 1920.0 / UIScreen.main.scale
|
||||||
let fittedSize = mediaDimensions.cgSize.fitted(CGSize(width: maxSide, height: maxSide))
|
let fittedSize = mediaDimensions.cgSize.fitted(CGSize(width: maxSide, height: maxSide))
|
||||||
@ -1315,6 +1353,16 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
}
|
}
|
||||||
let mediaEditor = MediaEditor(subject: subject.editorSubject, values: initialValues, hasHistogram: true)
|
let mediaEditor = MediaEditor(subject: subject.editorSubject, values: initialValues, hasHistogram: true)
|
||||||
mediaEditor.attachPreviewView(self.previewView)
|
mediaEditor.attachPreviewView(self.previewView)
|
||||||
|
mediaEditor.valuesUpdated = { [weak self] values in
|
||||||
|
if let self, let controller = self.controller, values.gradientColors != nil, controller.previousSavedValues != values {
|
||||||
|
if !isSavingAvailable && controller.previousSavedValues == nil {
|
||||||
|
controller.previousSavedValues = values
|
||||||
|
} else {
|
||||||
|
controller.isSavingAvailable = true
|
||||||
|
controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.gradientColorsDisposable = mediaEditor.gradientColors.start(next: { [weak self] colors in
|
self.gradientColorsDisposable = mediaEditor.gradientColors.start(next: { [weak self] colors in
|
||||||
if let self, let colors {
|
if let self, let colors {
|
||||||
@ -1377,7 +1425,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
isVideo: false,
|
isVideo: false,
|
||||||
updateSelectedEntity: { [weak self] _ in
|
updateSelectedEntity: { [weak self] _ in
|
||||||
if let self {
|
if let self {
|
||||||
self.requestUpdate()
|
self.requestUpdate(transition: .easeInOut(duration: 0.2))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateVideoPlayback: { [weak self] isPlaying in
|
updateVideoPlayback: { [weak self] isPlaying in
|
||||||
@ -1395,6 +1443,12 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
selectedEntityView.update(animated: false)
|
selectedEntityView.update(animated: false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onInteractionUpdated: { [weak self] isInteracting in
|
||||||
|
if let self {
|
||||||
|
self.isInteractingWithEntities = isInteracting
|
||||||
|
self.requestUpdate(transition: .easeInOut(duration: 0.2))
|
||||||
|
}
|
||||||
|
},
|
||||||
getCurrentImage: {
|
getCurrentImage: {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -1415,6 +1469,13 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
@objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
if let panRecognizer = gestureRecognizer as? UIPanGestureRecognizer, panRecognizer.minimumNumberOfTouches == 1, panRecognizer.state == .changed {
|
||||||
|
return false
|
||||||
|
} else if let panRecognizer = otherGestureRecognizer as? UIPanGestureRecognizer, panRecognizer.minimumNumberOfTouches == 1, panRecognizer.state == .changed {
|
||||||
|
return false
|
||||||
|
} else if gestureRecognizer is UITapGestureRecognizer, (otherGestureRecognizer is UIPinchGestureRecognizer || otherGestureRecognizer is UIRotationGestureRecognizer) && otherGestureRecognizer.state == .changed {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1429,6 +1490,8 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var enhanceGestureOffset: CGFloat?
|
||||||
|
|
||||||
@objc func handleDismissPan(_ gestureRecognizer: UIPanGestureRecognizer) {
|
@objc func handleDismissPan(_ gestureRecognizer: UIPanGestureRecognizer) {
|
||||||
guard let controller = self.controller, let layout = self.validLayout, (layout.inputHeight ?? 0.0).isZero else {
|
guard let controller = self.controller, let layout = self.validLayout, (layout.inputHeight ?? 0.0).isZero else {
|
||||||
return
|
return
|
||||||
@ -1464,7 +1527,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
if let mediaEditor = self.mediaEditor {
|
if let mediaEditor = self.mediaEditor {
|
||||||
let value = mediaEditor.getToolValue(.enhance) as? Float ?? 0.0
|
let value = mediaEditor.getToolValue(.enhance) as? Float ?? 0.0
|
||||||
let delta = Float((translation.x / self.frame.width) * 1.5)
|
let delta = Float((translation.x / self.frame.width) * 1.5)
|
||||||
let updatedValue = max(0.0, min(1.0, value + delta))
|
let updatedValue = max(-1.0, min(1.0, value + delta))
|
||||||
mediaEditor.setToolValue(.enhance, value: updatedValue)
|
mediaEditor.setToolValue(.enhance, value: updatedValue)
|
||||||
}
|
}
|
||||||
self.requestUpdate()
|
self.requestUpdate()
|
||||||
@ -1509,8 +1572,15 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
if entitiesHitTestResult == nil {
|
if entitiesHitTestResult == nil {
|
||||||
if self.entitiesView.hasSelection {
|
if self.entitiesView.hasSelection {
|
||||||
self.entitiesView.selectEntity(nil)
|
self.entitiesView.selectEntity(nil)
|
||||||
}
|
|
||||||
self.view.endEditing(true)
|
self.view.endEditing(true)
|
||||||
|
} else {
|
||||||
|
if let layout = self.validLayout, (layout.inputHeight ?? 0.0) > 0.0 {
|
||||||
|
self.view.endEditing(true)
|
||||||
|
} else {
|
||||||
|
let textEntity = DrawingTextEntity(text: NSAttributedString(), style: .regular, animation: .none, font: .sanFrancisco, alignment: .center, fontSize: 1.0, color: DrawingColor(color: .white))
|
||||||
|
self.interaction?.insertEntity(textEntity)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1572,7 +1642,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
if let componentView = self.componentHost.view {
|
if let componentView = self.componentHost.view {
|
||||||
componentView.layer.animatePosition(from: sourceLocalFrame.center, to: componentView.center, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
componentView.layer.animatePosition(from: sourceLocalFrame.center, to: componentView.center, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
componentView.layer.animateScale(from: sourceScale, to: 1.0, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
componentView.layer.animateScale(from: sourceScale, to: 1.0, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
componentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
componentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1596,7 +1666,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
|
|
||||||
let previousDimAlpha = self.backgroundDimView.alpha
|
let previousDimAlpha = self.backgroundDimView.alpha
|
||||||
self.backgroundDimView.alpha = 0.0
|
self.backgroundDimView.alpha = 0.0
|
||||||
self.backgroundDimView.layer.animateAlpha(from: previousDimAlpha, to: 0.0, duration: 0.25)
|
self.backgroundDimView.layer.animateAlpha(from: previousDimAlpha, to: 0.0, duration: 0.15)
|
||||||
|
|
||||||
if finished, case .message = controller.state.privacy {
|
if finished, case .message = controller.state.privacy {
|
||||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
||||||
@ -1605,24 +1675,26 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
let transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut))
|
let transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut))
|
||||||
transition.setAlpha(view: self.previewContainerView, alpha: 0.0, completion: { _ in
|
transition.setAlpha(view: self.previewContainerView, alpha: 0.0, completion: { _ in
|
||||||
completion()
|
completion()
|
||||||
|
if let view = self.entitiesView.getView(where: { $0 is DrawingMediaEntityView }) as? DrawingMediaEntityView {
|
||||||
|
view.previewView = nil
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} else if let transitionOut = controller.transitionOut(finished), let destinationView = transitionOut.destinationView {
|
} else if let transitionOut = controller.transitionOut(finished), let destinationView = transitionOut.destinationView {
|
||||||
if !finished, let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
var destinationTransitionView: UIView?
|
||||||
|
if !finished {
|
||||||
if let transitionIn = controller.transitionIn, case let .gallery(galleryTransitionIn) = transitionIn, let sourceImage = galleryTransitionIn.sourceImage {
|
if let transitionIn = controller.transitionIn, case let .gallery(galleryTransitionIn) = transitionIn, let sourceImage = galleryTransitionIn.sourceImage {
|
||||||
let transitionOutView = UIImageView(image: sourceImage)
|
let sourceSuperView = galleryTransitionIn.sourceView?.superview?.superview
|
||||||
var initialScale: CGFloat
|
let destinationTransitionOutView = UIImageView(image: sourceImage)
|
||||||
if sourceImage.size.height > sourceImage.size.width {
|
destinationTransitionOutView.clipsToBounds = true
|
||||||
initialScale = max(self.previewContainerView.bounds.width / sourceImage.size.width, self.previewContainerView.bounds.height / sourceImage.size.height)
|
destinationTransitionOutView.contentMode = .scaleAspectFill
|
||||||
} else {
|
destinationTransitionOutView.frame = self.previewContainerView.convert(self.previewContainerView.bounds, to: sourceSuperView)
|
||||||
initialScale = self.previewContainerView.bounds.width / sourceImage.size.width
|
sourceSuperView?.addSubview(destinationTransitionOutView)
|
||||||
}
|
destinationTransitionView = destinationTransitionOutView
|
||||||
transitionOutView.center = CGPoint(x: self.previewContainerView.bounds.width / 2.0, y: self.previewContainerView.bounds.height / 2.0)
|
|
||||||
transitionOutView.transform = CGAffineTransformMakeScale(initialScale, initialScale)
|
|
||||||
transitionOutView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
|
||||||
self.previewContainerView.addSubview(transitionOutView)
|
|
||||||
}
|
}
|
||||||
|
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
||||||
view.animateOut(to: .gallery)
|
view.animateOut(to: .gallery)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let destinationLocalFrame = destinationView.convert(transitionOut.destinationRect, to: self.view)
|
let destinationLocalFrame = destinationView.convert(transitionOut.destinationRect, to: self.view)
|
||||||
let destinationScale = destinationLocalFrame.width / self.previewContainerView.frame.width
|
let destinationScale = destinationLocalFrame.width / self.previewContainerView.frame.width
|
||||||
let destinationAspectRatio = destinationLocalFrame.height / destinationLocalFrame.width
|
let destinationAspectRatio = destinationLocalFrame.height / destinationLocalFrame.width
|
||||||
@ -1657,10 +1729,21 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
destinationView.isHidden = false
|
destinationView.isHidden = false
|
||||||
destinationSnapshotView?.removeFromSuperview()
|
destinationSnapshotView?.removeFromSuperview()
|
||||||
completion()
|
completion()
|
||||||
|
if let view = self.entitiesView.getView(where: { $0 is DrawingMediaEntityView }) as? DrawingMediaEntityView {
|
||||||
|
view.previewView = nil
|
||||||
|
}
|
||||||
})
|
})
|
||||||
self.previewContainerView.layer.animateScale(from: 1.0, to: destinationScale, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
self.previewContainerView.layer.animateScale(from: 1.0, to: destinationScale, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||||
self.previewContainerView.layer.animateBounds(from: self.previewContainerView.bounds, to: CGRect(origin: CGPoint(x: 0.0, y: (self.previewContainerView.bounds.height - self.previewContainerView.bounds.width * destinationAspectRatio) / 2.0), size: CGSize(width: self.previewContainerView.bounds.width, height: self.previewContainerView.bounds.width * destinationAspectRatio)), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
self.previewContainerView.layer.animateBounds(from: self.previewContainerView.bounds, to: CGRect(origin: CGPoint(x: 0.0, y: (self.previewContainerView.bounds.height - self.previewContainerView.bounds.width * destinationAspectRatio) / 2.0), size: CGSize(width: self.previewContainerView.bounds.width, height: self.previewContainerView.bounds.width * destinationAspectRatio)), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||||
|
|
||||||
|
if let destinationTransitionView {
|
||||||
|
self.previewContainerView.layer.allowsGroupOpacity = true
|
||||||
|
self.previewContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||||
|
destinationTransitionView.layer.animateFrame(from: destinationTransitionView.frame, to: destinationView.convert(destinationView.bounds, to: destinationTransitionView.superview), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak destinationTransitionView] _ in
|
||||||
|
destinationTransitionView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let targetCornerRadius: CGFloat
|
let targetCornerRadius: CGFloat
|
||||||
if transitionOut.destinationCornerRadius > 0.0 {
|
if transitionOut.destinationCornerRadius > 0.0 {
|
||||||
targetCornerRadius = self.previewContainerView.bounds.width
|
targetCornerRadius = self.previewContainerView.bounds.width
|
||||||
@ -1681,8 +1764,9 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
componentView.clipsToBounds = true
|
componentView.clipsToBounds = true
|
||||||
componentView.layer.animatePosition(from: componentView.center, to: destinationLocalFrame.center, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
componentView.layer.animatePosition(from: componentView.center, to: destinationLocalFrame.center, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||||
componentView.layer.animateScale(from: 1.0, to: destinationScale, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
componentView.layer.animateScale(from: 1.0, to: destinationScale, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||||
|
componentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||||
|
if finished {
|
||||||
componentView.layer.animateBounds(from: componentView.bounds, to: CGRect(origin: CGPoint(x: 0.0, y: (componentView.bounds.height - componentView.bounds.width) / 2.0), size: CGSize(width: componentView.bounds.width, height: componentView.bounds.width)), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
componentView.layer.animateBounds(from: componentView.bounds, to: CGRect(origin: CGPoint(x: 0.0, y: (componentView.bounds.height - componentView.bounds.width) / 2.0), size: CGSize(width: componentView.bounds.width, height: componentView.bounds.width)), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||||
componentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
|
||||||
componentView.layer.animate(
|
componentView.layer.animate(
|
||||||
from: componentView.layer.cornerRadius as NSNumber,
|
from: componentView.layer.cornerRadius as NSNumber,
|
||||||
to: componentView.bounds.width / 2.0 as NSNumber,
|
to: componentView.bounds.width / 2.0 as NSNumber,
|
||||||
@ -1692,6 +1776,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
removeOnCompletion: false
|
removeOnCompletion: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if let transitionIn = controller.transitionIn, case .camera = transitionIn {
|
} else if let transitionIn = controller.transitionIn, case .camera = transitionIn {
|
||||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
||||||
view.animateOut(to: .camera)
|
view.animateOut(to: .camera)
|
||||||
@ -1911,6 +1996,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
context: self.context,
|
context: self.context,
|
||||||
isDisplayingTool: self.isDisplayingTool,
|
isDisplayingTool: self.isDisplayingTool,
|
||||||
isInteractingWithEntities: self.isInteractingWithEntities,
|
isInteractingWithEntities: self.isInteractingWithEntities,
|
||||||
|
isSavingAvailable: controller.isSavingAvailable,
|
||||||
isDismissing: self.isDismissing,
|
isDismissing: self.isDismissing,
|
||||||
mediaEditor: self.mediaEditor,
|
mediaEditor: self.mediaEditor,
|
||||||
privacy: controller.state.privacy,
|
privacy: controller.state.privacy,
|
||||||
@ -2039,7 +2125,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
ToolValueComponent(
|
ToolValueComponent(
|
||||||
title: "Enhance",
|
title: "Enhance",
|
||||||
value: "\(Int(enhanceValue * 100.0))"
|
value: "\(Int(abs(enhanceValue) * 100.0))"
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -2072,7 +2158,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
transition.setFrame(view: self.previewContainerView, frame: previewFrame)
|
transition.setFrame(view: self.previewContainerView, frame: previewFrame)
|
||||||
let entitiesViewScale = previewSize.width / storyDimensions.width
|
let entitiesViewScale = previewSize.width / storyDimensions.width
|
||||||
self.entitiesContainerView.transform = CGAffineTransformMakeScale(entitiesViewScale, entitiesViewScale)
|
self.entitiesContainerView.transform = CGAffineTransformMakeScale(entitiesViewScale, entitiesViewScale)
|
||||||
transition.setFrame(view: self.entitiesContainerView, frame: CGRect(origin: .zero, size: previewFrame.size))
|
self.entitiesContainerView.frame = CGRect(origin: .zero, size: previewFrame.size)
|
||||||
transition.setFrame(view: self.gradientView, frame: CGRect(origin: .zero, size: previewFrame.size))
|
transition.setFrame(view: self.gradientView, frame: CGRect(origin: .zero, size: previewFrame.size))
|
||||||
transition.setFrame(view: self.drawingView, frame: CGRect(origin: .zero, size: self.entitiesView.bounds.size))
|
transition.setFrame(view: self.drawingView, frame: CGRect(origin: .zero, size: self.entitiesView.bounds.size))
|
||||||
|
|
||||||
@ -2153,6 +2239,8 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
public var completion: (Int64, MediaEditorScreen.Result, MediaEditorResultPrivacy, @escaping (@escaping () -> Void) -> Void) -> Void = { _, _, _, _ in }
|
public var completion: (Int64, MediaEditorScreen.Result, MediaEditorResultPrivacy, @escaping (@escaping () -> Void) -> Void) -> Void = { _, _, _, _ in }
|
||||||
public var dismissed: () -> Void = { }
|
public var dismissed: () -> Void = { }
|
||||||
|
|
||||||
|
private let hapticFeedback = HapticFeedback()
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
subject: Signal<Subject?, NoError>,
|
subject: Signal<Subject?, NoError>,
|
||||||
@ -2190,6 +2278,8 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func openPrivacySettings() {
|
func openPrivacySettings() {
|
||||||
|
self.hapticFeedback.impact(.light)
|
||||||
|
|
||||||
if case .message(_, _) = self.state.privacy {
|
if case .message(_, _) = self.state.privacy {
|
||||||
self.openSendAsMessage()
|
self.openSendAsMessage()
|
||||||
} else {
|
} else {
|
||||||
@ -2308,6 +2398,8 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func presentTimeoutSetup(sourceView: UIView) {
|
func presentTimeoutSetup(sourceView: UIView) {
|
||||||
|
self.hapticFeedback.impact(.light)
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
let updateTimeout: (Int?, Bool) -> Void = { [weak self] timeout, archive in
|
let updateTimeout: (Int?, Bool) -> Void = { [weak self] timeout, archive in
|
||||||
@ -2328,7 +2420,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
let title: String
|
let title: String
|
||||||
switch self.state.privacy {
|
switch self.state.privacy {
|
||||||
case let .story(_, timeoutValue, archivedValue):
|
case let .story(_, timeoutValue, archivedValue):
|
||||||
title = "Choose how long the story will be kept."
|
title = "Choose how long the story will be visible."
|
||||||
currentValue = timeoutValue
|
currentValue = timeoutValue
|
||||||
currentArchived = archivedValue
|
currentArchived = archivedValue
|
||||||
case let .message(_, timeoutValue):
|
case let .message(_, timeoutValue):
|
||||||
@ -2368,13 +2460,18 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
|
|
||||||
updateTimeout(86400 * 2, false)
|
updateTimeout(86400 * 2, false)
|
||||||
})))
|
})))
|
||||||
items.append(.action(ContextMenuActionItem(text: "Forever", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: "Keep Always", icon: { theme in
|
||||||
return currentArchived ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
return currentArchived ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||||
}, action: { _, a in
|
}, action: { _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
|
|
||||||
updateTimeout(86400, true)
|
updateTimeout(86400, true)
|
||||||
})))
|
})))
|
||||||
|
items.append(.separator)
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "Select 'Keep Always' to always show the story in your profile.", textLayout: .multiline, textFont: .small, icon: { theme in
|
||||||
|
return nil
|
||||||
|
}, action: { _, _ in
|
||||||
|
})))
|
||||||
case .message:
|
case .message:
|
||||||
items.append(.action(ContextMenuActionItem(text: "Until First View", icon: { _ in
|
items.append(.action(ContextMenuActionItem(text: "Until First View", icon: { _ in
|
||||||
return nil
|
return nil
|
||||||
@ -2419,6 +2516,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func maybePresentDiscardAlert() {
|
func maybePresentDiscardAlert() {
|
||||||
|
self.hapticFeedback.impact(.light)
|
||||||
if "".isEmpty {
|
if "".isEmpty {
|
||||||
self.requestDismiss(saveDraft: false, animated: true)
|
self.requestDismiss(saveDraft: false, animated: true)
|
||||||
return
|
return
|
||||||
@ -2474,7 +2572,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let mediaEditor = self.node.mediaEditor {
|
if let mediaEditor = self.node.mediaEditor {
|
||||||
mediaEditor.stop()
|
mediaEditor.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cancelled(saveDraft)
|
self.cancelled(saveDraft)
|
||||||
@ -2491,7 +2589,10 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
}
|
}
|
||||||
try? FileManager.default.createDirectory(atPath: draftPath(), withIntermediateDirectories: true)
|
try? FileManager.default.createDirectory(atPath: draftPath(), withIntermediateDirectories: true)
|
||||||
|
|
||||||
|
let privacy = self.state.privacy
|
||||||
|
|
||||||
if let resultImage = self.node.mediaEditor?.resultImage {
|
if let resultImage = self.node.mediaEditor?.resultImage {
|
||||||
|
self.node.mediaEditor?.seek(0.0, andPlay: false)
|
||||||
makeEditorImageComposition(account: self.context.account, inputImage: resultImage, dimensions: storyDimensions, values: values, time: .zero, completion: { resultImage in
|
makeEditorImageComposition(account: self.context.account, inputImage: resultImage, dimensions: storyDimensions, values: values, time: .zero, completion: { resultImage in
|
||||||
guard let resultImage else {
|
guard let resultImage else {
|
||||||
return
|
return
|
||||||
@ -2503,7 +2604,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
let path = draftPath() + "/\(Int64.random(in: .min ... .max)).jpg"
|
let path = draftPath() + "/\(Int64.random(in: .min ... .max)).jpg"
|
||||||
if let data = image.jpegData(compressionQuality: 0.87) {
|
if let data = image.jpegData(compressionQuality: 0.87) {
|
||||||
try? data.write(to: URL(fileURLWithPath: path))
|
try? data.write(to: URL(fileURLWithPath: path))
|
||||||
let draft = MediaEditorDraft(path: path, isVideo: false, thumbnail: thumbnailImage, dimensions: dimensions, values: values)
|
let draft = MediaEditorDraft(path: path, isVideo: false, thumbnail: thumbnailImage, dimensions: dimensions, values: values, privacy: privacy)
|
||||||
if let id {
|
if let id {
|
||||||
saveStorySource(engine: self.context.engine, item: draft, id: id)
|
saveStorySource(engine: self.context.engine, item: draft, id: id)
|
||||||
} else {
|
} else {
|
||||||
@ -2516,10 +2617,13 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
let saveVideoDraft: (String, PixelDimensions) -> Void = { videoPath, dimensions in
|
let saveVideoDraft: (String, PixelDimensions) -> Void = { videoPath, dimensions in
|
||||||
if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
|
if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
|
||||||
let path = draftPath() + "/\(Int64.random(in: .min ... .max)).mp4"
|
let path = draftPath() + "/\(Int64.random(in: .min ... .max)).mp4"
|
||||||
_ = thumbnailImage
|
try? FileManager.default.moveItem(atPath: videoPath, toPath: path)
|
||||||
_ = path
|
let draft = MediaEditorDraft(path: path, isVideo: true, thumbnail: thumbnailImage, dimensions: dimensions, values: values, privacy: privacy)
|
||||||
_ = videoPath
|
if let id {
|
||||||
_ = dimensions
|
saveStorySource(engine: self.context.engine, item: draft, id: id)
|
||||||
|
} else {
|
||||||
|
addStoryDraft(engine: self.context.engine, item: draft)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2531,7 +2635,9 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
case let .asset(asset):
|
case let .asset(asset):
|
||||||
if asset.mediaType == .video {
|
if asset.mediaType == .video {
|
||||||
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
|
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
|
||||||
let _ = avAsset
|
if let urlAsset = avAsset as? AVURLAsset {
|
||||||
|
saveVideoDraft(urlAsset.url.absoluteString, PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let options = PHImageRequestOptions()
|
let options = PHImageRequestOptions()
|
||||||
@ -2544,9 +2650,9 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
}
|
}
|
||||||
case let .draft(draft, _):
|
case let .draft(draft, _):
|
||||||
if draft.isVideo {
|
if draft.isVideo {
|
||||||
|
saveVideoDraft(draft.path, draft.dimensions)
|
||||||
} else if let image = UIImage(contentsOfFile: draft.path) {
|
} else if let image = UIImage(contentsOfFile: draft.path) {
|
||||||
saveImageDraft(image, PixelDimensions(image.size))
|
saveImageDraft(image, draft.dimensions)
|
||||||
}
|
}
|
||||||
// if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
|
// if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
|
||||||
// removeStoryDraft(engine: self.context.engine, path: draft.path, delete: false)
|
// removeStoryDraft(engine: self.context.engine, path: draft.path, delete: false)
|
||||||
@ -2567,7 +2673,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
|
|
||||||
self.dismissAllTooltips()
|
self.dismissAllTooltips()
|
||||||
|
|
||||||
mediaEditor.stop()
|
mediaEditor.invalidate()
|
||||||
|
|
||||||
if let navigationController = self.navigationController as? NavigationController {
|
if let navigationController = self.navigationController as? NavigationController {
|
||||||
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
|
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
|
||||||
@ -2664,9 +2770,10 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
private var videoExport: MediaEditorVideoExport?
|
private var videoExport: MediaEditorVideoExport?
|
||||||
private var exportDisposable = MetaDisposable()
|
private var exportDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
fileprivate var isSavingAvailable = false
|
||||||
private var previousSavedValues: MediaEditorValues?
|
private var previousSavedValues: MediaEditorValues?
|
||||||
func requestSave() {
|
func requestSave() {
|
||||||
guard let mediaEditor = self.node.mediaEditor, let subject = self.node.subject else {
|
guard let mediaEditor = self.node.mediaEditor, let subject = self.node.subject, self.isSavingAvailable else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2675,10 +2782,14 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
||||||
|
|
||||||
if let previousSavedValues = self.previousSavedValues, mediaEditor.values == previousSavedValues {
|
if let previousSavedValues = self.previousSavedValues, mediaEditor.values == previousSavedValues {
|
||||||
self.node.presentSaveTooltip()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.hapticFeedback.impact(.light)
|
||||||
|
|
||||||
self.previousSavedValues = mediaEditor.values
|
self.previousSavedValues = mediaEditor.values
|
||||||
|
self.isSavingAvailable = false
|
||||||
|
self.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut))
|
||||||
|
|
||||||
let tempVideoPath = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max)).mp4"
|
let tempVideoPath = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max)).mp4"
|
||||||
let saveToPhotos: (String, Bool) -> Void = { path, isVideo in
|
let saveToPhotos: (String, Bool) -> Void = { path, isVideo in
|
||||||
@ -2998,6 +3109,10 @@ private final class ToolValueComponent: Component {
|
|||||||
)
|
)
|
||||||
if let titleView = self.title.view {
|
if let titleView = self.title.view {
|
||||||
if titleView.superview == nil {
|
if titleView.superview == nil {
|
||||||
|
titleView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
||||||
|
titleView.layer.shadowRadius = 3.0
|
||||||
|
titleView.layer.shadowColor = UIColor.black.cgColor
|
||||||
|
titleView.layer.shadowOpacity = 0.35
|
||||||
self.addSubview(titleView)
|
self.addSubview(titleView)
|
||||||
}
|
}
|
||||||
transition.setPosition(view: titleView, position: titleFrame.center)
|
transition.setPosition(view: titleView, position: titleFrame.center)
|
||||||
@ -3020,6 +3135,10 @@ private final class ToolValueComponent: Component {
|
|||||||
)
|
)
|
||||||
if let valueView = self.value.view {
|
if let valueView = self.value.view {
|
||||||
if valueView.superview == nil {
|
if valueView.superview == nil {
|
||||||
|
valueView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
||||||
|
valueView.layer.shadowRadius = 3.0
|
||||||
|
valueView.layer.shadowColor = UIColor.black.cgColor
|
||||||
|
valueView.layer.shadowOpacity = 0.35
|
||||||
self.addSubview(valueView)
|
self.addSubview(valueView)
|
||||||
}
|
}
|
||||||
transition.setPosition(view: valueView, position: valueFrame.center)
|
transition.setPosition(view: valueView, position: valueFrame.center)
|
||||||
|
|||||||
@ -692,6 +692,17 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
controller.mediaEditor.setToolValue(.blur, value: value)
|
controller.mediaEditor.setToolValue(.blur, value: value)
|
||||||
state?.updated()
|
state?.updated()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
isTrackingUpdated: { [weak self] isTracking in
|
||||||
|
if let self {
|
||||||
|
let transition: Transition
|
||||||
|
if isTracking {
|
||||||
|
transition = .immediate
|
||||||
|
} else {
|
||||||
|
transition = .easeInOut(duration: 0.25)
|
||||||
|
}
|
||||||
|
transition.setAlpha(view: self.optionsBackgroundView, alpha: isTracking ? 0.0 : 1.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -716,6 +727,20 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
controller.mediaEditor.setToolValue(.blur, value: value)
|
controller.mediaEditor.setToolValue(.blur, value: value)
|
||||||
state?.updated()
|
state?.updated()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
isTrackingUpdated: { [weak self] isTracking in
|
||||||
|
if let self {
|
||||||
|
let transition: Transition
|
||||||
|
if isTracking {
|
||||||
|
transition = .immediate
|
||||||
|
} else {
|
||||||
|
transition = .easeInOut(duration: 0.25)
|
||||||
|
}
|
||||||
|
transition.setAlpha(view: self.optionsBackgroundView, alpha: isTracking ? 0.0 : 1.0)
|
||||||
|
if let view = self.toolOptions.view {
|
||||||
|
transition.setAlpha(view: view, alpha: isTracking ? 0.0 : 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|||||||
@ -230,7 +230,8 @@ public final class MessageInputActionButtonComponent: Component {
|
|||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
if let image = UIImage(bundleImageName: "Media Editor/Apply"), let cgImage = image.cgImage {
|
if let image = UIImage(bundleImageName: "Media Editor/Apply"), let cgImage = image.cgImage {
|
||||||
context.setBlendMode(.clear)
|
context.setBlendMode(.copy)
|
||||||
|
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.35).cgColor)
|
||||||
context.clip(to: CGRect(origin: CGPoint(x: -4.0 + UIScreenPixel, y: -3.0 - UIScreenPixel), size: CGSize(width: 40.0, height: 40.0)), mask: cgImage)
|
context.clip(to: CGRect(origin: CGPoint(x: -4.0 + UIScreenPixel, y: -3.0 - UIScreenPixel), size: CGSize(width: 40.0, height: 40.0)), mask: cgImage)
|
||||||
context.fill(CGRect(origin: .zero, size: size))
|
context.fill(CGRect(origin: .zero, size: size))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -276,14 +276,16 @@ public final class StoryPeerListComponent: Component {
|
|||||||
hasUnseen = itemSet.hasUnseen
|
hasUnseen = itemSet.hasUnseen
|
||||||
|
|
||||||
var hasItems = true
|
var hasItems = true
|
||||||
var itemProgress: Float?
|
var itemRingAnimation: StoryPeerListItemComponent.RingAnimation?
|
||||||
if peer.id == component.context.account.peerId {
|
if peer.id == component.context.account.peerId {
|
||||||
if let storySubscriptions = component.storySubscriptions, let accountItem = storySubscriptions.accountItem {
|
if let storySubscriptions = component.storySubscriptions, let accountItem = storySubscriptions.accountItem {
|
||||||
hasItems = accountItem.storyCount != 0
|
hasItems = accountItem.storyCount != 0
|
||||||
} else {
|
} else {
|
||||||
hasItems = false
|
hasItems = false
|
||||||
}
|
}
|
||||||
itemProgress = component.uploadProgress
|
if let uploadProgress = component.uploadProgress {
|
||||||
|
itemRingAnimation = .progress(uploadProgress)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let collapsedItemX: CGFloat
|
let collapsedItemX: CGFloat
|
||||||
@ -360,7 +362,7 @@ public final class StoryPeerListComponent: Component {
|
|||||||
peer: peer,
|
peer: peer,
|
||||||
hasUnseen: hasUnseen,
|
hasUnseen: hasUnseen,
|
||||||
hasItems: hasItems,
|
hasItems: hasItems,
|
||||||
progress: itemProgress,
|
ringAnimation: itemRingAnimation,
|
||||||
collapseFraction: isReallyVisible ? component.collapseFraction : 0.0,
|
collapseFraction: isReallyVisible ? component.collapseFraction : 0.0,
|
||||||
collapsedScaleFactor: collapsedItemScaleFactor,
|
collapsedScaleFactor: collapsedItemScaleFactor,
|
||||||
collapsedWidth: collapsedItemWidth,
|
collapsedWidth: collapsedItemWidth,
|
||||||
|
|||||||
@ -74,25 +74,46 @@ private func calculateMergingCircleShape(center: CGPoint, leftCenter: CGPoint?,
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class StoryProgressLayer: SimpleShapeLayer {
|
private final class StoryProgressLayer: SimpleLayer {
|
||||||
|
enum Value: Equatable {
|
||||||
|
case indefinite
|
||||||
|
case progress(Float)
|
||||||
|
}
|
||||||
|
|
||||||
private struct Params: Equatable {
|
private struct Params: Equatable {
|
||||||
var size: CGSize
|
var size: CGSize
|
||||||
var lineWidth: CGFloat
|
var lineWidth: CGFloat
|
||||||
var progress: Float
|
var value: Value
|
||||||
}
|
}
|
||||||
|
|
||||||
private var currentParams: Params?
|
private var currentParams: Params?
|
||||||
|
|
||||||
|
private let uploadProgressLayer = SimpleShapeLayer()
|
||||||
|
|
||||||
|
private let indefiniteDashLayer = SimpleShapeLayer()
|
||||||
|
private let indefiniteReplicatorLayer = CAReplicatorLayer()
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.fillColor = UIColor.white.cgColor
|
self.uploadProgressLayer.fillColor = nil
|
||||||
self.fillRule = .evenOdd
|
self.uploadProgressLayer.strokeColor = UIColor.white.cgColor
|
||||||
|
self.uploadProgressLayer.lineWidth = 2.0
|
||||||
|
self.uploadProgressLayer.lineCap = .round
|
||||||
|
|
||||||
self.fillColor = nil
|
self.indefiniteDashLayer.fillColor = nil
|
||||||
self.strokeColor = UIColor.white.cgColor
|
self.indefiniteDashLayer.strokeColor = UIColor.white.cgColor
|
||||||
self.lineWidth = 2.0
|
self.indefiniteDashLayer.lineWidth = 2.0
|
||||||
self.lineCap = .round
|
self.indefiniteDashLayer.lineCap = .round
|
||||||
|
self.indefiniteDashLayer.lineJoin = .round
|
||||||
|
self.indefiniteDashLayer.strokeEnd = 0.0333
|
||||||
|
|
||||||
|
let count = 1.0 / self.indefiniteDashLayer.strokeEnd
|
||||||
|
let angle = (2.0 * Double.pi) / Double(count)
|
||||||
|
self.indefiniteReplicatorLayer.addSublayer(self.indefiniteDashLayer)
|
||||||
|
self.indefiniteReplicatorLayer.instanceCount = Int(count)
|
||||||
|
self.indefiniteReplicatorLayer.instanceTransform = CATransform3DMakeRotation(CGFloat(angle), 0.0, 0.0, 1.0)
|
||||||
|
self.indefiniteReplicatorLayer.transform = CATransform3DMakeRotation(-.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
|
self.indefiniteReplicatorLayer.instanceDelay = 0.025
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(layer: Any) {
|
override init(layer: Any) {
|
||||||
@ -105,14 +126,15 @@ private final class StoryProgressLayer: SimpleShapeLayer {
|
|||||||
|
|
||||||
func reset() {
|
func reset() {
|
||||||
self.currentParams = nil
|
self.currentParams = nil
|
||||||
self.path = nil
|
self.indefiniteDashLayer.path = nil
|
||||||
|
self.uploadProgressLayer.path = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, lineWidth: CGFloat, progress: Float, transition: Transition) {
|
func update(size: CGSize, lineWidth: CGFloat, value: Value, transition: Transition) {
|
||||||
let params = Params(
|
let params = Params(
|
||||||
size: size,
|
size: size,
|
||||||
lineWidth: lineWidth,
|
lineWidth: lineWidth,
|
||||||
progress: progress
|
value: value
|
||||||
)
|
)
|
||||||
if self.currentParams == params {
|
if self.currentParams == params {
|
||||||
return
|
return
|
||||||
@ -120,23 +142,70 @@ private final class StoryProgressLayer: SimpleShapeLayer {
|
|||||||
self.currentParams = params
|
self.currentParams = params
|
||||||
|
|
||||||
let lineWidth: CGFloat = 2.0
|
let lineWidth: CGFloat = 2.0
|
||||||
|
let bounds = CGRect(origin: .zero, size: size)
|
||||||
if self.path == nil {
|
if self.uploadProgressLayer.path == nil {
|
||||||
let path = CGMutablePath()
|
let path = CGMutablePath()
|
||||||
path.addEllipse(in: CGRect(origin: CGPoint(x: lineWidth * 0.5, y: lineWidth * 0.5), size: CGSize(width: size.width - lineWidth, height: size.height - lineWidth)))
|
path.addEllipse(in: CGRect(origin: CGPoint(x: lineWidth * 0.5, y: lineWidth * 0.5), size: CGSize(width: size.width - lineWidth, height: size.height - lineWidth)))
|
||||||
self.path = path
|
self.uploadProgressLayer.path = path
|
||||||
|
self.uploadProgressLayer.frame = bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.setShapeLayerStrokeEnd(layer: self, strokeEnd: CGFloat(progress))
|
if self.indefiniteDashLayer.path == nil {
|
||||||
|
let path = CGMutablePath()
|
||||||
|
path.addEllipse(in: CGRect(origin: CGPoint(x: lineWidth * 0.5, y: lineWidth * 0.5), size: CGSize(width: size.width - lineWidth, height: size.height - lineWidth)))
|
||||||
|
self.indefiniteDashLayer.path = path
|
||||||
|
self.indefiniteReplicatorLayer.frame = bounds
|
||||||
|
self.indefiniteDashLayer.frame = bounds
|
||||||
|
}
|
||||||
|
|
||||||
if self.animation(forKey: "rotation") == nil {
|
switch value {
|
||||||
let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
case let .progress(progress):
|
||||||
basicAnimation.duration = 2.0
|
if self.indefiniteReplicatorLayer.superlayer != nil {
|
||||||
basicAnimation.fromValue = NSNumber(value: Float(0.0))
|
self.indefiniteReplicatorLayer.removeFromSuperlayer()
|
||||||
basicAnimation.toValue = NSNumber(value: Float(Double.pi * 2.0))
|
}
|
||||||
basicAnimation.repeatCount = Float.infinity
|
if self.uploadProgressLayer.superlayer == nil {
|
||||||
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
self.addSublayer(self.uploadProgressLayer)
|
||||||
self.add(basicAnimation, forKey: "rotation")
|
}
|
||||||
|
transition.setShapeLayerStrokeEnd(layer: self.uploadProgressLayer, strokeEnd: CGFloat(progress))
|
||||||
|
if self.uploadProgressLayer.animation(forKey: "rotation") == nil {
|
||||||
|
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
||||||
|
rotationAnimation.duration = 2.0
|
||||||
|
rotationAnimation.fromValue = NSNumber(value: Float(0.0))
|
||||||
|
rotationAnimation.toValue = NSNumber(value: Float(Double.pi * 2.0))
|
||||||
|
rotationAnimation.repeatCount = Float.infinity
|
||||||
|
rotationAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
||||||
|
self.uploadProgressLayer.add(rotationAnimation, forKey: "rotation")
|
||||||
|
}
|
||||||
|
case .indefinite:
|
||||||
|
if self.uploadProgressLayer.superlayer == nil {
|
||||||
|
self.uploadProgressLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
if self.indefiniteReplicatorLayer.superlayer == nil {
|
||||||
|
self.addSublayer(self.indefiniteReplicatorLayer)
|
||||||
|
}
|
||||||
|
if self.indefiniteReplicatorLayer.animation(forKey: "rotation") == nil {
|
||||||
|
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
||||||
|
rotationAnimation.duration = 4.0
|
||||||
|
rotationAnimation.fromValue = NSNumber(value: -.pi / 2.0)
|
||||||
|
rotationAnimation.toValue = NSNumber(value: -.pi / 2.0 + Double.pi * 2.0)
|
||||||
|
rotationAnimation.repeatCount = Float.infinity
|
||||||
|
rotationAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
||||||
|
self.indefiniteReplicatorLayer.add(rotationAnimation, forKey: "rotation")
|
||||||
|
}
|
||||||
|
if self.indefiniteDashLayer.animation(forKey: "dash") == nil {
|
||||||
|
let dashAnimation = CAKeyframeAnimation(keyPath: "strokeStart")
|
||||||
|
dashAnimation.keyTimes = [0.0, 0.45, 0.55, 1.0]
|
||||||
|
dashAnimation.values = [
|
||||||
|
self.indefiniteDashLayer.strokeStart,
|
||||||
|
self.indefiniteDashLayer.strokeEnd,
|
||||||
|
self.indefiniteDashLayer.strokeEnd,
|
||||||
|
self.indefiniteDashLayer.strokeStart,
|
||||||
|
]
|
||||||
|
dashAnimation.timingFunction = CAMediaTimingFunction(name: .linear)
|
||||||
|
dashAnimation.duration = 2.5
|
||||||
|
dashAnimation.repeatCount = .infinity
|
||||||
|
self.indefiniteDashLayer.add(dashAnimation, forKey: "dash")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,13 +213,18 @@ private final class StoryProgressLayer: SimpleShapeLayer {
|
|||||||
private var sharedAvatarBackgroundImage: UIImage?
|
private var sharedAvatarBackgroundImage: UIImage?
|
||||||
|
|
||||||
public final class StoryPeerListItemComponent: Component {
|
public final class StoryPeerListItemComponent: Component {
|
||||||
|
public enum RingAnimation: Equatable {
|
||||||
|
case progress(Float)
|
||||||
|
case loading
|
||||||
|
}
|
||||||
|
|
||||||
public let context: AccountContext
|
public let context: AccountContext
|
||||||
public let theme: PresentationTheme
|
public let theme: PresentationTheme
|
||||||
public let strings: PresentationStrings
|
public let strings: PresentationStrings
|
||||||
public let peer: EnginePeer
|
public let peer: EnginePeer
|
||||||
public let hasUnseen: Bool
|
public let hasUnseen: Bool
|
||||||
public let hasItems: Bool
|
public let hasItems: Bool
|
||||||
public let progress: Float?
|
public let ringAnimation: RingAnimation?
|
||||||
public let collapseFraction: CGFloat
|
public let collapseFraction: CGFloat
|
||||||
public let collapsedScaleFactor: CGFloat
|
public let collapsedScaleFactor: CGFloat
|
||||||
public let collapsedWidth: CGFloat
|
public let collapsedWidth: CGFloat
|
||||||
@ -166,7 +240,7 @@ public final class StoryPeerListItemComponent: Component {
|
|||||||
peer: EnginePeer,
|
peer: EnginePeer,
|
||||||
hasUnseen: Bool,
|
hasUnseen: Bool,
|
||||||
hasItems: Bool,
|
hasItems: Bool,
|
||||||
progress: Float?,
|
ringAnimation: RingAnimation?,
|
||||||
collapseFraction: CGFloat,
|
collapseFraction: CGFloat,
|
||||||
collapsedScaleFactor: CGFloat,
|
collapsedScaleFactor: CGFloat,
|
||||||
collapsedWidth: CGFloat,
|
collapsedWidth: CGFloat,
|
||||||
@ -181,7 +255,7 @@ public final class StoryPeerListItemComponent: Component {
|
|||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.hasUnseen = hasUnseen
|
self.hasUnseen = hasUnseen
|
||||||
self.hasItems = hasItems
|
self.hasItems = hasItems
|
||||||
self.progress = progress
|
self.ringAnimation = ringAnimation
|
||||||
self.collapseFraction = collapseFraction
|
self.collapseFraction = collapseFraction
|
||||||
self.collapsedScaleFactor = collapsedScaleFactor
|
self.collapsedScaleFactor = collapsedScaleFactor
|
||||||
self.collapsedWidth = collapsedWidth
|
self.collapsedWidth = collapsedWidth
|
||||||
@ -210,7 +284,7 @@ public final class StoryPeerListItemComponent: Component {
|
|||||||
if lhs.hasItems != rhs.hasItems {
|
if lhs.hasItems != rhs.hasItems {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.progress != rhs.progress {
|
if lhs.ringAnimation != rhs.ringAnimation {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.collapseFraction != rhs.collapseFraction {
|
if lhs.collapseFraction != rhs.collapseFraction {
|
||||||
@ -255,6 +329,8 @@ public final class StoryPeerListItemComponent: Component {
|
|||||||
private var component: StoryPeerListItemComponent?
|
private var component: StoryPeerListItemComponent?
|
||||||
private weak var componentState: EmptyComponentState?
|
private weak var componentState: EmptyComponentState?
|
||||||
|
|
||||||
|
private var demoLoading = false
|
||||||
|
|
||||||
public override init(frame: CGRect) {
|
public override init(frame: CGRect) {
|
||||||
self.backgroundContainer = UIView()
|
self.backgroundContainer = UIView()
|
||||||
self.backgroundContainer.isUserInteractionEnabled = false
|
self.backgroundContainer.isUserInteractionEnabled = false
|
||||||
@ -378,7 +454,7 @@ public final class StoryPeerListItemComponent: Component {
|
|||||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
|
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
|
||||||
let hadUnseen = self.component?.hasUnseen
|
let hadUnseen = self.component?.hasUnseen
|
||||||
let hadProgress = self.component?.progress != nil
|
let hadProgress = self.component?.ringAnimation != nil
|
||||||
let themeUpdated = self.component?.theme !== component.theme
|
let themeUpdated = self.component?.theme !== component.theme
|
||||||
|
|
||||||
let previousComponent = self.component
|
let previousComponent = self.component
|
||||||
@ -411,7 +487,7 @@ public final class StoryPeerListItemComponent: Component {
|
|||||||
}
|
}
|
||||||
self.avatarBackgroundView.image = avatarBackgroundImage
|
self.avatarBackgroundView.image = avatarBackgroundImage
|
||||||
|
|
||||||
self.avatarBackgroundView.isHidden = component.progress != nil
|
self.avatarBackgroundView.isHidden = component.ringAnimation != nil
|
||||||
|
|
||||||
if themeUpdated {
|
if themeUpdated {
|
||||||
self.avatarBackgroundView.tintColor = component.theme.rootController.navigationBar.opaqueBackgroundColor
|
self.avatarBackgroundView.tintColor = component.theme.rootController.navigationBar.opaqueBackgroundColor
|
||||||
@ -426,7 +502,7 @@ public final class StoryPeerListItemComponent: Component {
|
|||||||
|
|
||||||
let baseLineWidth: CGFloat
|
let baseLineWidth: CGFloat
|
||||||
let minimizedLineWidth: CGFloat = 3.0
|
let minimizedLineWidth: CGFloat = 3.0
|
||||||
if component.hasUnseen || component.progress != nil {
|
if component.hasUnseen || component.ringAnimation != nil {
|
||||||
baseLineWidth = 2.0
|
baseLineWidth = 2.0
|
||||||
} else {
|
} else {
|
||||||
baseLineWidth = 1.0 + UIScreenPixel
|
baseLineWidth = 1.0 + UIScreenPixel
|
||||||
@ -450,7 +526,7 @@ public final class StoryPeerListItemComponent: Component {
|
|||||||
transition.setScale(view: self.avatarContainer, scale: scaledAvatarSize / avatarSize.width)
|
transition.setScale(view: self.avatarContainer, scale: scaledAvatarSize / avatarSize.width)
|
||||||
transition.setScale(view: self.avatarBackgroundContainer, scale: scaledAvatarSize / avatarSize.width)
|
transition.setScale(view: self.avatarBackgroundContainer, scale: scaledAvatarSize / avatarSize.width)
|
||||||
|
|
||||||
if component.peer.id == component.context.account.peerId && !component.hasItems && component.progress == nil {
|
if component.peer.id == component.context.account.peerId && !component.hasItems && component.ringAnimation == nil {
|
||||||
self.indicatorColorLayer.isHidden = true
|
self.indicatorColorLayer.isHidden = true
|
||||||
|
|
||||||
let avatarAddBadgeView: UIImageView
|
let avatarAddBadgeView: UIImageView
|
||||||
@ -503,11 +579,11 @@ public final class StoryPeerListItemComponent: Component {
|
|||||||
|
|
||||||
self.indicatorShapeLayer.lineWidth = indicatorLineWidth
|
self.indicatorShapeLayer.lineWidth = indicatorLineWidth
|
||||||
|
|
||||||
if hadUnseen != component.hasUnseen || hadProgress != (component.progress != nil) {
|
if hadUnseen != component.hasUnseen || hadProgress != (component.ringAnimation != nil) {
|
||||||
let locations: [CGFloat] = [0.0, 1.0]
|
let locations: [CGFloat] = [0.0, 1.0]
|
||||||
let colors: [CGColor]
|
let colors: [CGColor]
|
||||||
|
|
||||||
if component.hasUnseen || component.progress != nil {
|
if component.hasUnseen || component.ringAnimation != nil {
|
||||||
colors = [UIColor(rgb: 0x34C76F).cgColor, UIColor(rgb: 0x3DA1FD).cgColor]
|
colors = [UIColor(rgb: 0x34C76F).cgColor, UIColor(rgb: 0x3DA1FD).cgColor]
|
||||||
} else {
|
} else {
|
||||||
colors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor]
|
colors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor]
|
||||||
@ -537,7 +613,7 @@ public final class StoryPeerListItemComponent: Component {
|
|||||||
|
|
||||||
let avatarPath = CGMutablePath()
|
let avatarPath = CGMutablePath()
|
||||||
avatarPath.addEllipse(in: CGRect(origin: CGPoint(), size: avatarSize).insetBy(dx: -1.0, dy: -1.0))
|
avatarPath.addEllipse(in: CGRect(origin: CGPoint(), size: avatarSize).insetBy(dx: -1.0, dy: -1.0))
|
||||||
if component.peer.id == component.context.account.peerId && !component.hasItems && component.progress == nil {
|
if component.peer.id == component.context.account.peerId && !component.hasItems && component.ringAnimation == nil {
|
||||||
let cutoutSize: CGFloat = 18.0 + UIScreenPixel * 2.0
|
let cutoutSize: CGFloat = 18.0 + UIScreenPixel * 2.0
|
||||||
avatarPath.addEllipse(in: CGRect(origin: CGPoint(x: avatarSize.width - cutoutSize + UIScreenPixel, y: avatarSize.height - 1.0 - cutoutSize + UIScreenPixel), size: CGSize(width: cutoutSize, height: cutoutSize)))
|
avatarPath.addEllipse(in: CGRect(origin: CGPoint(x: avatarSize.width - cutoutSize + UIScreenPixel, y: avatarSize.height - 1.0 - cutoutSize + UIScreenPixel), size: CGSize(width: cutoutSize, height: cutoutSize)))
|
||||||
} else if let mappedRightCenter {
|
} else if let mappedRightCenter {
|
||||||
@ -550,7 +626,7 @@ public final class StoryPeerListItemComponent: Component {
|
|||||||
//TODO:localize
|
//TODO:localize
|
||||||
let titleString: String
|
let titleString: String
|
||||||
if component.peer.id == component.context.account.peerId {
|
if component.peer.id == component.context.account.peerId {
|
||||||
if let _ = component.progress {
|
if let ringAnimation = component.ringAnimation, case .progress = ringAnimation {
|
||||||
titleString = "Uploading..."
|
titleString = "Uploading..."
|
||||||
} else {
|
} else {
|
||||||
titleString = "My story"
|
titleString = "My story"
|
||||||
@ -560,7 +636,7 @@ public final class StoryPeerListItemComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var titleTransition = transition
|
var titleTransition = transition
|
||||||
if previousComponent?.progress != nil && component.progress == nil {
|
if previousComponent?.ringAnimation != nil && component.ringAnimation == nil {
|
||||||
if let titleView = self.title.view, let snapshotView = titleView.snapshotContentTree() {
|
if let titleView = self.title.view, let snapshotView = titleView.snapshotContentTree() {
|
||||||
titleView.superview?.addSubview(snapshotView)
|
titleView.superview?.addSubview(snapshotView)
|
||||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||||
@ -590,7 +666,7 @@ public final class StoryPeerListItemComponent: Component {
|
|||||||
transition.setAlpha(view: titleView, alpha: 1.0 - component.collapseFraction)
|
transition.setAlpha(view: titleView, alpha: 1.0 - component.collapseFraction)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let progress = component.progress {
|
if let ringAnimation = component.ringAnimation {
|
||||||
var progressTransition = transition
|
var progressTransition = transition
|
||||||
let progressLayer: StoryProgressLayer
|
let progressLayer: StoryProgressLayer
|
||||||
if let current = self.progressLayer {
|
if let current = self.progressLayer {
|
||||||
@ -603,8 +679,13 @@ public final class StoryPeerListItemComponent: Component {
|
|||||||
}
|
}
|
||||||
let progressFrame = CGRect(origin: CGPoint(), size: indicatorFrame.size).insetBy(dx: 2.0, dy: 2.0)
|
let progressFrame = CGRect(origin: CGPoint(), size: indicatorFrame.size).insetBy(dx: 2.0, dy: 2.0)
|
||||||
progressTransition.setFrame(layer: progressLayer, frame: progressFrame)
|
progressTransition.setFrame(layer: progressLayer, frame: progressFrame)
|
||||||
progressLayer.update(size: progressFrame.size, lineWidth: 4.0, progress: progress, transition: transition)
|
|
||||||
|
|
||||||
|
switch ringAnimation {
|
||||||
|
case let .progress(progress):
|
||||||
|
progressLayer.update(size: progressFrame.size, lineWidth: 4.0, value: .progress(progress), transition: transition)
|
||||||
|
case .loading:
|
||||||
|
progressLayer.update(size: progressFrame.size, lineWidth: 4.0, value: .indefinite, transition: transition)
|
||||||
|
}
|
||||||
self.indicatorShapeLayer.opacity = 0.0
|
self.indicatorShapeLayer.opacity = 0.0
|
||||||
} else {
|
} else {
|
||||||
self.indicatorShapeLayer.opacity = 1.0
|
self.indicatorShapeLayer.opacity = 1.0
|
||||||
|
|||||||
@ -294,9 +294,10 @@ private final class PeerInfoScreenContactInfoItemNode: PeerInfoScreenItemNode {
|
|||||||
var height = topOffset * 2.0
|
var height = topOffset * 2.0
|
||||||
let usernameFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: usernameSize)
|
let usernameFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: usernameSize)
|
||||||
let phoneFrame = CGRect(origin: CGPoint(x: usernameSize.width > 0.0 ? width - sideInset - phoneSize.width : sideInset, y: topOffset), size: phoneSize)
|
let phoneFrame = CGRect(origin: CGPoint(x: usernameSize.width > 0.0 ? width - sideInset - phoneSize.width : sideInset, y: topOffset), size: phoneSize)
|
||||||
height += max(usernameSize.height, phoneSize.height)
|
let textHeight = max(usernameSize.height, phoneSize.height)
|
||||||
|
height += textHeight
|
||||||
|
|
||||||
let additionalTextFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: additionalTextSize)
|
let additionalTextFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset + textHeight + 3.0), size: additionalTextSize)
|
||||||
transition.updateFrame(node: self.usernameNode, frame: usernameFrame)
|
transition.updateFrame(node: self.usernameNode, frame: usernameFrame)
|
||||||
|
|
||||||
transition.updateFrame(node: self.phoneNumberNode, frame: phoneFrame)
|
transition.updateFrame(node: self.phoneNumberNode, frame: phoneFrame)
|
||||||
@ -385,7 +386,7 @@ private final class PeerInfoScreenContactInfoItemNode: PeerInfoScreenItemNode {
|
|||||||
if let current = self.linkHighlightingNode {
|
if let current = self.linkHighlightingNode {
|
||||||
linkHighlightingNode = current
|
linkHighlightingNode = current
|
||||||
} else {
|
} else {
|
||||||
linkHighlightingNode = LinkHighlightingNode(color: theme.list.itemAccentColor.withAlphaComponent(0.5))
|
linkHighlightingNode = LinkHighlightingNode(color: theme.list.itemAccentColor.withAlphaComponent(0.2))
|
||||||
self.linkHighlightingNode = linkHighlightingNode
|
self.linkHighlightingNode = linkHighlightingNode
|
||||||
self.contextSourceNode.contentNode.insertSubnode(linkHighlightingNode, belowSubnode: textNode)
|
self.contextSourceNode.contentNode.insertSubnode(linkHighlightingNode, belowSubnode: textNode)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -635,7 +635,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
|
|||||||
if let current = self.linkHighlightingNode {
|
if let current = self.linkHighlightingNode {
|
||||||
linkHighlightingNode = current
|
linkHighlightingNode = current
|
||||||
} else {
|
} else {
|
||||||
linkHighlightingNode = LinkHighlightingNode(color: theme.list.itemAccentColor.withAlphaComponent(0.5))
|
linkHighlightingNode = LinkHighlightingNode(color: theme.list.itemAccentColor.withAlphaComponent(0.2))
|
||||||
self.linkHighlightingNode = linkHighlightingNode
|
self.linkHighlightingNode = linkHighlightingNode
|
||||||
self.contextSourceNode.contentNode.insertSubnode(linkHighlightingNode, belowSubnode: textNode)
|
self.contextSourceNode.contentNode.insertSubnode(linkHighlightingNode, belowSubnode: textNode)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -431,7 +431,7 @@ class UpdateInfoItemNode: ListViewItemNode {
|
|||||||
if let current = self.linkHighlightingNode {
|
if let current = self.linkHighlightingNode {
|
||||||
linkHighlightingNode = current
|
linkHighlightingNode = current
|
||||||
} else {
|
} else {
|
||||||
linkHighlightingNode = LinkHighlightingNode(color: item.theme.list.itemAccentColor.withAlphaComponent(0.5))
|
linkHighlightingNode = LinkHighlightingNode(color: item.theme.list.itemAccentColor.withAlphaComponent(0.2))
|
||||||
self.linkHighlightingNode = linkHighlightingNode
|
self.linkHighlightingNode = linkHighlightingNode
|
||||||
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
|
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -544,7 +544,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
|||||||
if let queryItems = components.queryItems {
|
if let queryItems = components.queryItems {
|
||||||
for queryItem in queryItems {
|
for queryItem in queryItems {
|
||||||
if let value = queryItem.value {
|
if let value = queryItem.value {
|
||||||
if queryItem.name == "startApp"{
|
if queryItem.name == "startapp" {
|
||||||
startApp = value
|
startApp = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user