diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index c5773b83cd..b974fab2a8 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9357,3 +9357,5 @@ Sorry for the inconvenience."; "Privacy.Bio.NeverShareWith.Title" = "Never Share With"; "Conversation.OpenLink" = "OPEN LINK"; + +"Paint.Flip" = "Flip"; diff --git a/submodules/AttachmentUI/Sources/AttachmentContainer.swift b/submodules/AttachmentUI/Sources/AttachmentContainer.swift index 7d64e0d889..7b934c28b3 100644 --- a/submodules/AttachmentUI/Sources/AttachmentContainer.swift +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -540,7 +540,15 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { controller.setIgnoreAppearanceMethodInvocations(false) controller.viewDidDisappear(transition.isAnimated) } + if let (layout, _, coveredByModalTransition) = self.validLayout { + self.update(layout: layout, controllers: [], coveredByModalTransition: coveredByModalTransition, transition: .immediate) + } completion() + + var bounds = self.bounds + bounds.origin.y = 0.0 + self.bounds = bounds + return transition } } diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index fb64eed88a..12fc208a8a 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -207,7 +207,7 @@ public class AttachmentController: ViewController { private weak var controller: AttachmentController? private let dim: ASDisplayNode private let shadowNode: ASImageNode - private let container: AttachmentContainer + fileprivate let container: AttachmentContainer private let makeEntityInputView: () -> AttachmentTextInputPanelInputView? let panel: AttachmentPanel @@ -216,7 +216,7 @@ public class AttachmentController: ViewController { private var validLayout: ContainerViewLayout? private var modalProgress: CGFloat = 0.0 - private var isDismissing = false + fileprivate var isDismissing = false private let captionDisposable = MetaDisposable() private let mediaSelectionCountDisposable = MetaDisposable() @@ -313,6 +313,10 @@ public class AttachmentController: ViewController { self.container.updateModalProgress = { [weak self] progress, transition in 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.modalProgress = progress @@ -645,7 +649,7 @@ public class AttachmentController: ViewController { } else { 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) 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) - 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) if fromMenu, let _ = controller.getInputContainerNode() { @@ -965,12 +969,17 @@ public class AttachmentController: ViewController { self?.didDismiss() self?._dismiss() completion?() + self?.dismissedFlag = false + self?.node.isDismissing = false + self?.node.container.removeFromSupernode() }) } } else { self.didDismiss() self._dismiss() completion?() + self.node.isDismissing = false + self.node.container.removeFromSupernode() } } diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift index d689128673..4ba5cee279 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift @@ -179,7 +179,7 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel 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 if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift index 3cc65db389..dfe138a708 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift @@ -577,7 +577,7 @@ private final class RecurrentConfirmationNode: ASDisplayNode { 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( presentationData.strings.Bot_AccepRecurrentInfo(botName).string, diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index 341740e7ac..dd9ca262dc 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -157,6 +157,12 @@ private final class CameraContext { } } } + + private var _positionPromise = ValuePromise(.unspecified) + var position: Signal { + return self._positionPromise.get() + } + func togglePosition() { self.configure { self.input.invalidate(for: self.session) @@ -166,6 +172,7 @@ private final class CameraContext { } else { targetPosition = .back } + self._positionPromise.set(targetPosition) self.changingPosition = true self.device.configure(for: self.session, position: targetPosition) 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) { self.configure { + self._positionPromise.set(position) self.input.invalidate(for: self.session) self.device.configure(for: self.session, position: position) self.input.configure(for: self.session, device: self.device, audio: self.initialConfiguration.audio) @@ -322,6 +330,22 @@ public final class Camera { #endif } + public var position: Signal { + 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() { self.queue.async { if let context = self.contextRef?.takeUnretainedValue() { diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index bb45f15011..766a10f289 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -1255,39 +1255,42 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele } } - var applyNodeAsCurrent: ChatListFilterTabEntryId? - - if let directionIsToRight = directionIsToRight { - var updatedIndex = selectedIndex - if directionIsToRight { - updatedIndex = min(updatedIndex + 1, maxFilterIndex) - } else { - updatedIndex = max(updatedIndex - 1, 0) + let hasStoryCameraTransition = self.controller?.hasStoryCameraTransition ?? false + if hasStoryCameraTransition { + self.controller?.storyCameraPanGestureEnded(transitionFraction: translation.x / layout.size.width, velocity: velocity.x) + } else { + var applyNodeAsCurrent: ChatListFilterTabEntryId? + + if let directionIsToRight = directionIsToRight { + var updatedIndex = selectedIndex + if directionIsToRight { + updatedIndex = min(updatedIndex + 1, maxFilterIndex) + } else { + updatedIndex = max(updatedIndex - 1, 0) + } + let switchToId = self.availableFilters[updatedIndex].id + if switchToId != self.selectedId, let itemNode = self.itemNodes[switchToId] { + let _ = itemNode + self.selectedId = switchToId + applyNodeAsCurrent = switchToId + } } - let switchToId = self.availableFilters[updatedIndex].id - if switchToId != self.selectedId, let itemNode = self.itemNodes[switchToId] { - let _ = itemNode - self.selectedId = switchToId - applyNodeAsCurrent = switchToId + self.transitionFraction = 0.0 + let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring) + self.disableItemNodeOperationsWhileAnimating = true + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: transition) + DispatchQueue.main.async { + self.disableItemNodeOperationsWhileAnimating = false + if let (layout, navigationBarHeight, visualNavigationHeight, originalNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation, inlineNavigationTransitionFraction, storiesInset) = self.validLayout { + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: .immediate) + } } - } - self.transitionFraction = 0.0 - let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring) - self.disableItemNodeOperationsWhileAnimating = true - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: transition) - DispatchQueue.main.async { - self.disableItemNodeOperationsWhileAnimating = false - if let (layout, navigationBarHeight, visualNavigationHeight, originalNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation, inlineNavigationTransitionFraction, storiesInset) = self.validLayout { - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: .immediate) + + if let switchToId = applyNodeAsCurrent, let itemNode = self.itemNodes[switchToId] { + self.applyItemNodeAsCurrent(id: switchToId, itemNode: itemNode) } + self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) } - - self.controller?.storyCameraPanGestureEnded(transitionFraction: translation.x / layout.size.width, velocity: velocity.x) - - if let switchToId = applyNodeAsCurrent, let itemNode = self.itemNodes[switchToId] { - self.applyItemNodeAsCurrent(id: switchToId, itemNode: itemNode) - } - self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) } default: break diff --git a/submodules/Components/MultilineTextComponent/Sources/MultilineTextComponent.swift b/submodules/Components/MultilineTextComponent/Sources/MultilineTextComponent.swift index 7624c5ad3f..c6d8bd169b 100644 --- a/submodules/Components/MultilineTextComponent/Sources/MultilineTextComponent.swift +++ b/submodules/Components/MultilineTextComponent/Sources/MultilineTextComponent.swift @@ -19,6 +19,7 @@ public final class MultilineTextComponent: Component { public let cutout: TextNodeCutout? public let insets: UIEdgeInsets public let textShadowColor: UIColor? + public let textShadowBlur: CGFloat? public let textStroke: (UIColor, CGFloat)? public let highlightColor: UIColor? public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? @@ -35,6 +36,7 @@ public final class MultilineTextComponent: Component { cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets(), textShadowColor: UIColor? = nil, + textShadowBlur: CGFloat? = nil, textStroke: (UIColor, CGFloat)? = nil, highlightColor: UIColor? = nil, highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil, @@ -50,6 +52,7 @@ public final class MultilineTextComponent: Component { self.cutout = cutout self.insets = insets self.textShadowColor = textShadowColor + self.textShadowBlur = textShadowBlur self.textStroke = textStroke self.highlightColor = highlightColor self.highlightAction = highlightAction @@ -90,6 +93,9 @@ public final class MultilineTextComponent: Component { } else if (lhs.textShadowColor != nil) != (rhs.textShadowColor != nil) { return false } + if lhs.textShadowBlur != rhs.textShadowBlur { + return false + } if let lhsTextStroke = lhs.textStroke, let rhsTextStroke = rhs.textStroke { if !lhsTextStroke.0.isEqual(rhsTextStroke.0) { @@ -134,6 +140,7 @@ public final class MultilineTextComponent: Component { self.cutout = component.cutout self.insets = component.insets self.textShadowColor = component.textShadowColor + self.textShadowBlur = component.textShadowBlur self.textStroke = component.textStroke self.linkHighlightColor = component.highlightColor self.highlightAttributeAction = component.highlightAction diff --git a/submodules/Display/Source/ImmediateTextNode.swift b/submodules/Display/Source/ImmediateTextNode.swift index 53b456c4c6..7957d6fa9b 100644 --- a/submodules/Display/Source/ImmediateTextNode.swift +++ b/submodules/Display/Source/ImmediateTextNode.swift @@ -22,6 +22,7 @@ public class ImmediateTextNode: TextNode { public var lineSpacing: CGFloat = 0.0 public var insets: UIEdgeInsets = UIEdgeInsets() public var textShadowColor: UIColor? + public var textShadowBlur: CGFloat? public var textStroke: (UIColor, CGFloat)? public var cutout: TextNodeCutout? public var displaySpoilers = false @@ -97,7 +98,7 @@ public class ImmediateTextNode: TextNode { self.constrainedSize = constrainedSize 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() if layout.numberOfLines > 1 { self.trailingLineWidth = layout.trailingLineWidth @@ -241,6 +242,7 @@ open class ImmediateTextView: TextView { public var lineSpacing: CGFloat = 0.0 public var insets: UIEdgeInsets = UIEdgeInsets() public var textShadowColor: UIColor? + public var textShadowBlur: CGFloat? public var textStroke: (UIColor, CGFloat)? public var cutout: TextNodeCutout? public var displaySpoilers = false @@ -293,7 +295,7 @@ open class ImmediateTextView: TextView { self.constrainedSize = constrainedSize 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() if layout.numberOfLines > 1 { self.trailingLineWidth = layout.trailingLineWidth diff --git a/submodules/Display/Source/TextNode.swift b/submodules/Display/Source/TextNode.swift index 0a79dd2cd7..7aa5ab8860 100644 --- a/submodules/Display/Source/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -145,6 +145,7 @@ public final class TextNodeLayoutArguments { public let insets: UIEdgeInsets public let lineColor: UIColor? public let textShadowColor: UIColor? + public let textShadowBlur: CGFloat? public let textStroke: (UIColor, CGFloat)? public let displaySpoilers: Bool public let displayEmbeddedItemsUnderSpoilers: Bool @@ -163,6 +164,7 @@ public final class TextNodeLayoutArguments { insets: UIEdgeInsets = UIEdgeInsets(), lineColor: UIColor? = nil, textShadowColor: UIColor? = nil, + textShadowBlur: CGFloat? = nil, textStroke: (UIColor, CGFloat)? = nil, displaySpoilers: Bool = false, displayEmbeddedItemsUnderSpoilers: Bool = false @@ -180,6 +182,7 @@ public final class TextNodeLayoutArguments { self.insets = insets self.lineColor = lineColor self.textShadowColor = textShadowColor + self.textShadowBlur = textShadowBlur self.textStroke = textStroke self.displaySpoilers = displaySpoilers self.displayEmbeddedItemsUnderSpoilers = displayEmbeddedItemsUnderSpoilers @@ -200,6 +203,7 @@ public final class TextNodeLayoutArguments { insets: self.insets, lineColor: self.lineColor, textShadowColor: self.textShadowColor, + textShadowBlur: self.textShadowBlur, textStroke: self.textStroke, displaySpoilers: self.displaySpoilers, displayEmbeddedItemsUnderSpoilers: self.displayEmbeddedItemsUnderSpoilers @@ -257,6 +261,7 @@ public final class TextNodeLayout: NSObject { fileprivate let blockQuotes: [TextNodeBlockQuote] fileprivate let lineColor: UIColor? fileprivate let textShadowColor: UIColor? + fileprivate let textShadowBlur: CGFloat? fileprivate let textStroke: (UIColor, CGFloat)? fileprivate let displaySpoilers: Bool public let hasRTL: Bool @@ -264,7 +269,7 @@ public final class TextNodeLayout: NSObject { public let spoilerWords: [(NSRange, CGRect)] 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.maximumNumberOfLines = maximumNumberOfLines self.truncationType = truncationType @@ -284,6 +289,7 @@ public final class TextNodeLayout: NSObject { self.backgroundColor = backgroundColor self.lineColor = lineColor self.textShadowColor = textShadowColor + self.textShadowBlur = textShadowBlur self.textStroke = textStroke self.displaySpoilers = displaySpoilers 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 { let stringLength = attributedString.length @@ -1030,7 +1036,7 @@ open class TextNode: ASDisplayNode { var maybeTypesetter: CTTypesetter? maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString) 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! @@ -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 { - 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 { 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 { @@ -1641,11 +1647,11 @@ open class TextNode: ASDisplayNode { if stringMatch { layout = existingLayout } 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 } } 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 } @@ -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 { let stringLength = attributedString.length @@ -1759,7 +1765,7 @@ open class TextView: UIView { var maybeTypesetter: CTTypesetter? maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString) 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! @@ -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 { - 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 { 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 { @@ -2286,11 +2292,11 @@ open class TextView: UIView { if stringMatch { layout = existingLayout } 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 } } 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 } diff --git a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift index fd0ebbf2d0..0086109d72 100644 --- a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift +++ b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift @@ -496,6 +496,15 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { 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() { for case let view as DrawingEntityView in self.subviews { view.play() diff --git a/submodules/DrawingUI/Sources/DrawingMediaEntity.swift b/submodules/DrawingUI/Sources/DrawingMediaEntity.swift index a21d50d6a3..3bab1ecc44 100644 --- a/submodules/DrawingUI/Sources/DrawingMediaEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingMediaEntity.swift @@ -11,19 +11,19 @@ public final class DrawingMediaEntityView: DrawingEntityView, DrawingEntityMedia private var mediaEntity: DrawingMediaEntity { return self.entity as! DrawingMediaEntity } - - var started: ((Double) -> Void)? - + private var currentSize: CGSize? private var isVisible = true private var isPlaying = false - public var previewView: MediaEditorPreviewView? { + public weak var previewView: MediaEditorPreviewView? { didSet { if let previewView = self.previewView { previewView.isUserInteractionEnabled = false previewView.layer.allowsEdgeAntialiasing = true self.addSubview(previewView) + } else { + oldValue?.removeFromSuperview() } } } @@ -37,7 +37,10 @@ public final class DrawingMediaEntityView: DrawingEntityView, DrawingEntityMedia } deinit { - + if let previewView = self.previewView { + previewView.removeFromSuperview() + self.previewView = nil + } } override func play() { diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index 74caa1b4d3..a79eac7699 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -494,6 +494,7 @@ private final class DrawingScreenComponent: CombinedComponent { let existingStickerPickerInputData: Promise? let isVideo: Bool let isAvatar: Bool + let isInteractingWithEntities: Bool let present: (ViewController) -> Void let updateState: ActionSlot let updateColor: ActionSlot @@ -528,6 +529,7 @@ private final class DrawingScreenComponent: CombinedComponent { existingStickerPickerInputData: Promise?, isVideo: Bool, isAvatar: Bool, + isInteractingWithEntities: Bool, present: @escaping (ViewController) -> Void, updateState: ActionSlot, updateColor: ActionSlot, @@ -560,6 +562,7 @@ private final class DrawingScreenComponent: CombinedComponent { self.existingStickerPickerInputData = existingStickerPickerInputData self.isVideo = isVideo self.isAvatar = isAvatar + self.isInteractingWithEntities = isInteractingWithEntities self.present = present self.updateState = updateState self.updateColor = updateColor @@ -592,9 +595,15 @@ private final class DrawingScreenComponent: CombinedComponent { if lhs.context !== rhs.context { return false } + if lhs.isVideo != rhs.isVideo { + return false + } if lhs.isAvatar != rhs.isAvatar { return false } + if lhs.isInteractingWithEntities != rhs.isInteractingWithEntities { + return false + } return true } @@ -1138,9 +1147,9 @@ private final class DrawingScreenComponent: CombinedComponent { } } - var controlsVisible = true - if state.drawingViewState.isDrawing { - controlsVisible = false + var controlsAreVisible = true + if state.drawingViewState.isDrawing || component.isInteractingWithEntities { + controlsAreVisible = false } 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 .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 { @@ -1328,7 +1337,7 @@ private final class DrawingScreenComponent: CombinedComponent { completion() }) }) - .opacity(controlsVisible ? 1.0 : 0.0) + .opacity(controlsAreVisible ? 1.0 : 0.0) ) offsetX += delta @@ -1356,7 +1365,7 @@ private final class DrawingScreenComponent: CombinedComponent { completion() }) }) - .opacity(controlsVisible ? 1.0 : 0.0) + .opacity(controlsAreVisible ? 1.0 : 0.0) ) offsetX += delta @@ -1384,7 +1393,7 @@ private final class DrawingScreenComponent: CombinedComponent { completion() }) }) - .opacity(controlsVisible ? 1.0 : 0.0) + .opacity(controlsAreVisible ? 1.0 : 0.0) ) offsetX += delta @@ -1412,7 +1421,7 @@ private final class DrawingScreenComponent: CombinedComponent { completion() }) }) - .opacity(controlsVisible ? 1.0 : 0.0) + .opacity(controlsAreVisible ? 1.0 : 0.0) ) offsetX += delta @@ -1440,7 +1449,7 @@ private final class DrawingScreenComponent: CombinedComponent { completion() }) }) - .opacity(controlsVisible ? 1.0 : 0.0) + .opacity(controlsAreVisible ? 1.0 : 0.0) ) offsetX += delta delay += 0.025 @@ -1469,7 +1478,7 @@ private final class DrawingScreenComponent: CombinedComponent { completion() }) }) - .opacity(controlsVisible ? 1.0 : 0.0) + .opacity(controlsAreVisible ? 1.0 : 0.0) ) offsetX += delta @@ -1497,7 +1506,7 @@ private final class DrawingScreenComponent: CombinedComponent { completion() }) }) - .opacity(controlsVisible ? 1.0 : 0.0) + .opacity(controlsAreVisible ? 1.0 : 0.0) ) offsetX += delta @@ -1525,7 +1534,7 @@ private final class DrawingScreenComponent: CombinedComponent { completion() }) }) - .opacity(controlsVisible ? 1.0 : 0.0) + .opacity(controlsAreVisible ? 1.0 : 0.0) ) if state.selectedEntity is DrawingStickerEntity || state.selectedEntity is DrawingTextEntity { @@ -1569,7 +1578,7 @@ private final class DrawingScreenComponent: CombinedComponent { 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 .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( @@ -1764,7 +1773,7 @@ private final class DrawingScreenComponent: CombinedComponent { context.add(undoButton .position(CGPoint(x: environment.safeInsets.left + undoButton.size.width / 2.0 + 2.0, y: topInset)) .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 .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) - .opacity(state.drawingViewState.canRedo && !isEditingText && controlsVisible ? 1.0 : 0.0) + .opacity(state.drawingViewState.canRedo && !isEditingText && controlsAreVisible ? 1.0 : 0.0) ) let clearAllButton = clearAllButton.update( @@ -1804,7 +1813,7 @@ private final class DrawingScreenComponent: CombinedComponent { context.add(clearAllButton .position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - clearAllButton.size.width / 2.0 - 13.0, y: topInset)) .scale(isEditingText ? 0.01 : 1.0) - .opacity(isEditingText || !controlsVisible ? 0.0 : 1.0) + .opacity(isEditingText || !controlsAreVisible ? 0.0 : 1.0) ) 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)) .appear(.default(scale: true)) .disappear(.default(scale: true)) - .opacity(controlsVisible ? 1.0 : 0.0) + .opacity(controlsAreVisible ? 1.0 : 0.0) ) let modeRightInset: CGFloat = 57.0 @@ -1946,7 +1955,7 @@ private final class DrawingScreenComponent: CombinedComponent { .appear(.default(scale: true)) .disappear(.default(scale: true)) .cornerRadius(12.0) - .opacity(controlsVisible ? 1.0 : 0.0) + .opacity(controlsAreVisible ? 1.0 : 0.0) ) 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) }) - .opacity(controlsVisible ? 1.0 : 0.0) + .opacity(controlsAreVisible ? 1.0 : 0.0) ) let selectedIndex: Int @@ -2049,7 +2058,7 @@ private final class DrawingScreenComponent: CombinedComponent { } context.add(modeAndSize .position(modeAndSizePosition) - .opacity(controlsVisible ? 1.0 : 0.0) + .opacity(controlsAreVisible ? 1.0 : 0.0) ) var animatingOut = false @@ -2088,7 +2097,7 @@ private final class DrawingScreenComponent: CombinedComponent { } context.add(backButton .position(backButtonPosition) - .opacity(controlsVisible ? 1.0 : 0.0) + .opacity(controlsAreVisible ? 1.0 : 0.0) ) return context.availableSize @@ -2119,6 +2128,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U fileprivate let insertText: ActionSlot private let updateEntityView: ActionSlot<(UUID, Bool)> private let endEditingTextEntityView: ActionSlot<(UUID, Bool)> + private var isInteractingWithEntities = false private let apply: ActionSlot private let dismiss: ActionSlot @@ -2408,6 +2418,12 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U 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 return controller?.getCurrentImage() }, @@ -2549,6 +2565,12 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U 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) { guard let controller = self.controller else { return @@ -2594,6 +2616,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U existingStickerPickerInputData: controller.existingStickerPickerInputData, isVideo: controller.isVideo, isAvatar: controller.isAvatar, + isInteractingWithEntities: self.isInteractingWithEntities, present: { [weak self] c in self?.controller?.present(c, in: .window(.root)) }, @@ -2873,8 +2896,6 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U @available(iOSApplicationExtension 11.0, iOS 11.0, *) public func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal { - //self.chatDisplayNode.updateDropInteraction(isActive: true) - let operation: UIDropOperation operation = .copy return UIDropProposal(operation: operation) @@ -2887,8 +2908,6 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U return } 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 { let entity = DrawingStickerEntity(content: .image(image)) strongSelf.node.insertEntity.invoke(entity) @@ -2898,12 +2917,10 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U @available(iOSApplicationExtension 11.0, iOS 11.0, *) public func dropInteraction(_ interaction: UIDropInteraction, sessionDidExit session: UIDropSession) { - //self.chatDisplayNode.updateDropInteraction(isActive: false) } @available(iOSApplicationExtension 11.0, iOS 11.0, *) 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 updateColor: (DrawingColor) -> Void + private let onInteractionUpdated: (Bool) -> Void private let getCurrentImage: () -> UIImage? private let getControllerNode: () -> ASDisplayNode? private let present: (ViewController, PresentationContextType, Any?) -> Void @@ -2942,6 +2960,7 @@ public final class DrawingToolsInteraction { updateSelectedEntity: @escaping (DrawingEntity?) -> Void, updateVideoPlayback: @escaping (Bool) -> Void, updateColor: @escaping (DrawingColor) -> Void, + onInteractionUpdated: @escaping (Bool) -> Void, getCurrentImage: @escaping () -> UIImage?, getControllerNode: @escaping () -> ASDisplayNode?, present: @escaping (ViewController, PresentationContextType, Any?) -> Void, @@ -2955,6 +2974,7 @@ public final class DrawingToolsInteraction { self.updateSelectedEntity = updateSelectedEntity self.updateVideoPlayback = updateVideoPlayback self.updateColor = updateColor + self.onInteractionUpdated = onInteractionUpdated self.getCurrentImage = getCurrentImage self.getControllerNode = getControllerNode self.present = present @@ -2975,7 +2995,11 @@ public final class DrawingToolsInteraction { self.updateSelectedEntity(entity) } } - + self.entitiesView.onInteractionUpdated = { [weak self] isInteracting in + if let self { + self.onInteractionUpdated(isInteracting) + } + } self.entitiesView.requestedMenuForEntityView = { [weak self] entityView, isTopmost in guard let self, let node = self.getControllerNode() else { return @@ -3000,6 +3024,12 @@ public final class DrawingToolsInteraction { 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 { 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) { // self.entitiesView.pause() // diff --git a/submodules/ItemListUI/Sources/Items/ItemListMultilineTextItem.swift b/submodules/ItemListUI/Sources/Items/ItemListMultilineTextItem.swift index b2d1930fb7..53e377b19d 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListMultilineTextItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListMultilineTextItem.swift @@ -414,7 +414,7 @@ public class ItemListMultilineTextItemNode: ListViewItemNode { if let current = self.linkHighlightingNode { linkHighlightingNode = current } 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.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode) } diff --git a/submodules/ItemListUI/Sources/Items/ItemListTextWithLabelItem.swift b/submodules/ItemListUI/Sources/Items/ItemListTextWithLabelItem.swift index 784d308dd7..6dd6f49b08 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListTextWithLabelItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListTextWithLabelItem.swift @@ -458,7 +458,7 @@ public class ItemListTextWithLabelItemNode: ListViewItemNode { if let current = self.linkHighlightingNode { linkHighlightingNode = current } 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.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode) } diff --git a/submodules/PassportUI/Sources/SecureIdAuthFormContentNode.swift b/submodules/PassportUI/Sources/SecureIdAuthFormContentNode.swift index 55ccbd1e97..d9faad4f52 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthFormContentNode.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthFormContentNode.swift @@ -68,7 +68,7 @@ final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode, 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 if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 0071d7d9a7..15bc133974 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -1900,7 +1900,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { horizontalAlignment: .natural, maximumNumberOfLines: 0, lineSpacing: 0.0, - highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.3), + highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.2), highlightAction: { attributes in if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) diff --git a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift index 8c9e6c1639..915838b17d 100644 --- a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift +++ b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift @@ -119,7 +119,7 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { self.leftActionNode.addTarget(self, action: #selector(self.leftActionPressed), 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 if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift index a2a148ba3d..9a3307aedb 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift @@ -43,6 +43,7 @@ private class AdMessagesHistoryContextImpl { struct WebPage: Equatable, Codable { var title: String var url: String + var photo: TelegramMediaImage? } case peer(PeerId) @@ -270,7 +271,7 @@ private class AdMessagesHistoryContextImpl { accessHash: nil, title: webPage.title, username: nil, - photo: [], + photo: webPage.photo?.representations ?? [], creationDate: 0, version: 0, participationStatus: .left, @@ -514,9 +515,10 @@ private class AdMessagesHistoryContextImpl { if let fromId = fromId { target = .peer(fromId.peerId) } else if let webPage = webPage { - if case let .sponsoredWebPage(_, url, siteName, photo) = webPage { - let _ = photo - target = .webPage(CachedMessage.Target.WebPage(title: siteName, url: url)) + switch webPage { + case let .sponsoredWebPage(_, url, siteName, photo): + 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 { switch chatInvite { diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index 7fbdd67396..13a0d15f9a 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -33,25 +33,30 @@ private struct CameraState { case handsFree } let mode: CameraMode + let position: Camera.Position let flashMode: Camera.FlashMode let flashModeDidChange: Bool let recording: Recording let duration: Double 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 { - 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 { - 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 { - 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? 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 private let hapticFeedback = HapticFeedback() @@ -154,13 +159,18 @@ private final class CameraScreenComponent: CombinedComponent { super.init() - self.cameraStateDisposable = (camera.flashMode - |> deliverOnMainQueue).start(next: { [weak self] flashMode in + self.cameraStateDisposable = combineLatest(queue: Queue.mainQueue(), camera.flashMode, camera.position) + .start(next: { [weak self] flashMode, position in guard let self else { 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)) + + if previousState.position != self.cameraState.position { + UserDefaults.standard.set((self.cameraState.position == .front) as NSNumber, forKey: "TelegramStoryCameraUseFrontPosition") + } }) Queue.mainQueue().async { @@ -206,7 +216,13 @@ private final class CameraScreenComponent: CombinedComponent { self.hapticFeedback.impact(.light) } + private var lastFlipTimestamp: Double? func togglePosition() { + let currentTimestamp = CACurrentMediaTime() + if let lastFlipTimestamp = self.lastFlipTimestamp, currentTimestamp - lastFlipTimestamp < 2.0 { + return + } + self.lastFlipTimestamp = currentTimestamp self.camera.togglePosition() self.hapticFeedback.impact(.light) } @@ -752,7 +768,13 @@ public class CameraScreen: ViewController { self.previewView = CameraPreviewView(test: false)! 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 targetEnvironment(simulator) #else @@ -1079,12 +1101,11 @@ public class CameraScreen: ViewController { let maxScale = (layout.size.width - 16.0 * 2.0) / layout.size.width let topInset: CGFloat = (layout.statusBarHeight ?? 0.0) + 12.0 - - let maxOffset = (topInset - (layout.size.height - layout.size.height * maxScale) / 2.0) - //let maxOffset = -56.0 + let targetTopInset = ceil((layout.statusBarHeight ?? 0.0) - (layout.size.height - layout.size.height * maxScale) / 2.0) + let deltaOffset = (targetTopInset - topInset) 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) } @@ -1117,9 +1138,9 @@ public class CameraScreen: ViewController { if let view = self.componentHost.findTaggedView(tag: flashButtonTag) { 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.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() { self.node.animateInFromEditor(toGallery: self.galleryController != nil) } @@ -1331,38 +1352,42 @@ public class CameraScreen: ViewController { self.node.pauseCameraCapture() } - let controller = self.context.sharedContext.makeMediaPickerScreen(context: self.context, completion: { [weak self] result, transitionView, transitionRect, transitionImage, transitionOut, dismissed in - if let self { - stopCameraCapture() - - let resultTransition = ResultTransition( - sourceView: transitionView, - sourceRect: transitionRect, - sourceImage: transitionImage, - transitionOut: transitionOut - ) - if let asset = result as? PHAsset { - self.completion(.single(.asset(asset)), resultTransition, dismissed) - } else if let draft = result as? MediaEditorDraft { - self.completion(.single(.draft(draft)), resultTransition, dismissed) - } - } - }, dismissed: { [weak self] in - if let self { - self.node.resumeCameraCapture() - } - }) - controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in - if let self, let controller { - let transitionFactor = controller.modalStyleOverlayTransitionFactor - if transitionFactor > 0.1 { + 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 { stopCameraCapture() + + let resultTransition = ResultTransition( + sourceView: transitionView, + sourceRect: transitionRect, + sourceImage: transitionImage, + transitionOut: transitionOut + ) + if let asset = result as? PHAsset { + self.completion(.single(.asset(asset)), resultTransition, dismissed) + } else if let draft = result as? MediaEditorDraft { + self.completion(.single(.draft(draft)), resultTransition, dismissed) + } + } + }, dismissed: { [weak self] in + if let self { + self.node.resumeCameraCapture() + } + }) + controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in + if let self, let controller { + let transitionFactor = controller.modalStyleOverlayTransitionFactor + if transitionFactor > 0.1 { + stopCameraCapture() + } + self.node.updateModalTransitionFactor(transitionFactor, transition: transition) } - self.node.updateModalTransitionFactor(transitionFactor, transition: transition) } + self.galleryController = controller } - - self.galleryController = controller self.push(controller) } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift index a6bb919642..d1d547619f 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift @@ -397,15 +397,18 @@ final class CaptureControlsComponent: Component { } @objc private func handlePress(_ gestureRecognizer: UILongPressGestureRecognizer) { + guard let component = self.component else { + return + } let location = gestureRecognizer.location(in: self) switch gestureRecognizer.state { case .began: - self.component?.shutterPressed() - self.component?.swipeHintUpdated(.zoom) + component.shutterPressed() + component.swipeHintUpdated(.zoom) self.shutterUpdateOffset.invoke((0.0, .immediate)) case .ended, .cancelled: if location.x < self.frame.width / 2.0 - 60.0 { - self.component?.lockRecording() + component.lockRecording() var blobOffset: CGFloat = 0.0 if let galleryButton = self.galleryButtonView.view { @@ -414,7 +417,7 @@ final class CaptureControlsComponent: Component { self.shutterUpdateOffset.invoke((blobOffset, .spring(duration: 0.35))) } else { self.hapticFeedback.impact(.light) - self.component?.shutterReleased() + component.shutterReleased() self.shutterUpdateOffset.invoke((0.0, .spring(duration: 0.25))) } default: @@ -426,6 +429,9 @@ final class CaptureControlsComponent: Component { private var wasBanding: Bool? private var panBlobState: ShutterBlobView.BlobState? @objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) { + guard let component = self.component else { + return + } func rubberBandingOffset(offset: CGFloat, bandingStart: CGFloat) -> CGFloat { let bandedOffset = offset - bandingStart let range: CGFloat = 60.0 @@ -437,6 +443,9 @@ final class CaptureControlsComponent: Component { let location = gestureRecognizer.location(in: self) switch gestureRecognizer.state { case .changed: + guard case .holdRecording = component.shutterState else { + return + } var blobOffset: CGFloat = 0.0 if let galleryButton = self.galleryButtonView.view, let flipButton = self.flipButtonView.view { blobOffset = max(galleryButton.center.x, min(flipButton.center.x, location.x)) @@ -445,21 +454,21 @@ final class CaptureControlsComponent: Component { var isBanding = false if location.y < -10.0 { let fraction = 1.0 + min(8.0, ((abs(location.y) - 10.0) / 60.0)) - self.component?.zoomUpdated(fraction) + component.zoomUpdated(fraction) } 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 - 60.0 { - self.component?.swipeHintUpdated(.releaseLock) + component.swipeHintUpdated(.releaseLock) if location.x < 75.0 { self.panBlobState = .lock } else { self.panBlobState = .transientToLock } } else { - self.component?.swipeHintUpdated(.lock) + component.swipeHintUpdated(.lock) self.panBlobState = .video blobOffset = rubberBandingOffset(offset: blobOffset, bandingStart: 0.0) isBanding = true @@ -472,7 +481,7 @@ final class CaptureControlsComponent: Component { self.didFlip = true self.hapticFeedback.impact(.light) self.flipAnimationAction.invoke(Void()) - self.component?.flipTapped() + component.flipTapped() } } else { self.didFlip = false @@ -482,7 +491,7 @@ final class CaptureControlsComponent: Component { } } else { blobOffset = rubberBandingOffset(offset: blobOffset, bandingStart: 0.0) - self.component?.swipeHintUpdated(.zoom) + component.swipeHintUpdated(.zoom) self.panBlobState = .video isBanding = true } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/ImageTextureSource.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/ImageTextureSource.swift index 32b21dde64..01c100545f 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/ImageTextureSource.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/ImageTextureSource.swift @@ -56,6 +56,10 @@ final class ImageTextureSource: TextureSource { self.output?.consumeTexture(texture, render: false) } } + + func invalidate() { + self.texture = nil + } } func pixelBufferToMTLTexture(pixelBuffer: CVPixelBuffer, textureCache: CVMetalTextureCache) -> MTLTexture? { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index 7d5554dcdc..5878884c34 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -52,8 +52,10 @@ public final class MediaEditor { self.updateRenderChain() } self.valuesPromise.set(.single(self.values)) + self.valuesUpdated(self.values) } } + public var valuesUpdated: (MediaEditorValues) -> Void = { _ in } private var valuesPromise = Promise() private let renderer = MediaEditorRenderer() @@ -329,6 +331,7 @@ public final class MediaEditor { if asset.mediaType == .video { let options = PHImageRequestOptions() 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 if let image { if let info { @@ -353,6 +356,7 @@ public final class MediaEditor { } else { let options = PHImageRequestOptions() 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 if let image { var degraded = false @@ -498,6 +502,11 @@ public final class MediaEditor { self.player?.pause() } + public func invalidate() { + self.player?.pause() + self.renderer.textureSource?.invalidate() + } + private func updateVideoTimePosition() { guard let (targetPosition, _) = self.targetTimePosition else { return @@ -616,7 +625,7 @@ final class MediaEditorRenderChain { switch key { case .enhance: if let value = value as? Float { - self.enhancePass.value = value + self.enhancePass.value = abs(value) } else { self.enhancePass.value = 0.0 } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorDraft.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorDraft.swift index a3cb382040..62aa66ebef 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorDraft.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorDraft.swift @@ -6,6 +6,11 @@ import TelegramUIPreferences import PersistentStringHash 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 static func == (lhs: MediaEditorDraft, rhs: MediaEditorDraft) -> Bool { return lhs.path == rhs.path @@ -25,13 +30,15 @@ public final class MediaEditorDraft: Codable, Equatable { public let thumbnail: UIImage public let dimensions: PixelDimensions 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.isVideo = isVideo self.thumbnail = thumbnail self.dimensions = dimensions self.values = values + self.privacy = privacy } public init(from decoder: Decoder) throws { @@ -55,6 +62,7 @@ public final class MediaEditorDraft: Codable, Equatable { } else { fatalError() } + self.privacy = nil } public func encode(to encoder: Encoder) throws { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorPreviewView.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorPreviewView.swift index cde84b67fe..3df8dd8072 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorPreviewView.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorPreviewView.swift @@ -5,7 +5,7 @@ import MetalKit import SwiftSignalKit public final class MediaEditorPreviewView: MTKView, MTKViewDelegate, RenderTarget { - var renderer: MediaEditorRenderer? { + weak var renderer: MediaEditorRenderer? { didSet { if let renderer = self.renderer { renderer.renderTargetDidChange(self) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift index e96b10ec57..0913d42820 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift @@ -30,6 +30,7 @@ protocol RenderPass: AnyObject { protocol TextureSource { func connect(to: TextureConsumer) + func invalidate() } protocol RenderTarget: AnyObject { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift index e469b2316f..ba56a8f5a6 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift @@ -26,8 +26,8 @@ func textureRotatonForAVAsset(_ asset: AVAsset) -> TextureRotation { } final class VideoTextureSource: NSObject, TextureSource, AVPlayerItemOutputPullDelegate { - private let player: AVPlayer - private var playerItem: AVPlayerItem? + private weak var player: AVPlayer? + private weak var playerItem: AVPlayerItem? private var playerItemOutput: AVPlayerItemVideoOutput? private var playerItemStatusObservation: NSKeyValueObservation? @@ -57,11 +57,11 @@ final class VideoTextureSource: NSObject, TextureSource, AVPlayerItemOutputPullD super.init() - self.playerItemObservation = self.player.observe(\.currentItem, options: [.initial, .new], changeHandler: { [weak self] (player, change) in - guard let strongSelf = self, strongSelf.player == player else { + self.playerItemObservation = player.observe(\.currentItem, options: [.initial, .new], changeHandler: { [weak self] (player, change) in + guard let strongSelf = self else { return } - strongSelf.updatePlayerItem(strongSelf.player.currentItem) + strongSelf.updatePlayerItem(player.currentItem) }) } @@ -70,6 +70,15 @@ final class VideoTextureSource: NSObject, TextureSource, AVPlayerItemOutputPullD 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?) { self.displayLink?.invalidate() self.displayLink = nil @@ -160,7 +169,10 @@ final class VideoTextureSource: NSObject, TextureSource, AVPlayerItemOutputPullD } private func handleUpdate() { - if self.player.rate != 0 { + guard let player = self.player else { + return + } + if player.rate != 0 { self.forceUpdate = true } self.update(forced: self.forceUpdate) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/AdjustmentsComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/AdjustmentsComponent.swift index 212a12aa3d..fd384c23ee 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/AdjustmentsComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/AdjustmentsComponent.swift @@ -313,12 +313,21 @@ final class AdjustmentsComponent: Component { 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( transition: transition, component: AnyComponent( AdjustmentSliderComponent( title: tool.title, - value: tool.value, + value: value, minValue: tool.minValue, maxValue: tool.maxValue, startValue: tool.startValue, @@ -326,7 +335,11 @@ final class AdjustmentsComponent: Component { trackColor: nil, displayValue: true, valueUpdated: { value in - valueUpdated(tool.key, value) + var updatedValue = value + if valueIsNegative { + updatedValue *= -1.0 + } + valueUpdated(tool.key, updatedValue) }, isTrackingUpdated: { isTracking in isTrackingUpdated(tool.key, isTracking) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/BlurComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/BlurComponent.swift index 920d35c2c3..27bb548757 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/BlurComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/BlurComponent.swift @@ -118,15 +118,18 @@ final class BlurComponent: Component { let value: BlurValue let hasPortrait: Bool let valueUpdated: (BlurValue) -> Void + let isTrackingUpdated: (Bool) -> Void init( value: BlurValue, hasPortrait: Bool, - valueUpdated: @escaping (BlurValue) -> Void + valueUpdated: @escaping (BlurValue) -> Void, + isTrackingUpdated: @escaping (Bool) -> Void ) { self.value = value self.hasPortrait = hasPortrait self.valueUpdated = valueUpdated + self.isTrackingUpdated = isTrackingUpdated } static func ==(lhs: BlurComponent, rhs: BlurComponent) -> Bool { @@ -287,6 +290,36 @@ final class BlurComponent: Component { environment: {}, 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( transition: transition, @@ -304,6 +337,9 @@ final class BlurComponent: Component { if let state { valueUpdated(state.value.withUpdatedIntensity(value)) } + }, + isTrackingUpdated: { isTracking in + isTrackingUpdated(isTracking) } ) ), @@ -364,14 +400,17 @@ final class BlurScreenComponent: Component { let value: BlurValue let valueUpdated: (BlurValue) -> Void + let isTrackingUpdated: (Bool) -> Void init( value: BlurValue, - valueUpdated: @escaping (BlurValue) -> Void + valueUpdated: @escaping (BlurValue) -> Void, + isTrackingUpdated: @escaping (Bool) -> Void ) { self.value = value self.valueUpdated = valueUpdated + self.isTrackingUpdated = isTrackingUpdated } static func ==(lhs: BlurScreenComponent, rhs: BlurScreenComponent) -> Bool { @@ -435,6 +474,7 @@ final class BlurScreenComponent: Component { case .began: switch component.value.mode { case .radial: + component.isTrackingUpdated(true) let distance = sqrt(delta.x * delta.x + delta.y * delta.y) let close = abs(outerRadius - innerRadius) < blurInsetProximity @@ -455,6 +495,7 @@ final class BlurScreenComponent: Component { self.startRadius = outerRadius } case .linear: + component.isTrackingUpdated(true) 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)) @@ -617,6 +658,7 @@ final class BlurScreenComponent: Component { break } default: + component.isTrackingUpdated(false) self.activeControl = nil self.startCenterPoint = nil self.startDistance = nil @@ -630,6 +672,7 @@ final class BlurScreenComponent: Component { } switch gestureRecognizer.state { case .began: + component.isTrackingUpdated(true) self.activeControl = .wholeArea case .changed: let scale = Float(gestureRecognizer.scale) @@ -640,6 +683,7 @@ final class BlurScreenComponent: Component { gestureRecognizer.scale = 1.0 default: + component.isTrackingUpdated(false) self.activeControl = nil } } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CurvesComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CurvesComponent.swift index 953465f897..04afc05e3f 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CurvesComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CurvesComponent.swift @@ -4,6 +4,7 @@ import Display import ComponentFlow import LegacyComponents import MediaEditor +import MultilineTextComponent private class HistogramView: UIView { private var size: CGSize? @@ -332,6 +333,7 @@ final class CurvesScreenComponent: Component { private let line3 = SimpleLayer() private let line4 = SimpleLayer() + private let curveContainer = SimpleLayer() private let guideLayer = SimpleShapeLayer() private let curveLayer = SimpleShapeLayer() @@ -346,8 +348,15 @@ final class CurvesScreenComponent: Component { self.layer.addSublayer(self.line3) self.layer.addSublayer(self.line4) - self.layer.addSublayer(self.guideLayer) - self.layer.addSublayer(self.curveLayer) + self.layer.addSublayer(self.curveContainer) + 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.line2.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.5).cgColor @@ -361,6 +370,26 @@ final class CurvesScreenComponent: Component { self.curveLayer.lineWidth = 2.0 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(_:))) self.addGestureRecognizer(panGestureRecognizer) @@ -495,10 +524,16 @@ final class CurvesScreenComponent: Component { let blacksSize = self.blacks.update( transition: transition, component: AnyComponent( - Text( - text: String(format: "%.2f", value.blacks), - font: Font.regular(14.0), - color: UIColor(rgb: 0xffffff, alpha: 0.75) + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: String(format: "%.2f", value.blacks), + font: Font.regular(14.0), + textColor: UIColor(rgb: 0xffffff) + ) + ), + textShadowColor: UIColor(rgb: 0x000000, alpha: 0.3), + textShadowBlur: 1.5 ) ), environment: {}, @@ -509,16 +544,23 @@ final class CurvesScreenComponent: Component { if view.superview == nil { self.addSubview(view) } + view.alpha = 0.75 transition.setFrame(view: view, frame: blacksFrame) } let shadowsSize = self.shadows.update( transition: transition, component: AnyComponent( - Text( - text: String(format: "%.2f", value.shadows), - font: Font.regular(14.0), - color: UIColor(rgb: 0xffffff, alpha: 0.75) + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: String(format: "%.2f", value.shadows), + font: Font.regular(14.0), + textColor: UIColor(rgb: 0xffffff) + ) + ), + textShadowColor: UIColor(rgb: 0x000000, alpha: 0.3), + textShadowBlur: 1.5 ) ), environment: {}, @@ -529,16 +571,23 @@ final class CurvesScreenComponent: Component { if view.superview == nil { self.addSubview(view) } + view.alpha = 0.75 transition.setFrame(view: view, frame: shadowsFrame) } let midtonesSize = self.midtones.update( transition: transition, component: AnyComponent( - Text( - text: String(format: "%.2f", value.midtones), - font: Font.regular(14.0), - color: UIColor(rgb: 0xffffff, alpha: 0.75) + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: String(format: "%.2f", value.midtones), + font: Font.regular(14.0), + textColor: UIColor(rgb: 0xffffff) + ) + ), + textShadowColor: UIColor(rgb: 0x000000, alpha: 0.3), + textShadowBlur: 1.5 ) ), environment: {}, @@ -549,16 +598,23 @@ final class CurvesScreenComponent: Component { if view.superview == nil { self.addSubview(view) } + view.alpha = 0.75 transition.setFrame(view: view, frame: midtonesFrame) } let highlightsSize = self.highlights.update( transition: transition, component: AnyComponent( - Text( - text: String(format: "%.2f", value.highlights), - font: Font.regular(14.0), - color: UIColor(rgb: 0xffffff, alpha: 0.75) + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: String(format: "%.2f", value.highlights), + font: Font.regular(14.0), + textColor: UIColor(rgb: 0xffffff) + ) + ), + textShadowColor: UIColor(rgb: 0x000000, alpha: 0.3), + textShadowBlur: 1.5 ) ), environment: {}, @@ -569,16 +625,23 @@ final class CurvesScreenComponent: Component { if view.superview == nil { self.addSubview(view) } + view.alpha = 0.75 transition.setFrame(view: view, frame: highlightsFrame) } let whitesSize = self.whites.update( transition: transition, component: AnyComponent( - Text( - text: String(format: "%.2f", value.whites), - font: Font.regular(14.0), - color: UIColor(rgb: 0xffffff, alpha: 0.75) + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: String(format: "%.2f", value.whites), + font: Font.regular(14.0), + textColor: UIColor(rgb: 0xffffff) + ) + ), + textShadowColor: UIColor(rgb: 0x000000, alpha: 0.3), + textShadowBlur: 1.5 ) ), environment: {}, @@ -589,9 +652,12 @@ final class CurvesScreenComponent: Component { if view.superview == nil { self.addSubview(view) } + view.alpha = 0.75 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 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) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 21b7787e2d..90ccaa4f1e 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -40,6 +40,7 @@ final class MediaEditorScreenComponent: Component { let context: AccountContext let isDisplayingTool: Bool let isInteractingWithEntities: Bool + let isSavingAvailable: Bool let isDismissing: Bool let mediaEditor: MediaEditor? let privacy: MediaEditorResultPrivacy @@ -52,6 +53,7 @@ final class MediaEditorScreenComponent: Component { context: AccountContext, isDisplayingTool: Bool, isInteractingWithEntities: Bool, + isSavingAvailable: Bool, isDismissing: Bool, mediaEditor: MediaEditor?, privacy: MediaEditorResultPrivacy, @@ -63,6 +65,7 @@ final class MediaEditorScreenComponent: Component { self.context = context self.isDisplayingTool = isDisplayingTool self.isInteractingWithEntities = isInteractingWithEntities + self.isSavingAvailable = isSavingAvailable self.isDismissing = isDismissing self.mediaEditor = mediaEditor self.privacy = privacy @@ -82,6 +85,9 @@ final class MediaEditorScreenComponent: Component { if lhs.isInteractingWithEntities != rhs.isInteractingWithEntities { return false } + if lhs.isSavingAvailable != rhs.isSavingAvailable { + return false + } if lhs.isDismissing != rhs.isDismissing { return false } @@ -186,12 +192,15 @@ final class MediaEditorScreenComponent: Component { private let toolsButton = ComponentView() private let doneButton = ComponentView() + private let fadeView = UIButton() + private let inputPanel = ComponentView() private let inputPanelExternalState = MessageInputPanelComponent.ExternalState() private let scrubber = ComponentView() private let privacyButton = ComponentView() + private let flipStickerButton = ComponentView() private let muteButton = ComponentView() private let saveButton = ComponentView() private let settingsButton = ComponentView() @@ -208,24 +217,29 @@ final class MediaEditorScreenComponent: Component { override init(frame: CGRect) { super.init(frame: frame) - + 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) { fatalError("init(coder:) has not been implemented") } + @objc private func fadePressed() { + self.endEditing(true) + } + enum TransitionAnimationSource { case camera case gallery } 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 = [ self.drawButton, self.textButton, @@ -233,6 +247,11 @@ final class MediaEditorScreenComponent: Component { 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 for button in buttons { if let view = button.view { @@ -242,41 +261,46 @@ final class MediaEditorScreenComponent: Component { delay += 0.05 } } - + if let view = self.doneButton.view { view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) } + if case .camera = source { + if let view = self.saveButton.view { + view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) + } + + if let view = self.muteButton.view { + view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) + } + + if let view = self.settingsButton.view { + view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) + } + + if let view = self.privacyButton.view { + view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) + } + } + if let view = self.inputPanel.view { - 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 { - view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) - } - - if let view = self.muteButton.view { - view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) - } - - if let view = self.settingsButton.view { - view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) - } - - if let view = self.privacyButton.view { - view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) + 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) { self.isDismissed = true + let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut)) if let view = self.cancelButton.view { transition.setAlpha(view: view, alpha: 0.0) @@ -304,9 +328,11 @@ final class MediaEditorScreenComponent: Component { } if let view = self.inputPanel.view { - 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.animateScale(from: 1.0, to: 0.1, duration: 0.2) + 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.animateAlpha(from: view.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2) + } } if let view = self.saveButton.view { @@ -717,12 +743,22 @@ final class MediaEditorScreenComponent: Component { wasRecordingDismissed: false, timeoutValue: timeoutValue, timeoutSelected: timeoutSelected, - displayGradient: false,//component.inputHeight != 0.0, - bottomInset: 0.0 //component.inputHeight != 0.0 ? 0.0 : bottomContentInset + displayGradient: false, + bottomInset: 0.0 )), environment: {}, 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 sizeSliderVisible = false @@ -836,15 +872,19 @@ final class MediaEditorScreenComponent: Component { if let saveButtonView = self.saveButton.view { if saveButtonView.superview == nil { 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.shadowOpacity = 0.2 + saveButtonView.layer.shadowOpacity = 0.35 self.addSubview(saveButtonView) } + + let saveButtonAlpha = component.isSavingAvailable ? 1.0 : 0.3 + saveButtonView.isUserInteractionEnabled = component.isSavingAvailable + transition.setPosition(view: saveButtonView, position: saveButtonFrame.center) transition.setBounds(view: saveButtonView, bounds: CGRect(origin: .zero, size: saveButtonFrame.size)) 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 { @@ -880,9 +920,9 @@ final class MediaEditorScreenComponent: Component { if let muteButtonView = self.muteButton.view { if muteButtonView.superview == nil { 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.shadowOpacity = 0.2 + muteButtonView.layer.shadowOpacity = 0.35 self.addSubview(muteButtonView) } transition.setPosition(view: muteButtonView, position: muteButtonFrame.center) @@ -918,9 +958,9 @@ final class MediaEditorScreenComponent: Component { if let settingsButtonView = self.settingsButton.view { if settingsButtonView.superview == nil { settingsButtonView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0) - settingsButtonView.layer.shadowRadius = 4.0 + settingsButtonView.layer.shadowRadius = 3.0 settingsButtonView.layer.shadowColor = UIColor.black.cgColor - settingsButtonView.layer.shadowOpacity = 0.2 + settingsButtonView.layer.shadowOpacity = 0.35 //self.addSubview(settingsButtonView) } 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 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 enum TransitionIn { 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 { @@ -1270,10 +1298,20 @@ public final class MediaEditorScreen: ViewController { private func setup(with subject: MediaEditorScreen.Subject) { self.subject = subject - guard let _ = self.controller else { + guard let controller = self.controller else { 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 maxSide: CGFloat = 1920.0 / UIScreen.main.scale 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) 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 if let self, let colors { @@ -1377,7 +1425,7 @@ public final class MediaEditorScreen: ViewController { isVideo: false, updateSelectedEntity: { [weak self] _ in if let self { - self.requestUpdate() + self.requestUpdate(transition: .easeInOut(duration: 0.2)) } }, updateVideoPlayback: { [weak self] isPlaying in @@ -1395,6 +1443,12 @@ public final class MediaEditorScreen: ViewController { selectedEntityView.update(animated: false) } }, + onInteractionUpdated: { [weak self] isInteracting in + if let self { + self.isInteractingWithEntities = isInteracting + self.requestUpdate(transition: .easeInOut(duration: 0.2)) + } + }, getCurrentImage: { return nil }, @@ -1415,6 +1469,13 @@ public final class MediaEditorScreen: ViewController { } @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 } @@ -1429,6 +1490,8 @@ public final class MediaEditorScreen: ViewController { } } + private var enhanceGestureOffset: CGFloat? + @objc func handleDismissPan(_ gestureRecognizer: UIPanGestureRecognizer) { guard let controller = self.controller, let layout = self.validLayout, (layout.inputHeight ?? 0.0).isZero else { return @@ -1464,7 +1527,7 @@ public final class MediaEditorScreen: ViewController { if let mediaEditor = self.mediaEditor { let value = mediaEditor.getToolValue(.enhance) as? Float ?? 0.0 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) } self.requestUpdate() @@ -1509,8 +1572,15 @@ public final class MediaEditorScreen: ViewController { if entitiesHitTestResult == nil { if self.entitiesView.hasSelection { self.entitiesView.selectEntity(nil) + 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) + } } - self.view.endEditing(true) } } @@ -1572,7 +1642,7 @@ public final class MediaEditorScreen: ViewController { if let componentView = self.componentHost.view { 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.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 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 let view = self.componentHost.view as? MediaEditorScreenComponent.View { @@ -1605,23 +1675,25 @@ public final class MediaEditorScreen: ViewController { let transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut)) transition.setAlpha(view: self.previewContainerView, alpha: 0.0, completion: { _ in 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 { - 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 { - let transitionOutView = UIImageView(image: sourceImage) - var initialScale: CGFloat - if sourceImage.size.height > sourceImage.size.width { - initialScale = max(self.previewContainerView.bounds.width / sourceImage.size.width, self.previewContainerView.bounds.height / sourceImage.size.height) - } else { - initialScale = self.previewContainerView.bounds.width / sourceImage.size.width - } - 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) + let sourceSuperView = galleryTransitionIn.sourceView?.superview?.superview + let destinationTransitionOutView = UIImageView(image: sourceImage) + destinationTransitionOutView.clipsToBounds = true + destinationTransitionOutView.contentMode = .scaleAspectFill + destinationTransitionOutView.frame = self.previewContainerView.convert(self.previewContainerView.bounds, to: sourceSuperView) + sourceSuperView?.addSubview(destinationTransitionOutView) + destinationTransitionView = destinationTransitionOutView + } + 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 destinationScale = destinationLocalFrame.width / self.previewContainerView.frame.width @@ -1657,10 +1729,21 @@ public final class MediaEditorScreen: ViewController { destinationView.isHidden = false destinationSnapshotView?.removeFromSuperview() 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.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 if transitionOut.destinationCornerRadius > 0.0 { targetCornerRadius = self.previewContainerView.bounds.width @@ -1681,16 +1764,18 @@ public final class MediaEditorScreen: ViewController { componentView.clipsToBounds = true 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.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( - from: componentView.layer.cornerRadius as NSNumber, - to: componentView.bounds.width / 2.0 as NSNumber, - keyPath: "cornerRadius", - timingFunction: kCAMediaTimingFunctionSpring, - duration: 0.4, - 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.animate( + from: componentView.layer.cornerRadius as NSNumber, + to: componentView.bounds.width / 2.0 as NSNumber, + keyPath: "cornerRadius", + timingFunction: kCAMediaTimingFunctionSpring, + duration: 0.4, + removeOnCompletion: false + ) + } } } else if let transitionIn = controller.transitionIn, case .camera = transitionIn { if let view = self.componentHost.view as? MediaEditorScreenComponent.View { @@ -1911,6 +1996,7 @@ public final class MediaEditorScreen: ViewController { context: self.context, isDisplayingTool: self.isDisplayingTool, isInteractingWithEntities: self.isInteractingWithEntities, + isSavingAvailable: controller.isSavingAvailable, isDismissing: self.isDismissing, mediaEditor: self.mediaEditor, privacy: controller.state.privacy, @@ -1947,7 +2033,7 @@ public final class MediaEditorScreen: ViewController { let controller = DrawingScreen(context: self.context, sourceHint: .storyEditor, size: self.previewContainerView.frame.size, originalSize: storyDimensions, isVideo: false, isAvatar: false, drawingView: self.drawingView, entitiesView: self.entitiesView, selectionContainerView: self.selectionContainerView, existingStickerPickerInputData: self.stickerPickerInputData) self.drawingScreen = controller self.drawingView.isUserInteractionEnabled = true - + controller.requestDismiss = { [weak controller, weak self] in self?.drawingScreen = nil controller?.animateOut({ @@ -1955,7 +2041,7 @@ public final class MediaEditorScreen: ViewController { }) self?.drawingView.isUserInteractionEnabled = false self?.animateInFromTool() - + self?.interaction?.activate() self?.entitiesView.selectEntity(nil) } @@ -1966,13 +2052,13 @@ public final class MediaEditorScreen: ViewController { }) self?.drawingView.isUserInteractionEnabled = false self?.animateInFromTool() - + if let result = controller?.generateDrawingResultData() { self?.mediaEditor?.setDrawingAndEntities(data: result.data, image: result.drawingImage, entities: result.entities) } else { self?.mediaEditor?.setDrawingAndEntities(data: nil, image: nil, entities: []) } - + self?.interaction?.activate() self?.entitiesView.selectEntity(nil) } @@ -2039,7 +2125,7 @@ public final class MediaEditorScreen: ViewController { component: AnyComponent( ToolValueComponent( title: "Enhance", - value: "\(Int(enhanceValue * 100.0))" + value: "\(Int(abs(enhanceValue) * 100.0))" ) ), environment: {}, @@ -2072,7 +2158,7 @@ public final class MediaEditorScreen: ViewController { transition.setFrame(view: self.previewContainerView, frame: previewFrame) let entitiesViewScale = previewSize.width / storyDimensions.width 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.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 dismissed: () -> Void = { } + private let hapticFeedback = HapticFeedback() + public init( context: AccountContext, subject: Signal, @@ -2190,6 +2278,8 @@ public final class MediaEditorScreen: ViewController { } func openPrivacySettings() { + self.hapticFeedback.impact(.light) + if case .message(_, _) = self.state.privacy { self.openSendAsMessage() } else { @@ -2308,6 +2398,8 @@ public final class MediaEditorScreen: ViewController { } func presentTimeoutSetup(sourceView: UIView) { + self.hapticFeedback.impact(.light) + var items: [ContextMenuItem] = [] let updateTimeout: (Int?, Bool) -> Void = { [weak self] timeout, archive in @@ -2328,7 +2420,7 @@ public final class MediaEditorScreen: ViewController { let title: String switch self.state.privacy { 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 currentArchived = archivedValue case let .message(_, timeoutValue): @@ -2368,13 +2460,18 @@ public final class MediaEditorScreen: ViewController { 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 }, action: { _, a in a(.default) 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: items.append(.action(ContextMenuActionItem(text: "Until First View", icon: { _ in return nil @@ -2419,6 +2516,7 @@ public final class MediaEditorScreen: ViewController { } func maybePresentDiscardAlert() { + self.hapticFeedback.impact(.light) if "".isEmpty { self.requestDismiss(saveDraft: false, animated: true) return @@ -2474,7 +2572,7 @@ public final class MediaEditorScreen: ViewController { } if let mediaEditor = self.node.mediaEditor { - mediaEditor.stop() + mediaEditor.invalidate() } self.cancelled(saveDraft) @@ -2491,7 +2589,10 @@ public final class MediaEditorScreen: ViewController { } try? FileManager.default.createDirectory(atPath: draftPath(), withIntermediateDirectories: true) + let privacy = self.state.privacy + 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 guard let resultImage else { return @@ -2503,7 +2604,7 @@ public final class MediaEditorScreen: ViewController { let path = draftPath() + "/\(Int64.random(in: .min ... .max)).jpg" if let data = image.jpegData(compressionQuality: 0.87) { 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 { saveStorySource(engine: self.context.engine, item: draft, id: id) } else { @@ -2516,10 +2617,13 @@ public final class MediaEditorScreen: ViewController { let saveVideoDraft: (String, PixelDimensions) -> Void = { videoPath, dimensions in if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) { let path = draftPath() + "/\(Int64.random(in: .min ... .max)).mp4" - _ = thumbnailImage - _ = path - _ = videoPath - _ = dimensions + try? FileManager.default.moveItem(atPath: videoPath, toPath: path) + let draft = MediaEditorDraft(path: path, isVideo: true, thumbnail: thumbnailImage, dimensions: dimensions, values: values, privacy: privacy) + if let id { + 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): if asset.mediaType == .video { 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 { let options = PHImageRequestOptions() @@ -2544,9 +2650,9 @@ public final class MediaEditorScreen: ViewController { } case let .draft(draft, _): if draft.isVideo { - + saveVideoDraft(draft.path, draft.dimensions) } 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) { // removeStoryDraft(engine: self.context.engine, path: draft.path, delete: false) @@ -2567,7 +2673,7 @@ public final class MediaEditorScreen: ViewController { self.dismissAllTooltips() - mediaEditor.stop() + mediaEditor.invalidate() if let navigationController = self.navigationController as? NavigationController { navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate) @@ -2664,21 +2770,26 @@ public final class MediaEditorScreen: ViewController { private var videoExport: MediaEditorVideoExport? private var exportDisposable = MetaDisposable() + fileprivate var isSavingAvailable = false private var previousSavedValues: MediaEditorValues? 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 } - + let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) } let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView) mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities) if let previousSavedValues = self.previousSavedValues, mediaEditor.values == previousSavedValues { - self.node.presentSaveTooltip() return } + + self.hapticFeedback.impact(.light) + 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 saveToPhotos: (String, Bool) -> Void = { path, isVideo in @@ -2998,6 +3109,10 @@ private final class ToolValueComponent: Component { ) if let titleView = self.title.view { 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) } transition.setPosition(view: titleView, position: titleFrame.center) @@ -3020,6 +3135,10 @@ private final class ToolValueComponent: Component { ) if let valueView = self.value.view { 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) } transition.setPosition(view: valueView, position: valueFrame.center) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift index 039f404d44..bf8070a374 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift @@ -692,6 +692,17 @@ private final class MediaToolsScreenComponent: Component { controller.mediaEditor.setToolValue(.blur, value: value) 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: {}, @@ -716,6 +727,20 @@ private final class MediaToolsScreenComponent: Component { controller.mediaEditor.setToolValue(.blur, value: value) 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) + } + } } ) ), diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputActionButtonComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputActionButtonComponent.swift index 65ed6be20c..b9ddc09cf0 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputActionButtonComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputActionButtonComponent.swift @@ -230,7 +230,8 @@ public final class MessageInputActionButtonComponent: Component { context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) 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.fill(CGRect(origin: .zero, size: size)) } diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index 6c1f61890b..f6a3d405cf 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -211,7 +211,7 @@ public final class StoryPeerListComponent: Component { self.updateScrolling(transition: .immediate, keepVisibleUntilCompletion: false) } } - + private func updateScrolling(transition: Transition, keepVisibleUntilCompletion: Bool) { guard let component = self.component, let itemLayout = self.itemLayout else { return @@ -276,14 +276,16 @@ public final class StoryPeerListComponent: Component { hasUnseen = itemSet.hasUnseen var hasItems = true - var itemProgress: Float? + var itemRingAnimation: StoryPeerListItemComponent.RingAnimation? if peer.id == component.context.account.peerId { if let storySubscriptions = component.storySubscriptions, let accountItem = storySubscriptions.accountItem { hasItems = accountItem.storyCount != 0 } else { hasItems = false } - itemProgress = component.uploadProgress + if let uploadProgress = component.uploadProgress { + itemRingAnimation = .progress(uploadProgress) + } } let collapsedItemX: CGFloat @@ -360,7 +362,7 @@ public final class StoryPeerListComponent: Component { peer: peer, hasUnseen: hasUnseen, hasItems: hasItems, - progress: itemProgress, + ringAnimation: itemRingAnimation, collapseFraction: isReallyVisible ? component.collapseFraction : 0.0, collapsedScaleFactor: collapsedItemScaleFactor, collapsedWidth: collapsedItemWidth, diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift index d7f6fce759..ec48fcc84b 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift @@ -74,25 +74,46 @@ private func calculateMergingCircleShape(center: CGPoint, leftCenter: CGPoint?, return path } -private final class StoryProgressLayer: SimpleShapeLayer { +private final class StoryProgressLayer: SimpleLayer { + enum Value: Equatable { + case indefinite + case progress(Float) + } + private struct Params: Equatable { var size: CGSize var lineWidth: CGFloat - var progress: Float + var value: Value } - private var currentParams: Params? + private let uploadProgressLayer = SimpleShapeLayer() + + private let indefiniteDashLayer = SimpleShapeLayer() + private let indefiniteReplicatorLayer = CAReplicatorLayer() + override init() { super.init() - self.fillColor = UIColor.white.cgColor - self.fillRule = .evenOdd + self.uploadProgressLayer.fillColor = nil + self.uploadProgressLayer.strokeColor = UIColor.white.cgColor + self.uploadProgressLayer.lineWidth = 2.0 + self.uploadProgressLayer.lineCap = .round - self.fillColor = nil - self.strokeColor = UIColor.white.cgColor - self.lineWidth = 2.0 - self.lineCap = .round + self.indefiniteDashLayer.fillColor = nil + self.indefiniteDashLayer.strokeColor = UIColor.white.cgColor + self.indefiniteDashLayer.lineWidth = 2.0 + 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) { @@ -105,14 +126,15 @@ private final class StoryProgressLayer: SimpleShapeLayer { func reset() { 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( size: size, lineWidth: lineWidth, - progress: progress + value: value ) if self.currentParams == params { return @@ -120,23 +142,70 @@ private final class StoryProgressLayer: SimpleShapeLayer { self.currentParams = params let lineWidth: CGFloat = 2.0 - - if self.path == nil { + let bounds = CGRect(origin: .zero, size: size) + if self.uploadProgressLayer.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.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 { - let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z") - basicAnimation.duration = 2.0 - basicAnimation.fromValue = NSNumber(value: Float(0.0)) - basicAnimation.toValue = NSNumber(value: Float(Double.pi * 2.0)) - basicAnimation.repeatCount = Float.infinity - basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) - self.add(basicAnimation, forKey: "rotation") + switch value { + case let .progress(progress): + if self.indefiniteReplicatorLayer.superlayer != nil { + self.indefiniteReplicatorLayer.removeFromSuperlayer() + } + if self.uploadProgressLayer.superlayer == nil { + self.addSublayer(self.uploadProgressLayer) + } + 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? public final class StoryPeerListItemComponent: Component { + public enum RingAnimation: Equatable { + case progress(Float) + case loading + } + public let context: AccountContext public let theme: PresentationTheme public let strings: PresentationStrings public let peer: EnginePeer public let hasUnseen: Bool public let hasItems: Bool - public let progress: Float? + public let ringAnimation: RingAnimation? public let collapseFraction: CGFloat public let collapsedScaleFactor: CGFloat public let collapsedWidth: CGFloat @@ -166,7 +240,7 @@ public final class StoryPeerListItemComponent: Component { peer: EnginePeer, hasUnseen: Bool, hasItems: Bool, - progress: Float?, + ringAnimation: RingAnimation?, collapseFraction: CGFloat, collapsedScaleFactor: CGFloat, collapsedWidth: CGFloat, @@ -181,7 +255,7 @@ public final class StoryPeerListItemComponent: Component { self.peer = peer self.hasUnseen = hasUnseen self.hasItems = hasItems - self.progress = progress + self.ringAnimation = ringAnimation self.collapseFraction = collapseFraction self.collapsedScaleFactor = collapsedScaleFactor self.collapsedWidth = collapsedWidth @@ -210,7 +284,7 @@ public final class StoryPeerListItemComponent: Component { if lhs.hasItems != rhs.hasItems { return false } - if lhs.progress != rhs.progress { + if lhs.ringAnimation != rhs.ringAnimation { return false } if lhs.collapseFraction != rhs.collapseFraction { @@ -255,6 +329,8 @@ public final class StoryPeerListItemComponent: Component { private var component: StoryPeerListItemComponent? private weak var componentState: EmptyComponentState? + private var demoLoading = false + public override init(frame: CGRect) { self.backgroundContainer = UIView() self.backgroundContainer.isUserInteractionEnabled = false @@ -378,7 +454,7 @@ public final class StoryPeerListItemComponent: Component { self.containerNode.frame = CGRect(origin: CGPoint(), size: size) 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 previousComponent = self.component @@ -411,7 +487,7 @@ public final class StoryPeerListItemComponent: Component { } self.avatarBackgroundView.image = avatarBackgroundImage - self.avatarBackgroundView.isHidden = component.progress != nil + self.avatarBackgroundView.isHidden = component.ringAnimation != nil if themeUpdated { self.avatarBackgroundView.tintColor = component.theme.rootController.navigationBar.opaqueBackgroundColor @@ -426,7 +502,7 @@ public final class StoryPeerListItemComponent: Component { let baseLineWidth: CGFloat let minimizedLineWidth: CGFloat = 3.0 - if component.hasUnseen || component.progress != nil { + if component.hasUnseen || component.ringAnimation != nil { baseLineWidth = 2.0 } else { 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.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 let avatarAddBadgeView: UIImageView @@ -503,11 +579,11 @@ public final class StoryPeerListItemComponent: Component { 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 colors: [CGColor] - if component.hasUnseen || component.progress != nil { + if component.hasUnseen || component.ringAnimation != nil { colors = [UIColor(rgb: 0x34C76F).cgColor, UIColor(rgb: 0x3DA1FD).cgColor] } else { colors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor] @@ -537,7 +613,7 @@ public final class StoryPeerListItemComponent: Component { let avatarPath = CGMutablePath() 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 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 { @@ -550,7 +626,7 @@ public final class StoryPeerListItemComponent: Component { //TODO:localize let titleString: String if component.peer.id == component.context.account.peerId { - if let _ = component.progress { + if let ringAnimation = component.ringAnimation, case .progress = ringAnimation { titleString = "Uploading..." } else { titleString = "My story" @@ -560,7 +636,7 @@ public final class StoryPeerListItemComponent: Component { } 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() { titleView.superview?.addSubview(snapshotView) 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) } - if let progress = component.progress { + if let ringAnimation = component.ringAnimation { var progressTransition = transition let progressLayer: StoryProgressLayer 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) 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 } else { self.indicatorShapeLayer.opacity = 1.0 diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenContactInfoItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenContactInfoItem.swift index 01980ccd1f..0144179eb7 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenContactInfoItem.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenContactInfoItem.swift @@ -294,9 +294,10 @@ private final class PeerInfoScreenContactInfoItemNode: PeerInfoScreenItemNode { var height = topOffset * 2.0 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) - height += max(usernameSize.height, phoneSize.height) - - let additionalTextFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: additionalTextSize) + let textHeight = max(usernameSize.height, phoneSize.height) + height += textHeight + + 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.phoneNumberNode, frame: phoneFrame) @@ -385,7 +386,7 @@ private final class PeerInfoScreenContactInfoItemNode: PeerInfoScreenItemNode { if let current = self.linkHighlightingNode { linkHighlightingNode = current } else { - linkHighlightingNode = LinkHighlightingNode(color: theme.list.itemAccentColor.withAlphaComponent(0.5)) + linkHighlightingNode = LinkHighlightingNode(color: theme.list.itemAccentColor.withAlphaComponent(0.2)) self.linkHighlightingNode = linkHighlightingNode self.contextSourceNode.contentNode.insertSubnode(linkHighlightingNode, belowSubnode: textNode) } diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift index a8a1d3fcf0..8093277652 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift @@ -635,7 +635,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { if let current = self.linkHighlightingNode { linkHighlightingNode = current } else { - linkHighlightingNode = LinkHighlightingNode(color: theme.list.itemAccentColor.withAlphaComponent(0.5)) + linkHighlightingNode = LinkHighlightingNode(color: theme.list.itemAccentColor.withAlphaComponent(0.2)) self.linkHighlightingNode = linkHighlightingNode self.contextSourceNode.contentNode.insertSubnode(linkHighlightingNode, belowSubnode: textNode) } diff --git a/submodules/TelegramUpdateUI/Sources/UpdateInfoItem.swift b/submodules/TelegramUpdateUI/Sources/UpdateInfoItem.swift index 02f9e76230..8482927fc7 100644 --- a/submodules/TelegramUpdateUI/Sources/UpdateInfoItem.swift +++ b/submodules/TelegramUpdateUI/Sources/UpdateInfoItem.swift @@ -431,7 +431,7 @@ class UpdateInfoItemNode: ListViewItemNode { if let current = self.linkHighlightingNode { linkHighlightingNode = current } 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.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode) } diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 8045be8cbd..f0c925d8fe 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -544,7 +544,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { if let queryItems = components.queryItems { for queryItem in queryItems { if let value = queryItem.value { - if queryItem.name == "startApp"{ + if queryItem.name == "startapp" { startApp = value } }