Various improvements

This commit is contained in:
Ilya Laktyushin 2023-08-05 15:32:12 +02:00
parent 6016ae3b98
commit 74faad7a03
56 changed files with 846 additions and 275 deletions

View File

@ -9738,6 +9738,7 @@ Sorry for the inconvenience.";
"Story.PrivacyTooltipSelectedContacts.Contacts_1" = "1 contact"; "Story.PrivacyTooltipSelectedContacts.Contacts_1" = "1 contact";
"Story.PrivacyTooltipSelectedContacts.Contacts_any" = "%@ contacts"; "Story.PrivacyTooltipSelectedContacts.Contacts_any" = "%@ contacts";
"Story.PrivacyTooltipSelectedContactsFull" = "This story is now shown to %@.";
"Story.Privacy.GrayListSelect" = "[Select people]() who will never see any of your stories."; "Story.Privacy.GrayListSelect" = "[Select people]() who will never see any of your stories.";
"Story.Privacy.GrayListSelected" = "[%@]() will never see any of your stories."; "Story.Privacy.GrayListSelected" = "[%@]() will never see any of your stories.";

View File

@ -894,7 +894,7 @@ public protocol SharedAccountContext: AnyObject {
func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?) -> ViewController func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?) -> ViewController
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool) -> ViewController
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> Void) -> ViewController func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> Void) -> ViewController
func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, action: @escaping () -> Void) -> ViewController func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, action: @escaping () -> Void) -> ViewController

View File

@ -2634,7 +2634,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
location: .point(location, .top), location: .point(location, .top),
shouldDismissOnTouch: { [weak self] point, containerFrame in shouldDismissOnTouch: { [weak self] point, containerFrame in
if containerFrame.contains(point), premiumNeeded { if containerFrame.contains(point), premiumNeeded {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .stories) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .stories, forceDark: false)
self?.push(controller) self?.push(controller)
return .dismiss(consume: true) return .dismiss(consume: true)
} else { } else {

View File

@ -1562,7 +1562,7 @@ public final class ChatListNode: ListView {
let _ = dismissServerProvidedSuggestion(account: self.context.account, suggestion: .restorePremium).start() let _ = dismissServerProvidedSuggestion(account: self.context.account, suggestion: .restorePremium).start()
} }
} }
let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .ads) let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .ads, forceDark: false)
self.push?(controller) self.push?(controller)
}, openChatFolderUpdates: { [weak self] in }, openChatFolderUpdates: { [weak self] in
guard let self else { guard let self else {

View File

@ -137,9 +137,11 @@ public final class PagerComponentPanelEnvironment<TopPanelEnvironment>: Equatabl
public struct PagerComponentPanelState { public struct PagerComponentPanelState {
public var topPanelHeight: CGFloat public var topPanelHeight: CGFloat
public var scrollingPanelOffsetToTopEdge: CGFloat
public init(topPanelHeight: CGFloat) { public init(topPanelHeight: CGFloat, scrollingPanelOffsetToTopEdge: CGFloat) {
self.topPanelHeight = topPanelHeight self.topPanelHeight = topPanelHeight
self.scrollingPanelOffsetToTopEdge = scrollingPanelOffsetToTopEdge
} }
} }
@ -205,6 +207,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
public let contentIdUpdated: (AnyHashable) -> Void public let contentIdUpdated: (AnyHashable) -> Void
public let panelHideBehavior: PagerComponentPanelHideBehavior public let panelHideBehavior: PagerComponentPanelHideBehavior
public let clipContentToTopPanel: Bool public let clipContentToTopPanel: Bool
public let isExpanded: Bool
public init( public init(
isContentInFocus: Bool, isContentInFocus: Bool,
@ -225,7 +228,8 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
isTopPanelHiddenUpdated: @escaping (Bool, Transition) -> Void, isTopPanelHiddenUpdated: @escaping (Bool, Transition) -> Void,
contentIdUpdated: @escaping (AnyHashable) -> Void, contentIdUpdated: @escaping (AnyHashable) -> Void,
panelHideBehavior: PagerComponentPanelHideBehavior, panelHideBehavior: PagerComponentPanelHideBehavior,
clipContentToTopPanel: Bool clipContentToTopPanel: Bool,
isExpanded: Bool
) { ) {
self.isContentInFocus = isContentInFocus self.isContentInFocus = isContentInFocus
self.contentInsets = contentInsets self.contentInsets = contentInsets
@ -246,6 +250,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
self.contentIdUpdated = contentIdUpdated self.contentIdUpdated = contentIdUpdated
self.panelHideBehavior = panelHideBehavior self.panelHideBehavior = panelHideBehavior
self.clipContentToTopPanel = clipContentToTopPanel self.clipContentToTopPanel = clipContentToTopPanel
self.isExpanded = isExpanded
} }
public static func ==(lhs: PagerComponent, rhs: PagerComponent) -> Bool { public static func ==(lhs: PagerComponent, rhs: PagerComponent) -> Bool {
@ -288,7 +293,9 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
if lhs.clipContentToTopPanel != rhs.clipContentToTopPanel { if lhs.clipContentToTopPanel != rhs.clipContentToTopPanel {
return false return false
} }
if lhs.isExpanded != rhs.isExpanded {
return false
}
return true return true
} }
@ -534,6 +541,11 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
scrollingPanelOffsetFraction = 0.0 scrollingPanelOffsetFraction = 0.0
} }
var scrollingPanelOffsetToTopEdge: CGFloat = 0.0
if let centralId = centralId, let centralContentView = self.contentViews[centralId] {
scrollingPanelOffsetToTopEdge = centralContentView.scrollingPanelOffsetToTopEdge
}
var topPanelVisibility: CGFloat = 1.0 var topPanelVisibility: CGFloat = 1.0
if let centralId = centralId, let index = component.contents.firstIndex(where: { $0.id == centralId }) { if let centralId = centralId, let index = component.contents.firstIndex(where: { $0.id == centralId }) {
if let paneTransitionGestureState = self.paneTransitionGestureState { if let paneTransitionGestureState = self.paneTransitionGestureState {
@ -745,7 +757,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
effectiveTopPanelHeight = 0.0 effectiveTopPanelHeight = 0.0
case .show, .hideOnScroll: case .show, .hideOnScroll:
if component.externalTopPanelContainer != nil { if component.externalTopPanelContainer != nil {
effectiveTopPanelHeight = topPanelHeight effectiveTopPanelHeight = component.isExpanded ? 0.0 : topPanelHeight
} else { } else {
effectiveTopPanelHeight = 0.0 effectiveTopPanelHeight = 0.0
} }
@ -923,7 +935,8 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
if let panelStateUpdated = component.panelStateUpdated { if let panelStateUpdated = component.panelStateUpdated {
panelStateUpdated( panelStateUpdated(
PagerComponentPanelState( PagerComponentPanelState(
topPanelHeight: topPanelHeight topPanelHeight: topPanelHeight,
scrollingPanelOffsetToTopEdge: scrollingPanelOffsetToTopEdge
), ),
panelStateTransition panelStateTransition
) )

View File

@ -22,7 +22,7 @@ final class ContextMenuActionNode: ASDisplayNode {
var dismiss: (() -> Void)? var dismiss: (() -> Void)?
init(action: ContextMenuAction) { init(action: ContextMenuAction, blurred: Bool) {
self.actionArea = AccessibilityAreaNode() self.actionArea = AccessibilityAreaNode()
self.actionArea.accessibilityTraits = .button self.actionArea.accessibilityTraits = .button
@ -66,7 +66,10 @@ final class ContextMenuActionNode: ASDisplayNode {
super.init() super.init()
self.backgroundColor = UIColor(rgb: 0x2f2f2f) if !blurred {
self.backgroundColor = UIColor(rgb: 0x2f2f2f)
}
if let textNode = self.textNode { if let textNode = self.textNode {
self.addSubnode(textNode) self.addSubnode(textNode)
} }
@ -75,7 +78,11 @@ final class ContextMenuActionNode: ASDisplayNode {
} }
self.button.highligthedChanged = { [weak self] highlighted in self.button.highligthedChanged = { [weak self] highlighted in
self?.backgroundColor = highlighted ? UIColor(rgb: 0x8c8e8e) : UIColor(rgb: 0x2f2f2f) if blurred {
self?.backgroundColor = highlighted ? UIColor(rgb: 0xffffff, alpha: 0.5) : .clear
} else {
self?.backgroundColor = highlighted ? UIColor(rgb: 0x8c8e8e) : UIColor(rgb: 0x2f2f2f)
}
} }
self.view.addSubview(self.button) self.view.addSubview(self.button)
self.addSubnode(self.actionArea) self.addSubnode(self.actionArea)

View File

@ -20,16 +20,18 @@ public final class ContextMenuContainerNode: ASDisplayNode {
public var relativeArrowPosition: (CGFloat, Bool)? public var relativeArrowPosition: (CGFloat, Bool)?
//private let effectView: UIVisualEffectView private var effectView: UIVisualEffectView?
override public init() { public init(blurred: Bool) {
//self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
super.init() super.init()
self.backgroundColor = UIColor(rgb: 0x8c8e8e) if blurred {
//self.view.addSubview(self.effectView) let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
//self.effectView.mask = self.maskView self.view.addSubview(effectView)
self.effectView = effectView
} else {
self.backgroundColor = UIColor(rgb: 0x8c8e8e)
}
self.view.mask = self.maskView self.view.mask = self.maskView
} }
@ -46,7 +48,7 @@ public final class ContextMenuContainerNode: ASDisplayNode {
} }
public func updateLayout(transition: ContainedViewLayoutTransition) { public func updateLayout(transition: ContainedViewLayoutTransition) {
//self.effectView.frame = self.bounds self.effectView?.frame = self.bounds
let maskParams = CachedMaskParams(size: self.bounds.size, relativeArrowPosition: self.relativeArrowPosition?.0 ?? self.bounds.size.width / 2.0, arrowOnBottom: self.relativeArrowPosition?.1 ?? true) let maskParams = CachedMaskParams(size: self.bounds.size, relativeArrowPosition: self.relativeArrowPosition?.0 ?? self.bounds.size.width / 2.0, arrowOnBottom: self.relativeArrowPosition?.1 ?? true)
if self.cachedMaskParams != maskParams { if self.cachedMaskParams != maskParams {

View File

@ -27,16 +27,18 @@ public final class ContextMenuController: ViewController, KeyShortcutResponder,
private let actions: [ContextMenuAction] private let actions: [ContextMenuAction]
private let catchTapsOutside: Bool private let catchTapsOutside: Bool
private let hasHapticFeedback: Bool private let hasHapticFeedback: Bool
private let blurred: Bool
private var layout: ContainerViewLayout? private var layout: ContainerViewLayout?
public var centerHorizontally = false public var centerHorizontally = false
public var dismissed: (() -> Void)? public var dismissed: (() -> Void)?
public init(actions: [ContextMenuAction], catchTapsOutside: Bool = false, hasHapticFeedback: Bool = false) { public init(actions: [ContextMenuAction], catchTapsOutside: Bool = false, hasHapticFeedback: Bool = false, blurred: Bool = false) {
self.actions = actions self.actions = actions
self.catchTapsOutside = catchTapsOutside self.catchTapsOutside = catchTapsOutside
self.hasHapticFeedback = hasHapticFeedback self.hasHapticFeedback = hasHapticFeedback
self.blurred = blurred
super.init(navigationBarPresentationData: nil) super.init(navigationBarPresentationData: nil)
@ -53,7 +55,7 @@ public final class ContextMenuController: ViewController, KeyShortcutResponder,
self?.contextMenuNode.animateOut(bounce: (self?.presentationArguments as? ContextMenuControllerPresentationArguments)?.bounce ?? true, completion: { self?.contextMenuNode.animateOut(bounce: (self?.presentationArguments as? ContextMenuControllerPresentationArguments)?.bounce ?? true, completion: {
self?.presentingViewController?.dismiss(animated: false) self?.presentingViewController?.dismiss(animated: false)
}) })
}, catchTapsOutside: self.catchTapsOutside, hasHapticFeedback: self.hasHapticFeedback) }, catchTapsOutside: self.catchTapsOutside, hasHapticFeedback: self.hasHapticFeedback, blurred: self.blurred)
self.displayNodeDidLoad() self.displayNodeDidLoad()
} }

View File

@ -145,16 +145,16 @@ final class ContextMenuNode: ASDisplayNode {
private let feedback: HapticFeedback? private let feedback: HapticFeedback?
init(actions: [ContextMenuAction], dismiss: @escaping () -> Void, catchTapsOutside: Bool, hasHapticFeedback: Bool = false) { init(actions: [ContextMenuAction], dismiss: @escaping () -> Void, catchTapsOutside: Bool, hasHapticFeedback: Bool = false, blurred: Bool = false) {
self.actions = actions self.actions = actions
self.dismiss = dismiss self.dismiss = dismiss
self.catchTapsOutside = catchTapsOutside self.catchTapsOutside = catchTapsOutside
self.containerNode = ContextMenuContainerNode() self.containerNode = ContextMenuContainerNode(blurred: blurred)
self.scrollNode = ContextMenuContentScrollNode() self.scrollNode = ContextMenuContentScrollNode()
self.actionNodes = actions.map { action in self.actionNodes = actions.map { action in
return ContextMenuActionNode(action: action) return ContextMenuActionNode(action: action, blurred: blurred)
} }
if hasHapticFeedback { if hasHapticFeedback {

View File

@ -33,7 +33,7 @@ final class TooltipControllerNode: ASDisplayNode {
self.dismissByTapOutside = dismissByTapOutside self.dismissByTapOutside = dismissByTapOutside
self.dismissByTapOutsideSource = dismissByTapOutsideSource self.dismissByTapOutsideSource = dismissByTapOutsideSource
self.containerNode = ContextMenuContainerNode() self.containerNode = ContextMenuContainerNode(blurred: false)
self.containerNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8) self.containerNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
self.imageNode = ASImageNode() self.imageNode = ASImageNode()

View File

@ -3,6 +3,10 @@ import UIKit
import Display import Display
import SwiftSignalKit import SwiftSignalKit
import AccountContext import AccountContext
import TelegramCore
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import StickerResources
import MediaEditor import MediaEditor
private func generateIcon(style: DrawingLocationEntity.Style) -> UIImage? { private func generateIcon(style: DrawingLocationEntity.Style) -> UIImage? {
@ -50,6 +54,12 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
let textView: DrawingTextView let textView: DrawingTextView
let iconView: UIImageView let iconView: UIImageView
private let imageNode: TransformImageNode
private var animationNode: AnimatedStickerNode?
private var didSetUpAnimationNode = false
private let stickerFetchedDisposable = MetaDisposable()
private let cachedDisposable = MetaDisposable()
init(context: AccountContext, entity: DrawingLocationEntity) { init(context: AccountContext, entity: DrawingLocationEntity) {
self.backgroundView = UIView() self.backgroundView = UIView()
@ -79,6 +89,7 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
self.textView.textContainer.lineBreakMode = .byTruncatingTail self.textView.textContainer.lineBreakMode = .byTruncatingTail
self.iconView = UIImageView() self.iconView = UIImageView()
self.imageNode = TransformImageNode()
super.init(context: context, entity: entity) super.init(context: context, entity: entity)
@ -89,6 +100,8 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
self.addSubview(self.iconView) self.addSubview(self.iconView)
self.update(animated: false) self.update(animated: false)
self.setup()
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -100,7 +113,14 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
self.textView.setNeedsLayersUpdate() self.textView.setNeedsLayersUpdate()
var result = self.textView.sizeThatFits(CGSize(width: self.locationEntity.width, height: .greatestFiniteMagnitude)) var result = self.textView.sizeThatFits(CGSize(width: self.locationEntity.width, height: .greatestFiniteMagnitude))
self.textSize = result self.textSize = result
result.width = floorToScreenPixels(max(224.0, ceil(result.width) + 20.0) + result.height * 0.55)
let widthExtension: CGFloat
if self.locationEntity.icon != nil {
widthExtension = result.height * 0.77
} else {
widthExtension = result.height * 0.65
}
result.width = floorToScreenPixels(max(224.0, ceil(result.width) + 20.0) + widthExtension)
result.height = ceil(result.height * 1.2); result.height = ceil(result.height * 1.2);
return result; return result;
} }
@ -117,9 +137,23 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
public override func layoutSubviews() { public override func layoutSubviews() {
super.layoutSubviews() super.layoutSubviews()
let iconSize = min(76.0, floor(self.bounds.height * 0.6)) let iconSize: CGFloat
self.iconView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(iconSize * 0.25), y: floorToScreenPixels((self.bounds.height - iconSize) / 2.0)), size: CGSize(width: iconSize, height: iconSize)) let iconOffset: CGFloat
self.textView.frame = CGRect(origin: CGPoint(x: self.bounds.width - self.textSize.width, y: floorToScreenPixels((self.bounds.height - self.textSize.height) / 2.0)), size: self.textSize) if self.locationEntity.icon != nil {
iconSize = min(80.0, floor(self.bounds.height * 0.7))
iconOffset = 0.2
} else {
iconSize = min(76.0, floor(self.bounds.height * 0.6))
iconOffset = 0.3
}
self.iconView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(iconSize * iconOffset), y: floorToScreenPixels((self.bounds.height - iconSize) / 2.0)), size: CGSize(width: iconSize, height: iconSize))
self.imageNode.frame = self.iconView.frame.offsetBy(dx: 0.0, dy: 2.0)
let imageSize = CGSize(width: iconSize, height: iconSize)
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
self.textView.frame = CGRect(origin: CGPoint(x: self.bounds.width - self.textSize.width - 6.0, y: floorToScreenPixels((self.bounds.height - self.textSize.height) / 2.0)), size: self.textSize)
self.backgroundView.frame = self.bounds self.backgroundView.frame = self.bounds
self.blurredBackgroundView.frame = self.bounds self.blurredBackgroundView.frame = self.bounds
self.blurredBackgroundView.update(size: self.bounds.size, transition: .immediate) self.blurredBackgroundView.update(size: self.bounds.size, transition: .immediate)
@ -170,7 +204,7 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
let font = Font.with(size: fontSize, design: .camera, weight: .semibold) let font = Font.with(size: fontSize, design: .camera, weight: .semibold)
text.addAttribute(.font, value: font, range: range) text.addAttribute(.font, value: font, range: range)
text.addAttribute(.kern, value: -1.5 as NSNumber, range: range) text.addAttribute(.kern, value: -3.5 as NSNumber, range: range)
self.textView.font = font self.textView.font = font
let paragraphStyle = NSMutableParagraphStyle() let paragraphStyle = NSMutableParagraphStyle()
@ -240,6 +274,52 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
super.update(animated: animated) super.update(animated: animated)
} }
private func setup() {
if let file = self.locationEntity.icon {
self.iconView.isHidden = true
self.addSubnode(self.imageNode)
if let dimensions = file.dimensions {
if file.isAnimatedSticker || file.isVideoSticker || file.mimeType == "video/webm" {
if self.animationNode == nil {
let animationNode = DefaultAnimatedStickerNodeImpl()
animationNode.autoplay = false
self.animationNode = animationNode
animationNode.started = { [weak self, weak animationNode] in
self?.imageNode.isHidden = true
let _ = animationNode
// if let animationNode = animationNode {
// let _ = (animationNode.status
// |> take(1)
// |> deliverOnMainQueue).start(next: { [weak self] status in
// self?.started?(status.duration)
// })
// }
}
self.addSubnode(animationNode)
if file.isCustomTemplateEmoji {
animationNode.dynamicColor = UIColor(rgb: 0xffffff)
}
}
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: self.context.account.postbox, userLocation: .other, file: file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 256.0, height: 256.0))))
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: stickerPackFileReference(file), resource: file.resource).start())
} else {
if let animationNode = self.animationNode {
animationNode.visibility = false
self.animationNode = nil
animationNode.removeFromSupernode()
self.imageNode.isHidden = false
self.didSetUpAnimationNode = false
}
self.imageNode.setSignal(chatMessageSticker(account: self.context.account, userLocation: .other, file: file, small: false, synchronousLoad: false))
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: stickerPackFileReference(file), resource: chatMessageStickerResource(file: file, small: false)).start())
}
self.setNeedsLayout()
}
}
}
override func updateSelectionView() { override func updateSelectionView() {
guard let selectionView = self.selectionView as? DrawingLocationEntititySelectionView else { guard let selectionView = self.selectionView as? DrawingLocationEntititySelectionView else {
return return
@ -276,6 +356,34 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
UIGraphicsEndImageContext() UIGraphicsEndImageContext()
return image return image
} }
func getRenderSubEntities() -> [DrawingEntity] {
// let textSize = self.textView.bounds.size
// let textPosition = self.locationEntity.position
// let scale = self.locationEntity.scale
// let rotation = self.locationEntity.rotation
//
// let itemSize: CGFloat = floor(24.0 * self.displayFontSize * 0.78 / 17.0)
let entities: [DrawingEntity] = []
// for (emojiRect, emojiAttribute) in self.emojiRects {
// guard let file = emojiAttribute.file else {
// continue
// }
// let emojiTextPosition = emojiRect.center.offsetBy(dx: -textSize.width / 2.0, dy: -textSize.height / 2.0)
//
// let entity = DrawingStickerEntity(content: .file(file))
// entity.referenceDrawingSize = CGSize(width: itemSize * 4.0, height: itemSize * 4.0)
// entity.scale = scale
// entity.position = textPosition.offsetBy(
// dx: (emojiTextPosition.x * cos(rotation) - emojiTextPosition.y * sin(rotation)) * scale,
// dy: (emojiTextPosition.y * cos(rotation) + emojiTextPosition.x * sin(rotation)) * scale
// )
// entity.rotation = rotation
// entities.append(entity)
// }
return entities
}
} }
final class DrawingLocationEntititySelectionView: DrawingEntitySelectionView { final class DrawingLocationEntititySelectionView: DrawingEntitySelectionView {

View File

@ -112,6 +112,8 @@ private final class StickerSelectionComponent: Component {
private var searchVisible = false private var searchVisible = false
private var forceUpdate = false private var forceUpdate = false
private var topPanelScrollingOffset: CGFloat = 0.0
override init(frame: CGRect) { override init(frame: CGRect) {
self.keyboardView = ComponentView<Empty>() self.keyboardView = ComponentView<Empty>()
self.keyboardClippingView = UIView() self.keyboardClippingView = UIView()
@ -253,7 +255,13 @@ private final class StickerSelectionComponent: Component {
externalTopPanelContainer: self.panelHostView, externalTopPanelContainer: self.panelHostView,
externalBottomPanelContainer: nil, externalBottomPanelContainer: nil,
displayTopPanelBackground: .blur, displayTopPanelBackground: .blur,
topPanelExtensionUpdated: { _, _ in }, topPanelExtensionUpdated: { _, _ in
},
topPanelScrollingOffset: { [weak self] offset, transition in
if let self {
self.topPanelScrollingOffset = offset
}
},
hideInputUpdated: { [weak self] _, searchVisible, transition in hideInputUpdated: { [weak self] _, searchVisible, transition in
guard let self else { guard let self else {
return return
@ -263,7 +271,6 @@ private final class StickerSelectionComponent: Component {
self.state?.updated(transition: transition) self.state?.updated(transition: transition)
}, },
hideTopPanelUpdated: { _, _ in hideTopPanelUpdated: { _, _ in
print()
}, },
switchToTextInput: {}, switchToTextInput: {},
switchToGifSubject: { _ in }, switchToGifSubject: { _ in },
@ -330,8 +337,15 @@ private final class StickerSelectionComponent: Component {
transition.setFrame(view: self.panelBackgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: keyboardSize.width, height: topPanelHeight))) transition.setFrame(view: self.panelBackgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: keyboardSize.width, height: topPanelHeight)))
self.panelBackgroundView.update(size: self.panelBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition) self.panelBackgroundView.update(size: self.panelBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition)
transition.setAlpha(view: self.panelBackgroundView, alpha: self.searchVisible ? 0.0 : 1.0) let topPanelAlpha: CGFloat
transition.setAlpha(view: self.panelSeparatorView, alpha: self.searchVisible ? 0.0 : 1.0) if self.searchVisible {
topPanelAlpha = 0.0
} else {
topPanelAlpha = max(0.0, min(1.0, (self.topPanelScrollingOffset / 20.0)))
}
transition.setAlpha(view: self.panelBackgroundView, alpha: topPanelAlpha)
transition.setAlpha(view: self.panelSeparatorView, alpha: topPanelAlpha)
transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight), size: CGSize(width: keyboardSize.width, height: UIScreenPixel))) transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight), size: CGSize(width: keyboardSize.width, height: UIScreenPixel)))
} }

View File

@ -1353,7 +1353,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
} }
controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text, customUndoText: nil, timeout: nil), elevatedLayout: true, animateInAsReplacement: false, action: { action in controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text, customUndoText: nil, timeout: nil), elevatedLayout: true, animateInAsReplacement: false, action: { action in
if case .info = action { if case .info = action {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .savedGifs) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .savedGifs, forceDark: false)
controllerInteraction?.pushController(controller) controllerInteraction?.pushController(controller)
return true return true
} }
@ -1566,7 +1566,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
} }
controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text, customUndoText: nil, timeout: nil), elevatedLayout: true, animateInAsReplacement: false, action: { action in controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text, customUndoText: nil, timeout: nil), elevatedLayout: true, animateInAsReplacement: false, action: { action in
if case .info = action { if case .info = action {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .savedGifs) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .savedGifs, forceDark: false)
controllerInteraction?.pushController(controller) controllerInteraction?.pushController(controller)
return true return true
} }

View File

@ -3,10 +3,10 @@ import Contacts
import CoreLocation import CoreLocation
import SwiftSignalKit import SwiftSignalKit
public func geocodeLocation(address: String) -> Signal<[CLPlacemark]?, NoError> { public func geocodeLocation(address: String, locale: Locale? = nil) -> Signal<[CLPlacemark]?, NoError> {
return Signal { subscriber in return Signal { subscriber in
let geocoder = CLGeocoder() let geocoder = CLGeocoder()
geocoder.geocodeAddressString(address) { (placemarks, _) in geocoder.geocodeAddressString(address, in: nil, preferredLocale: locale) { placemarks, _ in
subscriber.putNext(placemarks) subscriber.putNext(placemarks)
subscriber.putCompletion() subscriber.putCompletion()
} }
@ -16,17 +16,17 @@ public func geocodeLocation(address: String) -> Signal<[CLPlacemark]?, NoError>
} }
} }
public func geocodeLocation(address: CNPostalAddress) -> Signal<(Double, Double)?, NoError> { public func geocodeLocation(address: CNPostalAddress, locale: Locale? = nil) -> Signal<(Double, Double)?, NoError> {
return Signal { subscriber in return Signal { subscriber in
let geocoder = CLGeocoder() let geocoder = CLGeocoder()
geocoder.geocodePostalAddress(address, completionHandler: { placemarks, _ in geocoder.geocodePostalAddress(address, preferredLocale: locale) { placemarks, _ in
if let location = placemarks?.first?.location { if let location = placemarks?.first?.location {
subscriber.putNext((location.coordinate.latitude, location.coordinate.longitude)) subscriber.putNext((location.coordinate.latitude, location.coordinate.longitude))
} else { } else {
subscriber.putNext(nil) subscriber.putNext(nil)
} }
subscriber.putCompletion() subscriber.putCompletion()
}) }
return ActionDisposable { return ActionDisposable {
geocoder.cancelGeocode() geocoder.cancelGeocode()
} }
@ -34,9 +34,11 @@ public func geocodeLocation(address: CNPostalAddress) -> Signal<(Double, Double)
} }
public struct ReverseGeocodedPlacemark { public struct ReverseGeocodedPlacemark {
public let name: String?
public let street: String? public let street: String?
public let city: String? public let city: String?
public let country: String? public let country: String?
public let countryCode: String?
public var compactDisplayAddress: String? { public var compactDisplayAddress: String? {
if let street = self.street { if let street = self.street {
@ -67,17 +69,20 @@ public struct ReverseGeocodedPlacemark {
} }
} }
public func reverseGeocodeLocation(latitude: Double, longitude: Double) -> Signal<ReverseGeocodedPlacemark?, NoError> { public func reverseGeocodeLocation(latitude: Double, longitude: Double, locale: Locale? = nil) -> Signal<ReverseGeocodedPlacemark?, NoError> {
return Signal { subscriber in return Signal { subscriber in
let geocoder = CLGeocoder() let geocoder = CLGeocoder()
let locale = Locale(identifier: "en-US")
geocoder.reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude), preferredLocale: locale, completionHandler: { placemarks, _ in geocoder.reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude), preferredLocale: locale, completionHandler: { placemarks, _ in
if let placemarks = placemarks, let placemark = placemarks.first { if let placemarks = placemarks, let placemark = placemarks.first {
let result: ReverseGeocodedPlacemark let result: ReverseGeocodedPlacemark
if placemark.thoroughfare == nil && placemark.locality == nil && placemark.country == nil { if placemark.thoroughfare == nil && placemark.locality == nil && placemark.country == nil {
result = ReverseGeocodedPlacemark(street: placemark.name, city: nil, country: nil) result = ReverseGeocodedPlacemark(name: placemark.name, street: placemark.name, city: nil, country: nil, countryCode: nil)
} else { } else {
result = ReverseGeocodedPlacemark(street: placemark.thoroughfare, city: placemark.locality, country: placemark.country) if placemark.thoroughfare == nil && placemark.locality == nil, let ocean = placemark.ocean {
result = ReverseGeocodedPlacemark(name: ocean, street: nil, city: nil, country: placemark.country, countryCode: placemark.isoCountryCode)
} else {
result = ReverseGeocodedPlacemark(name: nil, street: placemark.thoroughfare, city: placemark.locality, country: placemark.country, countryCode: placemark.isoCountryCode)
}
} }
subscriber.putNext(result) subscriber.putNext(result)
subscriber.putCompletion() subscriber.putCompletion()
@ -91,3 +96,13 @@ public func reverseGeocodeLocation(latitude: Double, longitude: Double) -> Signa
} }
} }
} }
let customAbbreviations = ["AE": "UAE", "GB": "UK", "US": "USA"]
public func displayCountryName(_ countryCode: String, locale: Locale?) -> String {
let locale = locale ?? Locale.current
if locale.identifier.lowercased().contains("en"), let shortName = customAbbreviations[countryCode] {
return shortName
} else {
return locale.localizedString(forRegionCode: countryCode) ?? countryCode
}
}

View File

@ -171,6 +171,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
var isDragging = false var isDragging = false
var beganInteractiveDragging: (() -> Void)? var beganInteractiveDragging: (() -> Void)?
var endedInteractiveDragging: ((CLLocationCoordinate2D) -> Void)? var endedInteractiveDragging: ((CLLocationCoordinate2D) -> Void)?
var disableHorizontalTransitionGesture = false
var annotationSelected: ((LocationPinAnnotation?) -> Void)? var annotationSelected: ((LocationPinAnnotation?) -> Void)?
var userLocationAnnotationSelected: (() -> Void)? var userLocationAnnotationSelected: (() -> Void)?
@ -249,6 +250,11 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
return false return false
} }
} }
self.mapView?.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
return self?.disableHorizontalTransitionGesture == true
}
self.mapView?.delegate = self self.mapView?.delegate = self
self.mapView?.mapType = self.mapMode.mapType self.mapView?.mapType = self.mapMode.mapType
self.mapView?.isRotateEnabled = self.isRotateEnabled self.mapView?.isRotateEnabled = self.isRotateEnabled

View File

@ -9,15 +9,14 @@ import SegmentedControlNode
final class LocationOptionsNode: ASDisplayNode { final class LocationOptionsNode: ASDisplayNode {
private var presentationData: PresentationData private var presentationData: PresentationData
private let backgroundNode: ASDisplayNode private let backgroundNode: NavigationBackgroundNode
private let separatorNode: ASDisplayNode private let separatorNode: ASDisplayNode
private let segmentedControlNode: SegmentedControlNode private let segmentedControlNode: SegmentedControlNode
init(presentationData: PresentationData, updateMapMode: @escaping (LocationMapMode) -> Void) { init(presentationData: PresentationData, updateMapMode: @escaping (LocationMapMode) -> Void) {
self.presentationData = presentationData self.presentationData = presentationData
self.backgroundNode = ASDisplayNode() self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor)
self.backgroundNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.opaqueBackgroundColor
self.separatorNode = ASDisplayNode() self.separatorNode = ASDisplayNode()
self.separatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor self.separatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
@ -45,13 +44,15 @@ final class LocationOptionsNode: ASDisplayNode {
func updatePresentationData(_ presentationData: PresentationData) { func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData self.presentationData = presentationData
self.backgroundNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.opaqueBackgroundColor self.backgroundNode.updateColor(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
self.separatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor self.separatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.presentationData.theme)) self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.presentationData.theme))
} }
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
self.backgroundNode.update(size: size, transition: transition)
transition.updateFrame(node: self.separatorNode, frame: CGRect(x: 0.0, y: size.height, width: size.width, height: UIScreenPixel)) transition.updateFrame(node: self.separatorNode, frame: CGRect(x: 0.0, y: size.height, width: size.width, height: UIScreenPixel))
let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: size.width - 16.0 - leftInset - rightInset), transition: .immediate) let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: size.width - 16.0 - leftInset - rightInset), transition: .immediate)

View File

@ -18,7 +18,7 @@ public enum LocationPickerMode {
} }
class LocationPickerInteraction { class LocationPickerInteraction {
let sendLocation: (CLLocationCoordinate2D, String?) -> Void let sendLocation: (CLLocationCoordinate2D, String?, String?) -> Void
let sendLiveLocation: (CLLocationCoordinate2D) -> Void let sendLiveLocation: (CLLocationCoordinate2D) -> Void
let sendVenue: (TelegramMediaMap, Int64?, String?) -> Void let sendVenue: (TelegramMediaMap, Int64?, String?) -> Void
let toggleMapModeSelection: () -> Void let toggleMapModeSelection: () -> Void
@ -33,7 +33,7 @@ class LocationPickerInteraction {
let openHomeWorkInfo: () -> Void let openHomeWorkInfo: () -> Void
let showPlacesInThisArea: () -> Void let showPlacesInThisArea: () -> Void
init(sendLocation: @escaping (CLLocationCoordinate2D, String?) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D) -> Void, sendVenue: @escaping (TelegramMediaMap, Int64?, String?) -> Void, toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, openSearch: @escaping () -> Void, updateSearchQuery: @escaping (String) -> Void, dismissSearch: @escaping () -> Void, dismissInput: @escaping () -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, openHomeWorkInfo: @escaping () -> Void, showPlacesInThisArea: @escaping ()-> Void) { init(sendLocation: @escaping (CLLocationCoordinate2D, String?, String?) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D) -> Void, sendVenue: @escaping (TelegramMediaMap, Int64?, String?) -> Void, toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, openSearch: @escaping () -> Void, updateSearchQuery: @escaping (String) -> Void, dismissSearch: @escaping () -> Void, dismissInput: @escaping () -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, openHomeWorkInfo: @escaping () -> Void, showPlacesInThisArea: @escaping ()-> Void) {
self.sendLocation = sendLocation self.sendLocation = sendLocation
self.sendLiveLocation = sendLiveLocation self.sendLiveLocation = sendLiveLocation
self.sendVenue = sendVenue self.sendVenue = sendVenue
@ -65,7 +65,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab
private let mode: LocationPickerMode private let mode: LocationPickerMode
private let source: Source private let source: Source
let initialLocation: CLLocationCoordinate2D? let initialLocation: CLLocationCoordinate2D?
private let completion: (TelegramMediaMap, Int64?, String?, String?) -> Void private let completion: (TelegramMediaMap, Int64?, String?, String?, String?) -> Void
private var presentationData: PresentationData private var presentationData: PresentationData
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
@ -85,7 +85,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab
public var isContainerPanning: () -> Bool = { return false } public var isContainerPanning: () -> Bool = { return false }
public var isContainerExpanded: () -> Bool = { return false } public var isContainerExpanded: () -> Bool = { return false }
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: LocationPickerMode, source: Source = .generic, initialLocation: CLLocationCoordinate2D? = nil, completion: @escaping (TelegramMediaMap, Int64?, String?, String?) -> Void) { public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: LocationPickerMode, source: Source = .generic, initialLocation: CLLocationCoordinate2D? = nil, completion: @escaping (TelegramMediaMap, Int64?, String?, String?, String?) -> Void) {
self.context = context self.context = context
self.mode = mode self.mode = mode
self.source = source self.source = source
@ -123,11 +123,11 @@ public final class LocationPickerController: ViewController, AttachmentContainab
return TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: timeout, liveProximityNotificationRadius: nil) return TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: timeout, liveProximityNotificationRadius: nil)
} }
self.interaction = LocationPickerInteraction(sendLocation: { [weak self] coordinate, name in self.interaction = LocationPickerInteraction(sendLocation: { [weak self] coordinate, name, countryCode in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.completion(locationWithTimeout(coordinate, nil), nil, nil, name) strongSelf.completion(locationWithTimeout(coordinate, nil), nil, nil, name, countryCode)
strongSelf.dismiss() strongSelf.dismiss()
}, sendLiveLocation: { [weak self] coordinate in }, sendLiveLocation: { [weak self] coordinate in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -152,21 +152,21 @@ public final class LocationPickerController: ViewController, AttachmentContainab
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor15Minutes, color: .accent, action: { [weak self, weak controller] in ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor15Minutes, color: .accent, action: { [weak self, weak controller] in
controller?.dismissAnimated() controller?.dismissAnimated()
if let strongSelf = self { if let strongSelf = self {
strongSelf.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 15 * 60), nil, nil, nil) strongSelf.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 15 * 60), nil, nil, nil, nil)
strongSelf.dismiss() strongSelf.dismiss()
} }
}), }),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor1Hour, color: .accent, action: { [weak self, weak controller] in ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor1Hour, color: .accent, action: { [weak self, weak controller] in
controller?.dismissAnimated() controller?.dismissAnimated()
if let strongSelf = self { if let strongSelf = self {
strongSelf.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 60 * 60 - 1), nil, nil, nil) strongSelf.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 60 * 60 - 1), nil, nil, nil, nil)
strongSelf.dismiss() strongSelf.dismiss()
} }
}), }),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor8Hours, color: .accent, action: { [weak self, weak controller] in ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor8Hours, color: .accent, action: { [weak self, weak controller] in
controller?.dismissAnimated() controller?.dismissAnimated()
if let strongSelf = self { if let strongSelf = self {
strongSelf.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 8 * 60 * 60), nil, nil, nil) strongSelf.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 8 * 60 * 60), nil, nil, nil, nil)
strongSelf.dismiss() strongSelf.dismiss()
} }
}) })
@ -185,9 +185,9 @@ public final class LocationPickerController: ViewController, AttachmentContainab
} }
let venueType = venue.venue?.type ?? "" let venueType = venue.venue?.type ?? ""
if ["home", "work"].contains(venueType) { if ["home", "work"].contains(venueType) {
completion(TelegramMediaMap(latitude: venue.latitude, longitude: venue.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil), nil, nil, nil) completion(TelegramMediaMap(latitude: venue.latitude, longitude: venue.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil), nil, nil, nil, nil)
} else { } else {
completion(venue, queryId, resultId, nil) completion(venue, queryId, resultId, nil, nil)
} }
strongSelf.dismiss() strongSelf.dismiss()
}, toggleMapModeSelection: { [weak self] in }, toggleMapModeSelection: { [weak self] in
@ -412,7 +412,7 @@ private final class LocationPickerContext: AttachmentMediaPickerContext {
public func storyLocationPickerController( public func storyLocationPickerController(
context: AccountContext, context: AccountContext,
location: CLLocationCoordinate2D?, location: CLLocationCoordinate2D?,
completion: @escaping (TelegramMediaMap, Int64?, String?, String?) -> Void completion: @escaping (TelegramMediaMap, Int64?, String?, String?, String?) -> Void
) -> ViewController { ) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme) let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData)) let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
@ -420,8 +420,8 @@ public func storyLocationPickerController(
return nil return nil
}) })
controller.requestController = { _, present in controller.requestController = { _, present in
let locationPickerController = LocationPickerController(context: context, updatedPresentationData: updatedPresentationData, mode: .share(peer: nil, selfPeer: nil, hasLiveLocation: false), source: .story, initialLocation: location, completion: { location, queryId, resultId, address in let locationPickerController = LocationPickerController(context: context, updatedPresentationData: updatedPresentationData, mode: .share(peer: nil, selfPeer: nil, hasLiveLocation: false), source: .story, initialLocation: location, completion: { location, queryId, resultId, address, countryCode in
completion(location, queryId, resultId, address) completion(location, queryId, resultId, address, countryCode)
}) })
present(locationPickerController, locationPickerController.mediaPickerContext) present(locationPickerController, locationPickerController.mediaPickerContext)
} }

View File

@ -36,8 +36,8 @@ private enum LocationPickerEntryId: Hashable {
} }
private enum LocationPickerEntry: Comparable, Identifiable { private enum LocationPickerEntry: Comparable, Identifiable {
case city(PresentationTheme, String, String, TelegramMediaMap?, Int64?, String?, CLLocationCoordinate2D?, String?) case city(PresentationTheme, String, String, TelegramMediaMap?, Int64?, String?, CLLocationCoordinate2D?, String?, String?)
case location(PresentationTheme, String, String, TelegramMediaMap?, Int64?, String?, CLLocationCoordinate2D?, String?, Bool) case location(PresentationTheme, String, String, TelegramMediaMap?, Int64?, String?, CLLocationCoordinate2D?, String?, String?, Bool)
case liveLocation(PresentationTheme, String, String, CLLocationCoordinate2D?) case liveLocation(PresentationTheme, String, String, CLLocationCoordinate2D?)
case header(PresentationTheme, String) case header(PresentationTheme, String)
case venue(PresentationTheme, TelegramMediaMap?, Int64?, String?, Int) case venue(PresentationTheme, TelegramMediaMap?, Int64?, String?, Int)
@ -62,14 +62,14 @@ private enum LocationPickerEntry: Comparable, Identifiable {
static func ==(lhs: LocationPickerEntry, rhs: LocationPickerEntry) -> Bool { static func ==(lhs: LocationPickerEntry, rhs: LocationPickerEntry) -> Bool {
switch lhs { switch lhs {
case let .city(lhsTheme, lhsTitle, lhsSubtitle, lhsVenue, lhsQueryId, lhsResultId, lhsCoordinate, lhsName): case let .city(lhsTheme, lhsTitle, lhsSubtitle, lhsVenue, lhsQueryId, lhsResultId, lhsCoordinate, lhsName, lhsCountryCode):
if case let .city(rhsTheme, rhsTitle, rhsSubtitle, rhsVenue, rhsQueryId, rhsResultId, rhsCoordinate, rhsName) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsVenue?.venue?.id == rhsVenue?.venue?.id, lhsQueryId == rhsQueryId && lhsResultId == rhsResultId, lhsCoordinate == rhsCoordinate, lhsName == rhsName { if case let .city(rhsTheme, rhsTitle, rhsSubtitle, rhsVenue, rhsQueryId, rhsResultId, rhsCoordinate, rhsName, rhsCountryCode) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsVenue?.venue?.id == rhsVenue?.venue?.id, lhsQueryId == rhsQueryId && lhsResultId == rhsResultId, lhsCoordinate == rhsCoordinate, lhsName == rhsName, lhsCountryCode == rhsCountryCode {
return true return true
} else { } else {
return false return false
} }
case let .location(lhsTheme, lhsTitle, lhsSubtitle, lhsVenue, lhsQueryId, lhsResultId, lhsCoordinate, lhsName, lhsIsTop): case let .location(lhsTheme, lhsTitle, lhsSubtitle, lhsVenue, lhsQueryId, lhsResultId, lhsCoordinate, lhsName, lhsCountryCode, lhsIsTop):
if case let .location(rhsTheme, rhsTitle, rhsSubtitle, rhsVenue, rhsQueryId, rhsResultId, rhsCoordinate, rhsName, rhsIsTop) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsVenue?.venue?.id == rhsVenue?.venue?.id, lhsQueryId == rhsQueryId && lhsResultId == rhsResultId, lhsCoordinate == rhsCoordinate, lhsName == rhsName, lhsIsTop == rhsIsTop { if case let .location(rhsTheme, rhsTitle, rhsSubtitle, rhsVenue, rhsQueryId, rhsResultId, rhsCoordinate, rhsName, rhsCountryCode, rhsIsTop) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsVenue?.venue?.id == rhsVenue?.venue?.id, lhsQueryId == rhsQueryId && lhsResultId == rhsResultId, lhsCoordinate == rhsCoordinate, lhsName == rhsName, lhsCountryCode == rhsCountryCode, lhsIsTop == rhsIsTop {
return true return true
} else { } else {
return false return false
@ -147,7 +147,7 @@ private enum LocationPickerEntry: Comparable, Identifiable {
func item(engine: TelegramEngine, presentationData: PresentationData, interaction: LocationPickerInteraction?) -> ListViewItem { func item(engine: TelegramEngine, presentationData: PresentationData, interaction: LocationPickerInteraction?) -> ListViewItem {
switch self { switch self {
case let .city(_, title, subtitle, _, _, _, coordinate, name): case let .city(_, title, subtitle, _, _, _, coordinate, name, countryCode):
let icon: LocationActionListItemIcon let icon: LocationActionListItemIcon
if let name { if let name {
icon = .venue(TelegramMediaMap(latitude: 0, longitude: 0, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: MapVenue(title: name, address: "City", provider: nil, id: "city", type: "building/default"), liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil)) icon = .venue(TelegramMediaMap(latitude: 0, longitude: 0, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: MapVenue(title: name, address: "City", provider: nil, id: "city", type: "building/default"), liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil))
@ -156,12 +156,12 @@ private enum LocationPickerEntry: Comparable, Identifiable {
} }
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: engine, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: nil, action: { return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: engine, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: nil, action: {
if let coordinate = coordinate { if let coordinate = coordinate {
interaction?.sendLocation(coordinate, name) interaction?.sendLocation(coordinate, name, countryCode)
} }
}, highlighted: { highlighted in }, highlighted: { highlighted in
interaction?.updateSendActionHighlight(highlighted) interaction?.updateSendActionHighlight(highlighted)
}) })
case let .location(_, title, subtitle, venue, queryId, resultId, coordinate, name, isTop): case let .location(_, title, subtitle, venue, queryId, resultId, coordinate, name, countryCode, isTop):
let icon: LocationActionListItemIcon let icon: LocationActionListItemIcon
if let venue = venue { if let venue = venue {
icon = .venue(venue) icon = .venue(venue)
@ -172,7 +172,7 @@ private enum LocationPickerEntry: Comparable, Identifiable {
if let venue = venue { if let venue = venue {
interaction?.sendVenue(venue, queryId, resultId) interaction?.sendVenue(venue, queryId, resultId)
} else if let coordinate = coordinate { } else if let coordinate = coordinate {
interaction?.sendLocation(coordinate, name) interaction?.sendLocation(coordinate, name, countryCode)
} }
}, highlighted: { highlighted in }, highlighted: { highlighted in
if isTop { if isTop {
@ -262,6 +262,8 @@ struct LocationPickerState {
var selectedLocation: LocationPickerLocation var selectedLocation: LocationPickerLocation
var city: String? var city: String?
var street: String? var street: String?
var countryCode: String?
var isStreet: Bool
var forceSelection: Bool var forceSelection: Bool
var searchingVenuesAround: Bool var searchingVenuesAround: Bool
@ -271,6 +273,7 @@ struct LocationPickerState {
self.selectedLocation = .none self.selectedLocation = .none
self.city = nil self.city = nil
self.street = nil self.street = nil
self.isStreet = false
self.forceSelection = false self.forceSelection = false
self.searchingVenuesAround = false self.searchingVenuesAround = false
} }
@ -587,7 +590,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
case .pick: case .pick:
title = presentationData.strings.Map_SetThisLocation title = presentationData.strings.Map_SetThisLocation
} }
entries.append(.location(presentationData.theme, title, address ?? presentationData.strings.Map_Locating, nil, nil, nil, coordinate, state.street, true)) entries.append(.location(presentationData.theme, title, address ?? presentationData.strings.Map_Locating, nil, nil, nil, coordinate, state.street, state.countryCode, true))
case .selecting: case .selecting:
let title: String let title: String
switch strongSelf.mode { switch strongSelf.mode {
@ -600,7 +603,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
case .pick: case .pick:
title = presentationData.strings.Map_SetThisLocation title = presentationData.strings.Map_SetThisLocation
} }
entries.append(.location(presentationData.theme, title, presentationData.strings.Map_Locating, nil, nil, nil, nil, nil, true)) entries.append(.location(presentationData.theme, title, presentationData.strings.Map_Locating, nil, nil, nil, nil, nil, nil, true))
case let .venue(venue, queryId, resultId): case let .venue(venue, queryId, resultId):
let title: String let title: String
switch strongSelf.mode { switch strongSelf.mode {
@ -609,7 +612,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
case .pick: case .pick:
title = presentationData.strings.Map_SetThisPlace title = presentationData.strings.Map_SetThisPlace
} }
entries.append(.location(presentationData.theme, title, venue.venue?.title ?? "", venue, queryId, resultId, venue.coordinate, nil, true)) entries.append(.location(presentationData.theme, title, venue.venue?.title ?? "", venue, queryId, resultId, venue.coordinate, nil, nil, true))
case .none: case .none:
let title: String let title: String
var coordinate = userLocation?.coordinate var coordinate = userLocation?.coordinate
@ -630,13 +633,13 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
} }
if source == .story { if source == .story {
if state.city != "" { if state.city != "" {
entries.append(.city(presentationData.theme, state.city ?? presentationData.strings.Map_Locating, "City", nil, nil, nil, coordinate, state.city)) entries.append(.city(presentationData.theme, state.city ?? presentationData.strings.Map_Locating, "City", nil, nil, nil, coordinate, state.city, state.countryCode))
} }
if state.street != "" { if state.street != "" {
entries.append(.location(presentationData.theme, state.street ?? presentationData.strings.Map_Locating, "Street", nil, nil, nil, coordinate, state.street, false)) entries.append(.location(presentationData.theme, state.street ?? presentationData.strings.Map_Locating, state.isStreet ? "Street" : "Location", nil, nil, nil, coordinate, state.street, nil, false))
} }
} else { } else {
entries.append(.location(presentationData.theme, title, (userLocation?.horizontalAccuracy).flatMap { presentationData.strings.Map_AccurateTo(stringForDistance(strings: presentationData.strings, distance: $0)).string } ?? presentationData.strings.Map_Locating, nil, nil, nil, coordinate, state.street, true)) entries.append(.location(presentationData.theme, title, (userLocation?.horizontalAccuracy).flatMap { presentationData.strings.Map_AccurateTo(stringForDistance(strings: presentationData.strings, distance: $0)).string } ?? presentationData.strings.Map_Locating, nil, nil, nil, coordinate, state.street, nil, true))
} }
} }
@ -775,8 +778,9 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
} }
} }
let locale = localeWithStrings(presentationData.strings)
if case let .location(coordinate, address) = state.selectedLocation, address == nil { if case let .location(coordinate, address) = state.selectedLocation, address == nil {
strongSelf.geocodingDisposable.set((reverseGeocodeLocation(latitude: coordinate.latitude, longitude: coordinate.longitude) strongSelf.geocodingDisposable.set((reverseGeocodeLocation(latitude: coordinate.latitude, longitude: coordinate.longitude, locale: locale)
|> deliverOnMainQueue).start(next: { [weak self] placemark in |> deliverOnMainQueue).start(next: { [weak self] placemark in
if let strongSelf = self { if let strongSelf = self {
var address = placemark?.fullAddress ?? "" var address = placemark?.fullAddress ?? ""
@ -785,8 +789,9 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
} }
var cityName: String? var cityName: String?
var streetName: String? var streetName: String?
if let city = placemark?.city, let country = placemark?.country { let countryCode = placemark?.countryCode
cityName = "\(city), \(country)" if let city = placemark?.city, let countryCode = placemark?.countryCode {
cityName = "\(city), \(displayCountryName(countryCode, locale: locale))"
} else { } else {
cityName = "" cityName = ""
} }
@ -796,6 +801,10 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
} else { } else {
streetName = street streetName = street
} }
} else if let name = placemark?.name {
streetName = name
} else if let country = placemark?.country, cityName == "" {
streetName = country
} else { } else {
streetName = "" streetName = ""
} }
@ -807,6 +816,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
state.selectedLocation = .location(coordinate, address) state.selectedLocation = .location(coordinate, address)
state.city = cityName state.city = cityName
state.street = streetName state.street = streetName
state.countryCode = countryCode
state.isStreet = placemark?.street != nil
return state return state
} }
} }
@ -814,7 +825,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
} else { } else {
let coordinate = controller.initialLocation ?? userLocation?.coordinate let coordinate = controller.initialLocation ?? userLocation?.coordinate
if case .none = state.selectedLocation, let coordinate, state.city == nil { if case .none = state.selectedLocation, let coordinate, state.city == nil {
strongSelf.geocodingDisposable.set((reverseGeocodeLocation(latitude: coordinate.latitude, longitude: coordinate.longitude) strongSelf.geocodingDisposable.set((reverseGeocodeLocation(latitude: coordinate.latitude, longitude: coordinate.longitude, locale: locale)
|> deliverOnMainQueue).start(next: { [weak self] placemark in |> deliverOnMainQueue).start(next: { [weak self] placemark in
if let strongSelf = self { if let strongSelf = self {
var address = placemark?.fullAddress ?? "" var address = placemark?.fullAddress ?? ""
@ -823,8 +834,9 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
} }
var cityName: String? var cityName: String?
var streetName: String? var streetName: String?
if let city = placemark?.city, let country = placemark?.country { let countryCode = placemark?.countryCode
cityName = "\(city), \(country)" if let city = placemark?.city, let countryCode = placemark?.countryCode {
cityName = "\(city), \(displayCountryName(countryCode, locale: locale))"
} else { } else {
cityName = "" cityName = ""
} }
@ -834,6 +846,10 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
} else { } else {
streetName = street streetName = street
} }
} else if let name = placemark?.name {
streetName = name
} else if let country = placemark?.country, cityName == "" {
streetName = country
} else { } else {
streetName = "" streetName = ""
} }
@ -844,6 +860,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
var state = state var state = state
state.city = cityName state.city = cityName
state.street = streetName state.street = streetName
state.countryCode = countryCode
state.isStreet = placemark?.street != nil
return state return state
} }
} }

View File

@ -173,12 +173,13 @@ final class LocationSearchContainerNode: ASDisplayNode {
} }
} }
|> mapToSignal { query -> Signal<([LocationSearchEntry], String)?, NoError> in |> mapToSignal { query -> Signal<([LocationSearchEntry], String)?, NoError> in
if let query = query, !query.isEmpty { if let query, !query.isEmpty {
let foundVenues = nearbyVenues(context: context, story: story, latitude: coordinate.latitude, longitude: coordinate.longitude, query: query) let foundVenues = nearbyVenues(context: context, story: story, latitude: coordinate.latitude, longitude: coordinate.longitude, query: query)
|> afterCompleted { |> afterCompleted {
isSearching.set(false) isSearching.set(false)
} }
let foundPlacemarks = geocodeLocation(address: query) let locale = localeWithStrings(presentationData.strings)
let foundPlacemarks = geocodeLocation(address: query, locale: locale)
return combineLatest(foundVenues, foundPlacemarks, themeAndStringsPromise.get()) return combineLatest(foundVenues, foundPlacemarks, themeAndStringsPromise.get())
|> delay(0.1, queue: Queue.concurrentDefaultQueue()) |> delay(0.1, queue: Queue.concurrentDefaultQueue())
|> beforeStarted { |> beforeStarted {
@ -219,7 +220,7 @@ final class LocationSearchContainerNode: ASDisplayNode {
} else { } else {
return .single(nil) return .single(nil)
|> afterCompleted { |> afterCompleted {
isSearching.set(true) isSearching.set(false)
} }
} }
} }

View File

@ -77,6 +77,7 @@ public final class LocationViewController: ViewController {
private var presentationData: PresentationData private var presentationData: PresentationData
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
private var showAll: Bool private var showAll: Bool
private let disableDismissGesture: Bool
private let locationManager = LocationManager() private let locationManager = LocationManager()
private var permissionDisposable: Disposable? private var permissionDisposable: Disposable?
@ -87,10 +88,11 @@ public final class LocationViewController: ViewController {
public var dismissed: () -> Void = {} public var dismissed: () -> Void = {}
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, subject: EngineMessage, params: LocationViewParams) { public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, subject: EngineMessage, disableDismissGesture: Bool = false, params: LocationViewParams) {
self.context = context self.context = context
self.subject = subject self.subject = subject
self.showAll = params.showAll self.showAll = params.showAll
self.disableDismissGesture = disableDismissGesture
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
@ -498,6 +500,8 @@ public final class LocationViewController: ViewController {
} }
strongSelf.controllerNode.showAll() strongSelf.controllerNode.showAll()
} }
self.controllerNode.headerNode.mapNode.disableHorizontalTransitionGesture = self.disableDismissGesture
} }
private func updateRightBarButton() { private func updateRightBarButton() {

View File

@ -208,7 +208,7 @@ public func archiveSettingsController(context: AccountContext) -> ViewController
guard let controller else { guard let controller else {
return return
} }
let premiumController = context.sharedContext.makePremiumIntroController(context: context, source: .settings) let premiumController = context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false)
controller.push(premiumController) controller.push(premiumController)
} }

View File

@ -103,7 +103,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
self.containerNode = ASDisplayNode() self.containerNode = ASDisplayNode()
self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
self.tooltipContainerNode = ContextMenuContainerNode() self.tooltipContainerNode = ContextMenuContainerNode(blurred: false)
self.tooltipContainerNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8) self.tooltipContainerNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
self.textNode = ImmediateTextNode() self.textNode = ImmediateTextNode()

View File

@ -194,6 +194,11 @@ final class PendingStoryManager {
var storyObserverContexts: [Int32: Bag<(Float) -> Void>] = [:] var storyObserverContexts: [Int32: Bag<(Float) -> Void>] = [:]
private let allStoriesEventsPipe = ValuePipe<(Int32, Int32)>()
var allStoriesUploadEvents: Signal<(Int32, Int32), NoError> {
return self.allStoriesEventsPipe.signal()
}
private let allStoriesUploadProgressPromise = Promise<Float?>(nil) private let allStoriesUploadProgressPromise = Promise<Float?>(nil)
private var allStoriesUploadProgressValue: Float? = nil private var allStoriesUploadProgressValue: Float? = nil
var allStoriesUploadProgress: Signal<Float?, NoError> { var allStoriesUploadProgress: Signal<Float?, NoError> {
@ -272,6 +277,10 @@ final class PendingStoryManager {
private func update(localState: Stories.LocalState) { private func update(localState: Stories.LocalState) {
if let currentPendingItemContext = self.currentPendingItemContext, !localState.items.contains(where: { $0.randomId == currentPendingItemContext.item.randomId }) { if let currentPendingItemContext = self.currentPendingItemContext, !localState.items.contains(where: { $0.randomId == currentPendingItemContext.item.randomId }) {
self.currentPendingItemContext = nil self.currentPendingItemContext = nil
self.queue.after(0.1, {
let _ = currentPendingItemContext
print(currentPendingItemContext)
})
} }
if self.currentPendingItemContext == nil, let firstItem = localState.items.first { if self.currentPendingItemContext == nil, let firstItem = localState.items.first {
@ -304,7 +313,10 @@ final class PendingStoryManager {
currentPendingItemContext.progress = progress currentPendingItemContext.progress = progress
currentPendingItemContext.updated() currentPendingItemContext.updated()
} }
case .completed: case let .completed(id):
if let id {
self.allStoriesEventsPipe.putNext((stableId, id))
}
// wait for the local state to change via Postbox // wait for the local state to change via Postbox
break break
} }
@ -363,6 +375,12 @@ final class PendingStoryManager {
return impl.storyUploadProgress(stableId: stableId, next: subscriber.putNext) return impl.storyUploadProgress(stableId: stableId, next: subscriber.putNext)
} }
} }
public func allStoriesUploadEvents() -> Signal<(Int32, Int32), NoError> {
return self.impl.signalWith { impl, subscriber in
return impl.allStoriesUploadEvents.start(next: subscriber.putNext)
}
}
init(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods) { init(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods) {
let queue = Queue.mainQueue() let queue = Queue.mainQueue()

View File

@ -796,10 +796,10 @@ private func apiInputPrivacyRules(privacy: EngineStoryPrivacy, transaction: Tran
return privacyRules return privacyRules
} }
func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) { func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) -> Signal<Int32, NoError> {
let inputMedia = prepareUploadStoryContent(account: account, media: media) let inputMedia = prepareUploadStoryContent(account: account, media: media)
let _ = (account.postbox.transaction { transaction in return (account.postbox.transaction { transaction in
var currentState: Stories.LocalState var currentState: Stories.LocalState
if let value = transaction.getLocalStoryState()?.get(Stories.LocalState.self) { if let value = transaction.getLocalStoryState()?.get(Stories.LocalState.self) {
currentState = value currentState = value
@ -825,7 +825,8 @@ func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, media
randomId: randomId randomId: randomId
)) ))
transaction.setLocalStoryState(state: CodableEntry(currentState)) transaction.setLocalStoryState(state: CodableEntry(currentState))
}).start() return stableId
})
} }
func _internal_cancelStoryUpload(account: Account, stableId: Int32) { func _internal_cancelStoryUpload(account: Account, stableId: Int32) {

View File

@ -1049,8 +1049,15 @@ public extension TelegramEngine {
} }
} }
public func uploadStory(media: EngineStoryInputMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) { public func uploadStory(media: EngineStoryInputMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) -> Signal<Int32, NoError> {
_internal_uploadStory(account: self.account, media: media, mediaAreas: mediaAreas, text: text, entities: entities, pin: pin, privacy: privacy, isForwardingDisabled: isForwardingDisabled, period: period, randomId: randomId) return _internal_uploadStory(account: self.account, media: media, mediaAreas: mediaAreas, text: text, entities: entities, pin: pin, privacy: privacy, isForwardingDisabled: isForwardingDisabled, period: period, randomId: randomId)
}
public func allStoriesUploadEvents() -> Signal<(Int32, Int32), NoError> {
guard let pendingStoryManager = self.account.pendingStoryManager else {
return .complete()
}
return pendingStoryManager.allStoriesUploadEvents()
} }
public func lookUpPendingStoryIdMapping(stableId: Int32) -> Int32? { public func lookUpPendingStoryIdMapping(stableId: Int32) -> Int32? {

View File

@ -72,7 +72,12 @@ public final class BlockedPeersContext {
flags |= 1 << 0 flags |= 1 << 0
} }
self.disposable.set((self.account.network.request(Api.functions.contacts.getBlocked(flags: flags, offset: Int32(self._state.peers.count), limit: 64)) var limit: Int32 = 200
if self._state.peers.count > 0 {
limit = 100
}
self.disposable.set((self.account.network.request(Api.functions.contacts.getBlocked(flags: flags, offset: Int32(self._state.peers.count), limit: limit))
|> retryRequest |> retryRequest
|> mapToSignal { result -> Signal<(peers: [RenderedPeer], canLoadMore: Bool, totalCount: Int?), NoError> in |> mapToSignal { result -> Signal<(peers: [RenderedPeer], canLoadMore: Bool, totalCount: Int?), NoError> in
return postbox.transaction { transaction -> (peers: [RenderedPeer], canLoadMore: Bool, totalCount: Int?) in return postbox.transaction { transaction -> (peers: [RenderedPeer], canLoadMore: Bool, totalCount: Int?) in
@ -140,23 +145,74 @@ public final class BlockedPeersContext {
public func updatePeerIds(_ peerIds: [EnginePeer.Id]) -> Signal<Never, BlockedPeersContextAddError> { public func updatePeerIds(_ peerIds: [EnginePeer.Id]) -> Signal<Never, BlockedPeersContextAddError> {
assert(Queue.mainQueue().isCurrent()) assert(Queue.mainQueue().isCurrent())
let validIds = Set(peerIds) let network = self.account.network
var peersToRemove: [EnginePeer.Id] = [] let subject = self.subject
for peer in self._state.peers { let currentPeers = self._state.peers
if !validIds.contains(peer.peerId) {
peersToRemove.append(peer.peerId) var flags: Int32 = 0
if case .stories = self.subject {
flags |= 1 << 0
}
return self.account.postbox.transaction { transaction -> [Peer] in
var peers: [Peer] = []
var removedPeerIds = Set<EnginePeer.Id>()
var validPeerIds = Set<EnginePeer.Id>()
var allPeerIds = Set<EnginePeer.Id>()
for peerId in peerIds {
if let peer = transaction.getPeer(peerId) {
peers.append(peer)
}
validPeerIds.insert(peerId)
allPeerIds.insert(peerId)
} }
for peer in currentPeers {
if !validPeerIds.contains(peer.peerId) {
removedPeerIds.insert(peer.peerId)
allPeerIds.insert(peer.peerId)
}
}
transaction.updatePeerCachedData(peerIds: allPeerIds, update: { peerId, current in
let previous: CachedUserData
if let current = current as? CachedUserData {
previous = current
} else {
previous = CachedUserData()
}
if case .stories = subject {
var userFlags = previous.flags
if validPeerIds.contains(peerId) {
userFlags.insert(.isBlockedFromStories)
} else if removedPeerIds.contains(peerId) {
userFlags.remove(.isBlockedFromStories)
}
return previous.withUpdatedFlags(userFlags)
} else {
if validPeerIds.contains(peerId) {
return previous.withUpdatedIsBlocked(true)
} else if removedPeerIds.contains(peerId) {
return previous.withUpdatedIsBlocked(false)
} else {
return previous
}
}
})
return peers
} }
var updateSignals: [Signal<Never, BlockedPeersContextAddError>] = [] |> castError(BlockedPeersContextAddError.self)
for peerId in peersToRemove { |> mapToSignal { peers -> Signal<Never, BlockedPeersContextAddError> in
updateSignals.append(self.remove(peerId: peerId) |> mapError { _ in .generic }) let inputPeers = peers.compactMap { apiInputPeer($0) }
} return network.request(Api.functions.contacts.setBlocked(flags: flags, id: inputPeers, limit: Int32(peers.count)))
for peerId in peerIds { |> mapError { _ -> BlockedPeersContextAddError in
updateSignals.append(self.add(peerId: peerId)) return .generic
} }
return combineLatest(updateSignals) |> mapToSignal { _ -> Signal<Never, BlockedPeersContextAddError> in
|> mapToSignal { _ in return .complete()
return .never() }
} }
} }
@ -209,7 +265,6 @@ public final class BlockedPeersContext {
|> castError(BlockedPeersContextAddError.self) |> castError(BlockedPeersContextAddError.self)
} }
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { peer -> Signal<Never, BlockedPeersContextAddError> in |> mapToSignal { peer -> Signal<Never, BlockedPeersContextAddError> in
guard let strongSelf = self, let peer = peer else { guard let strongSelf = self, let peer = peer else {
return .complete() return .complete()

View File

@ -1251,6 +1251,7 @@ final class AvatarEditorScreenComponent: Component {
externalBottomPanelContainer: nil, externalBottomPanelContainer: nil,
displayTopPanelBackground: .blur, displayTopPanelBackground: .blur,
topPanelExtensionUpdated: { _, _ in }, topPanelExtensionUpdated: { _, _ in },
topPanelScrollingOffset: { _, _ in },
hideInputUpdated: { _, _, _ in }, hideInputUpdated: { _, _, _ in },
hideTopPanelUpdated: { [weak self] hideTopPanel, transition in hideTopPanelUpdated: { [weak self] hideTopPanel, transition in
if let strongSelf = self { if let strongSelf = self {

View File

@ -1942,6 +1942,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
strongSelf.topBackgroundExtensionUpdated?(transition.containedViewLayoutTransition) strongSelf.topBackgroundExtensionUpdated?(transition.containedViewLayoutTransition)
} }
}, },
topPanelScrollingOffset: { _, _ in },
hideInputUpdated: { [weak self] hideInput, adjustLayout, transition in hideInputUpdated: { [weak self] hideInput, adjustLayout, transition in
guard let strongSelf = self else { guard let strongSelf = self else {
return return

View File

@ -174,6 +174,7 @@ public final class EmojiStatusSelectionComponent: Component {
externalBottomPanelContainer: nil, externalBottomPanelContainer: nil,
displayTopPanelBackground: .blur, displayTopPanelBackground: .blur,
topPanelExtensionUpdated: { _, _ in }, topPanelExtensionUpdated: { _, _ in },
topPanelScrollingOffset: { _, _ in },
hideInputUpdated: { _, _, _ in }, hideInputUpdated: { _, _, _ in },
hideTopPanelUpdated: { [weak self] hideTopPanel, transition in hideTopPanelUpdated: { [weak self] hideTopPanel, transition in
guard let strongSelf = self else { guard let strongSelf = self else {

View File

@ -6509,8 +6509,44 @@ public final class EmojiPagerContentComponent: Component {
calculateUpdatedItemPositions = true calculateUpdatedItemPositions = true
} }
var itemTransition = transition
if let previousItemLayout = self.itemLayout {
if previousItemLayout.width != availableSize.width {
itemTransition = .immediate
} else if transition.userData(ContentAnimation.self) == nil {
if previousItemLayout.itemInsets.top != pagerEnvironment.containerInsets.top + 9.0 {
} else {
itemTransition = .immediate
}
}
} else {
itemTransition = .immediate
}
var isFirstUpdate = false
var resetScrolling = false
if self.scrollView.bounds.isEmpty && component.displaySearchWithPlaceholder != nil {
resetScrolling = true
}
if previousComponent == nil {
isFirstUpdate = true
}
if previousComponent?.itemContentUniqueId != component.itemContentUniqueId {
resetScrolling = true
}
if resetScrolling {
itemTransition = .immediate
}
var animateContentCrossfade = false
if let previousComponent, previousComponent.itemContentUniqueId != component.itemContentUniqueId, itemTransition.animation.isImmediate {
if !(previousComponent.contentItemGroups.contains(where: { $0.fillWithLoadingPlaceholders }) && component.contentItemGroups.contains(where: { $0.fillWithLoadingPlaceholders })) && previousComponent.itemContentUniqueId?.id != component.itemContentUniqueId?.id {
animateContentCrossfade = true
}
}
var customContentHeight: CGFloat = 0.0 var customContentHeight: CGFloat = 0.0
if let customContentView = component.inputInteractionHolder.inputInteraction?.customContentView { if let customContentView = component.inputInteractionHolder.inputInteraction?.customContentView, !self.isSearchActivated {
var customContentViewTransition = transition var customContentViewTransition = transition
if let _ = self.visibleCustomContentView { if let _ = self.visibleCustomContentView {
@ -6519,6 +6555,11 @@ public final class EmojiPagerContentComponent: Component {
self.visibleCustomContentView = customContentView self.visibleCustomContentView = customContentView
self.scrollView.addSubview(customContentView) self.scrollView.addSubview(customContentView)
self.mirrorContentScrollView.addSubview(customContentView.tintContainerView) self.mirrorContentScrollView.addSubview(customContentView.tintContainerView)
if animateContentCrossfade {
customContentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
customContentView.tintContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
} }
let availableCustomContentSize = availableSize let availableCustomContentSize = availableSize
let customContentViewSize = customContentView.update(theme: keyboardChildEnvironment.theme, useOpaqueTheme: useOpaqueTheme, availableSize: availableCustomContentSize, transition: customContentViewTransition) let customContentViewSize = customContentView.update(theme: keyboardChildEnvironment.theme, useOpaqueTheme: useOpaqueTheme, availableSize: availableCustomContentSize, transition: customContentViewTransition)
@ -6528,8 +6569,17 @@ public final class EmojiPagerContentComponent: Component {
} else { } else {
if let visibleCustomContentView = self.visibleCustomContentView { if let visibleCustomContentView = self.visibleCustomContentView {
self.visibleCustomContentView = nil self.visibleCustomContentView = nil
visibleCustomContentView.removeFromSuperview() if animateContentCrossfade {
visibleCustomContentView.tintContainerView.removeFromSuperview() visibleCustomContentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
visibleCustomContentView.removeFromSuperview()
})
visibleCustomContentView.tintContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
visibleCustomContentView.tintContainerView.removeFromSuperview()
})
} else {
visibleCustomContentView.removeFromSuperview()
visibleCustomContentView.tintContainerView.removeFromSuperview()
}
} }
} }
@ -6547,8 +6597,6 @@ public final class EmojiPagerContentComponent: Component {
)) ))
} }
var itemTransition = transition
let extractedExpr = ItemLayout( let extractedExpr = ItemLayout(
layoutType: component.itemLayoutType, layoutType: component.itemLayoutType,
width: availableSize.width, width: availableSize.width,
@ -6562,18 +6610,6 @@ public final class EmojiPagerContentComponent: Component {
customLayout: component.inputInteractionHolder.inputInteraction?.customLayout customLayout: component.inputInteractionHolder.inputInteraction?.customLayout
) )
let itemLayout = extractedExpr let itemLayout = extractedExpr
if let previousItemLayout = self.itemLayout {
if previousItemLayout.width != itemLayout.width {
itemTransition = .immediate
} else if transition.userData(ContentAnimation.self) == nil {
if previousItemLayout.itemInsets.top != itemLayout.itemInsets.top {
} else {
itemTransition = .immediate
}
}
} else {
itemTransition = .immediate
}
self.itemLayout = itemLayout self.itemLayout = itemLayout
self.ignoreScrolling = true self.ignoreScrolling = true
@ -6592,23 +6628,8 @@ public final class EmojiPagerContentComponent: Component {
transition.setBounds(view: self.vibrancyClippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? clippingTopInset : 0.0), size: availableSize)) transition.setBounds(view: self.vibrancyClippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? clippingTopInset : 0.0), size: availableSize))
let previousSize = self.scrollView.bounds.size let previousSize = self.scrollView.bounds.size
var resetScrolling = false
var isFirstUpdate = false
if self.scrollView.bounds.isEmpty && component.displaySearchWithPlaceholder != nil {
resetScrolling = true
}
if previousComponent == nil {
isFirstUpdate = true
}
if previousComponent?.itemContentUniqueId != component.itemContentUniqueId {
resetScrolling = true
}
self.scrollView.bounds = CGRect(origin: self.scrollView.bounds.origin, size: scrollSize) self.scrollView.bounds = CGRect(origin: self.scrollView.bounds.origin, size: scrollSize)
if resetScrolling {
itemTransition = .immediate
}
let warpHeight: CGFloat = 50.0 let warpHeight: CGFloat = 50.0
var topWarpInset = pagerEnvironment.containerInsets.top var topWarpInset = pagerEnvironment.containerInsets.top
if self.isSearchActivated { if self.isSearchActivated {
@ -6898,14 +6919,7 @@ public final class EmojiPagerContentComponent: Component {
visibleEmptySearchResultsView.tintContainerView.removeFromSuperview() visibleEmptySearchResultsView.tintContainerView.removeFromSuperview()
} }
} }
var animateContentCrossfade = false
if let previousComponent, previousComponent.itemContentUniqueId != component.itemContentUniqueId, itemTransition.animation.isImmediate {
if !(previousComponent.contentItemGroups.contains(where: { $0.fillWithLoadingPlaceholders }) && component.contentItemGroups.contains(where: { $0.fillWithLoadingPlaceholders })) && previousComponent.itemContentUniqueId?.id != component.itemContentUniqueId?.id {
animateContentCrossfade = true
}
}
let crossfadeMinScale: CGFloat = 0.4 let crossfadeMinScale: CGFloat = 0.4
if animateContentCrossfade { if animateContentCrossfade {

View File

@ -501,6 +501,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
externalBottomPanelContainer: nil, externalBottomPanelContainer: nil,
displayTopPanelBackground: .blur, displayTopPanelBackground: .blur,
topPanelExtensionUpdated: { _, _ in }, topPanelExtensionUpdated: { _, _ in },
topPanelScrollingOffset: { _, _ in },
hideInputUpdated: { _, _, _ in }, hideInputUpdated: { _, _, _ in },
hideTopPanelUpdated: { _, _ in hideTopPanelUpdated: { _, _ in
}, },

View File

@ -108,6 +108,7 @@ public final class EntityKeyboardComponent: Component {
public let externalBottomPanelContainer: PagerExternalTopPanelContainer? public let externalBottomPanelContainer: PagerExternalTopPanelContainer?
public let displayTopPanelBackground: DisplayTopPanelBackground public let displayTopPanelBackground: DisplayTopPanelBackground
public let topPanelExtensionUpdated: (CGFloat, Transition) -> Void public let topPanelExtensionUpdated: (CGFloat, Transition) -> Void
public let topPanelScrollingOffset: (CGFloat, Transition) -> Void
public let hideInputUpdated: (Bool, Bool, Transition) -> Void public let hideInputUpdated: (Bool, Bool, Transition) -> Void
public let hideTopPanelUpdated: (Bool, Transition) -> Void public let hideTopPanelUpdated: (Bool, Transition) -> Void
public let switchToTextInput: () -> Void public let switchToTextInput: () -> Void
@ -141,6 +142,7 @@ public final class EntityKeyboardComponent: Component {
externalBottomPanelContainer: PagerExternalTopPanelContainer?, externalBottomPanelContainer: PagerExternalTopPanelContainer?,
displayTopPanelBackground: DisplayTopPanelBackground, displayTopPanelBackground: DisplayTopPanelBackground,
topPanelExtensionUpdated: @escaping (CGFloat, Transition) -> Void, topPanelExtensionUpdated: @escaping (CGFloat, Transition) -> Void,
topPanelScrollingOffset: @escaping (CGFloat, Transition) -> Void,
hideInputUpdated: @escaping (Bool, Bool, Transition) -> Void, hideInputUpdated: @escaping (Bool, Bool, Transition) -> Void,
hideTopPanelUpdated: @escaping (Bool, Transition) -> Void, hideTopPanelUpdated: @escaping (Bool, Transition) -> Void,
switchToTextInput: @escaping () -> Void, switchToTextInput: @escaping () -> Void,
@ -173,6 +175,7 @@ public final class EntityKeyboardComponent: Component {
self.externalBottomPanelContainer = externalBottomPanelContainer self.externalBottomPanelContainer = externalBottomPanelContainer
self.displayTopPanelBackground = displayTopPanelBackground self.displayTopPanelBackground = displayTopPanelBackground
self.topPanelExtensionUpdated = topPanelExtensionUpdated self.topPanelExtensionUpdated = topPanelExtensionUpdated
self.topPanelScrollingOffset = topPanelScrollingOffset
self.hideInputUpdated = hideInputUpdated self.hideInputUpdated = hideInputUpdated
self.hideTopPanelUpdated = hideTopPanelUpdated self.hideTopPanelUpdated = hideTopPanelUpdated
self.switchToTextInput = switchToTextInput self.switchToTextInput = switchToTextInput
@ -272,6 +275,7 @@ public final class EntityKeyboardComponent: Component {
private var topPanelExtension: CGFloat? private var topPanelExtension: CGFloat?
private var isTopPanelExpanded: Bool = false private var isTopPanelExpanded: Bool = false
private var isTopPanelHidden: Bool = false private var isTopPanelHidden: Bool = false
private var topPanelScrollingOffset: CGFloat?
public var centralId: AnyHashable? { public var centralId: AnyHashable? {
if let pagerView = self.pagerView.findTaggedView(tag: PagerComponentViewTag()) as? PagerComponent<EntityKeyboardChildEnvironment, EntityKeyboardTopContainerPanelEnvironment>.View { if let pagerView = self.pagerView.findTaggedView(tag: PagerComponentViewTag()) as? PagerComponent<EntityKeyboardChildEnvironment, EntityKeyboardTopContainerPanelEnvironment>.View {
@ -776,6 +780,7 @@ public final class EntityKeyboardComponent: Component {
return return
} }
strongSelf.topPanelExtensionUpdated(height: panelState.topPanelHeight, transition: transition) strongSelf.topPanelExtensionUpdated(height: panelState.topPanelHeight, transition: transition)
strongSelf.topPanelScrollingOffset(offset: panelState.scrollingPanelOffsetToTopEdge, transition: transition)
}, },
isTopPanelExpandedUpdated: { [weak self] isExpanded, transition in isTopPanelExpandedUpdated: { [weak self] isExpanded, transition in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -796,7 +801,8 @@ public final class EntityKeyboardComponent: Component {
component.contentIdUpdated(id) component.contentIdUpdated(id)
}, },
panelHideBehavior: panelHideBehavior, panelHideBehavior: panelHideBehavior,
clipContentToTopPanel: component.clipContentToTopPanel clipContentToTopPanel: component.clipContentToTopPanel,
isExpanded: component.isExpanded
)), )),
environment: { environment: {
EntityKeyboardChildEnvironment( EntityKeyboardChildEnvironment(
@ -889,6 +895,18 @@ public final class EntityKeyboardComponent: Component {
} }
} }
private func topPanelScrollingOffset(offset: CGFloat, transition: Transition) {
guard let component = self.component else {
return
}
if self.topPanelScrollingOffset != offset {
self.topPanelScrollingOffset = offset
}
if self.searchComponent == nil {
component.topPanelScrollingOffset(offset, transition)
}
}
private func isTopPanelExpandedUpdated(isExpanded: Bool, transition: Transition) { private func isTopPanelExpandedUpdated(isExpanded: Bool, transition: Transition) {
if self.isTopPanelExpanded != isExpanded { if self.isTopPanelExpanded != isExpanded {
self.isTopPanelExpanded = isExpanded self.isTopPanelExpanded = isExpanded

View File

@ -352,6 +352,7 @@ private final class TopicIconSelectionComponent: Component {
externalBottomPanelContainer: nil, externalBottomPanelContainer: nil,
displayTopPanelBackground: .blur, displayTopPanelBackground: .blur,
topPanelExtensionUpdated: { _, _ in }, topPanelExtensionUpdated: { _, _ in },
topPanelScrollingOffset: { _, _ in },
hideInputUpdated: { _, _, _ in }, hideInputUpdated: { _, _, _ in },
hideTopPanelUpdated: { _, _ in }, hideTopPanelUpdated: { _, _ in },
switchToTextInput: {}, switchToTextInput: {},

View File

@ -12,6 +12,7 @@ public final class DrawingLocationEntity: DrawingEntity, Codable {
case title case title
case style case style
case location case location
case icon
case queryId case queryId
case resultId case resultId
case referenceDrawingSize case referenceDrawingSize
@ -38,6 +39,7 @@ public final class DrawingLocationEntity: DrawingEntity, Codable {
public var title: String public var title: String
public var style: Style public var style: Style
public var location: TelegramMediaMap public var location: TelegramMediaMap
public var icon: TelegramMediaFile?
public var queryId: Int64? public var queryId: Int64?
public var resultId: String? public var resultId: String?
public var color: DrawingColor = .clear public var color: DrawingColor = .clear
@ -64,12 +66,13 @@ public final class DrawingLocationEntity: DrawingEntity, Codable {
return false return false
} }
public init(title: String, style: Style, location: TelegramMediaMap, queryId: Int64?, resultId: String?) { public init(title: String, style: Style, location: TelegramMediaMap, icon: TelegramMediaFile?, queryId: Int64?, resultId: String?) {
self.uuid = UUID() self.uuid = UUID()
self.title = title self.title = title
self.style = style self.style = style
self.location = location self.location = location
self.icon = icon
self.queryId = queryId self.queryId = queryId
self.resultId = resultId self.resultId = resultId
@ -92,6 +95,10 @@ public final class DrawingLocationEntity: DrawingEntity, Codable {
fatalError() fatalError()
} }
if let iconData = try container.decodeIfPresent(Data.self, forKey: .icon) {
self.icon = PostboxDecoder(buffer: MemoryBuffer(data: iconData)).decodeRootObject() as? TelegramMediaFile
}
self.queryId = try container.decodeIfPresent(Int64.self, forKey: .queryId) self.queryId = try container.decodeIfPresent(Int64.self, forKey: .queryId)
self.resultId = try container.decodeIfPresent(String.self, forKey: .resultId) self.resultId = try container.decodeIfPresent(String.self, forKey: .resultId)
@ -111,11 +118,18 @@ public final class DrawingLocationEntity: DrawingEntity, Codable {
try container.encode(self.title, forKey: .title) try container.encode(self.title, forKey: .title)
try container.encode(self.style, forKey: .style) try container.encode(self.style, forKey: .style)
let encoder = PostboxEncoder() var encoder = PostboxEncoder()
encoder.encodeRootObject(self.location) encoder.encodeRootObject(self.location)
let locationData = encoder.makeData() let locationData = encoder.makeData()
try container.encode(locationData, forKey: .location) try container.encode(locationData, forKey: .location)
if let icon = self.icon {
encoder = PostboxEncoder()
encoder.encodeRootObject(icon)
let iconData = encoder.makeData()
try container.encode(iconData, forKey: .icon)
}
try container.encodeIfPresent(self.queryId, forKey: .queryId) try container.encodeIfPresent(self.queryId, forKey: .queryId)
try container.encodeIfPresent(self.resultId, forKey: .resultId) try container.encodeIfPresent(self.resultId, forKey: .resultId)
@ -130,7 +144,7 @@ public final class DrawingLocationEntity: DrawingEntity, Codable {
} }
public func duplicate() -> DrawingEntity { public func duplicate() -> DrawingEntity {
let newEntity = DrawingLocationEntity(title: self.title, style: self.style, location: self.location, queryId: self.queryId, resultId: self.resultId) let newEntity = DrawingLocationEntity(title: self.title, style: self.style, location: self.location, icon: self.icon, queryId: self.queryId, resultId: self.resultId)
newEntity.referenceDrawingSize = self.referenceDrawingSize newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position newEntity.position = self.position
newEntity.width = self.width newEntity.width = self.width

View File

@ -2707,38 +2707,97 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
controller.push(galleryController) controller.push(galleryController)
} }
private let staticEmojiPack = Promise<LoadedStickerPack>()
private var didSetupStaticEmojiPack = false
func presentLocationPicker(_ existingEntity: DrawingLocationEntity? = nil) { func presentLocationPicker(_ existingEntity: DrawingLocationEntity? = nil) {
guard let controller = self.controller else { guard let controller = self.controller else {
return return
} }
if !self.didSetupStaticEmojiPack {
self.staticEmojiPack.set(self.context.engine.stickers.loadedStickerPack(reference: .name("staticemoji"), forceActualized: false))
}
func flag(countryCode: String) -> String {
let base : UInt32 = 127397
var flagString = ""
for v in countryCode.uppercased().unicodeScalars {
flagString.unicodeScalars.append(UnicodeScalar(base + v.value)!)
}
return flagString
}
var location: CLLocationCoordinate2D? var location: CLLocationCoordinate2D?
if let subject = self.subject, case let .asset(asset) = subject { if let subject = self.subject, case let .asset(asset) = subject {
location = asset.location?.coordinate location = asset.location?.coordinate
} }
let locationController = storyLocationPickerController(context: self.context, location: location, completion: { [weak self] location, queryId, resultId, address in let locationController = storyLocationPickerController(context: self.context, location: location, completion: { [weak self] location, queryId, resultId, address, countryCode in
if let self { if let self {
let title: String let emojiFile: Signal<TelegramMediaFile?, NoError>
if let venueTitle = location.venue?.title { if let countryCode {
title = venueTitle let flagEmoji = flag(countryCode: countryCode)
emojiFile = self.staticEmojiPack.get()
|> filter { result in
if case .result = result {
return true
} else {
return false
}
}
|> take(1)
|> map { result -> TelegramMediaFile? in
if case let .result(_, items, _) = result, let match = items.first(where: { item in
var displayText: String?
for attribute in item.file.attributes {
if case let .CustomEmoji(_, _, alt, _) = attribute {
displayText = alt
break
}
}
if let displayText, displayText.hasPrefix(flagEmoji) {
return true
} else {
return false
}
}) {
return match.file
} else {
return nil
}
}
} else { } else {
title = address ?? "Location" emojiFile = .single(nil)
} }
let position = existingEntity?.position
let scale = existingEntity?.scale ?? 1.0 let _ = emojiFile.start(next: { [weak self] emojiFile in
if let existingEntity { guard let self else {
self.entitiesView.remove(uuid: existingEntity.uuid, animated: true) return
} }
self.interaction?.insertEntity( let title: String
DrawingLocationEntity( if let venueTitle = location.venue?.title {
title: title, title = venueTitle
style: existingEntity?.style ?? .white, } else {
location: location, title = address ?? "Location"
queryId: queryId, }
resultId: resultId let position = existingEntity?.position
), let scale = existingEntity?.scale ?? 1.0
scale: scale, if let existingEntity {
position: position self.entitiesView.remove(uuid: existingEntity.uuid, animated: true)
) }
self.interaction?.insertEntity(
DrawingLocationEntity(
title: title,
style: existingEntity?.style ?? .white,
location: location,
icon: emojiFile,
queryId: queryId,
resultId: resultId
),
scale: scale,
position: position
)
})
} }
}) })
locationController.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak locationController] transition in locationController.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak locationController] transition in
@ -2915,11 +2974,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
controller.presentGallery = { [weak self] in controller.presentGallery = { [weak self] in
if let self { if let self {
self.stickerScreen = nil
self.presentGallery() self.presentGallery()
} }
} }
controller.presentLocationPicker = { [weak self, weak controller] in controller.presentLocationPicker = { [weak self, weak controller] in
if let self { if let self {
self.stickerScreen = nil
controller?.dismiss(animated: true) controller?.dismiss(animated: true)
self.presentLocationPicker() self.presentLocationPicker()
} }
@ -3551,7 +3612,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let controller = UndoOverlayController(presentationData: presentationData, content: .autoDelete(isOn: true, title: nil, text: text, customUndoText: nil), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { [weak self] action in let controller = UndoOverlayController(presentationData: presentationData, content: .autoDelete(isOn: true, title: nil, text: text, customUndoText: nil), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { [weak self] action in
if case .info = action, let self { if case .info = action, let self {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .stories) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .stories, forceDark: true)
self.push(controller) self.push(controller)
} }
return false } return false }
@ -3570,7 +3631,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let controller = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_read", scale: 0.25, colors: [:], title: title, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { [weak self] action in let controller = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_read", scale: 0.25, colors: [:], title: title, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { [weak self] action in
if case .info = action, let self { if case .info = action, let self {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .stories) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .stories, forceDark: true)
self.push(controller) self.push(controller)
} }
return false } return false }
@ -3588,7 +3649,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let controller = UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: text), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { [weak self] action in let controller = UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: text), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { [weak self] action in
if case .info = action, let self { if case .info = action, let self {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .stories) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .stories, forceDark: true)
self.push(controller) self.push(controller)
} }
return false } return false }
@ -3668,7 +3729,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
if saveDraft { if saveDraft {
self.saveDraft(id: nil) self.saveDraft(id: nil)
} else { } else {
if case let .draft(draft, _) = self.node.subject { if case let .draft(draft, id) = self.node.subject, id == nil {
removeStoryDraft(engine: self.context.engine, path: draft.path, delete: true) removeStoryDraft(engine: self.context.engine, path: draft.path, delete: true)
} }
} }
@ -3726,7 +3787,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let draft = MediaEditorDraft(path: path, isVideo: false, thumbnail: thumbnailImage, dimensions: dimensions, duration: nil, values: values, caption: caption, privacy: privacy, timestamp: timestamp) let draft = MediaEditorDraft(path: path, isVideo: false, thumbnail: thumbnailImage, dimensions: dimensions, duration: nil, values: values, caption: caption, privacy: privacy, timestamp: timestamp)
try? data.write(to: URL(fileURLWithPath: draft.fullPath())) try? data.write(to: URL(fileURLWithPath: draft.fullPath()))
if let id { if let id {
saveStorySource(engine: context.engine, item: draft, id: id) saveStorySource(engine: context.engine, item: draft, peerId: context.account.peerId, id: id)
} else { } else {
addStoryDraft(engine: context.engine, item: draft) addStoryDraft(engine: context.engine, item: draft)
} }
@ -3740,7 +3801,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let draft = MediaEditorDraft(path: path, isVideo: true, thumbnail: thumbnailImage, dimensions: dimensions, duration: duration, values: values, caption: caption, privacy: privacy, timestamp: timestamp) let draft = MediaEditorDraft(path: path, isVideo: true, thumbnail: thumbnailImage, dimensions: dimensions, duration: duration, values: values, caption: caption, privacy: privacy, timestamp: timestamp)
try? FileManager.default.moveItem(atPath: videoPath, toPath: draft.fullPath()) try? FileManager.default.moveItem(atPath: videoPath, toPath: draft.fullPath())
if let id { if let id {
saveStorySource(engine: context.engine, item: draft, id: id) saveStorySource(engine: context.engine, item: draft, peerId: context.account.peerId, id: id)
} else { } else {
addStoryDraft(engine: context.engine, item: draft) addStoryDraft(engine: context.engine, item: draft)
} }
@ -3811,7 +3872,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
randomId = Int64.random(in: .min ... .max) randomId = Int64.random(in: .min ... .max)
} }
var mediaAreas: [MediaArea] = self.initialMediaAreas ?? [] var mediaAreas: [MediaArea] = []
if case .draft = subject {
} else {
mediaAreas = self.initialMediaAreas ?? []
}
var stickers: [TelegramMediaFile] = [] var stickers: [TelegramMediaFile] = []
for entity in codableEntities { for entity in codableEntities {
switch entity { switch entity {

View File

@ -4,35 +4,40 @@ import Postbox
import TelegramCore import TelegramCore
import TelegramUIPreferences import TelegramUIPreferences
import MediaEditor import MediaEditor
import AccountContext
public func saveStorySource(engine: TelegramEngine, item: MediaEditorDraft, peerId: EnginePeer.Id, id: Int64) {
public func saveStorySource(engine: TelegramEngine, item: MediaEditorDraft, id: Int64) { let key = EngineDataBuffer(length: 16)
let key = EngineDataBuffer(length: 8) key.setInt64(0, value: peerId.toInt64())
key.setInt64(0, value: id) key.setInt64(8, value: id)
let _ = engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: key, item: item).start() let _ = engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: key, item: item).start()
} }
public func removeStorySource(engine: TelegramEngine, id: Int64) { public func removeStorySource(engine: TelegramEngine, peerId: EnginePeer.Id, id: Int64) {
let key = EngineDataBuffer(length: 8) let key = EngineDataBuffer(length: 16)
key.setInt64(0, value: id) key.setInt64(0, value: peerId.toInt64())
key.setInt64(8, value: id)
let _ = engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: key).start() let _ = engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: key).start()
} }
public func getStorySource(engine: TelegramEngine, id: Int64) -> Signal<MediaEditorDraft?, NoError> { public func getStorySource(engine: TelegramEngine, peerId: EnginePeer.Id, id: Int64) -> Signal<MediaEditorDraft?, NoError> {
let key = EngineDataBuffer(length: 8) let key = EngineDataBuffer(length: 16)
key.setInt64(0, value: id) key.setInt64(0, value: peerId.toInt64())
key.setInt64(8, value: id)
return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: key)) return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: key))
|> map { result -> MediaEditorDraft? in |> map { result -> MediaEditorDraft? in
return result?.get(MediaEditorDraft.self) return result?.get(MediaEditorDraft.self)
} }
} }
public func moveStorySource(engine: TelegramEngine, from fromId: Int64, to toId: Int64) { public func moveStorySource(engine: TelegramEngine, peerId: EnginePeer.Id, from fromId: Int64, to toId: Int64) {
let fromKey = EngineDataBuffer(length: 8) let fromKey = EngineDataBuffer(length: 16)
fromKey.setInt64(0, value: fromId) fromKey.setInt64(0, value: peerId.toInt64())
fromKey.setInt64(8, value: fromId)
let toKey = EngineDataBuffer(length: 8) let toKey = EngineDataBuffer(length: 16)
toKey.setInt64(0, value: toId) toKey.setInt64(0, value: peerId.toInt64())
toKey.setInt64(8, value: toId)
let _ = (engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: fromKey)) let _ = (engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: fromKey))
|> mapToSignal { item -> Signal<Never, NoError> in |> mapToSignal { item -> Signal<Never, NoError> in

View File

@ -277,10 +277,8 @@ final class StickersResultPanelComponent: Component {
guard let self, let component = self.component else { guard let self, let component = self.component else {
return return
} }
let controller = component.context.sharedContext.makePremiumIntroController(context: component.context, source: .stickers) let controller = component.context.sharedContext.makePremiumIntroController(context: component.context, source: .stickers, forceDark: false)
component.present(controller) component.present(controller)
// let controller = PremiumIntroScreen(context: component.context, source: .stickers)
// controllerInteraction.navigationController()?.pushViewController(controller)
})) }))
} else { } else {
return nil return nil

View File

@ -123,12 +123,26 @@ final class SectionHeaderComponent: Component {
if let view = self.action.view { if let view = self.action.view {
if view.superview == nil { if view.superview == nil {
self.addSubview(view) self.addSubview(view)
if !transition.animation.isImmediate {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
}
} }
let actionFrame = CGRect(origin: CGPoint(x: availableSize.width - leftInset - actionSize.width, y: floor((height - titleSize.height) / 2.0)), size: actionSize) let actionFrame = CGRect(origin: CGPoint(x: availableSize.width - leftInset - actionSize.width, y: floor((height - titleSize.height) / 2.0)), size: actionSize)
view.frame = actionFrame view.frame = actionFrame
} }
} else if self.action.view?.superview != nil { } else if let view = self.action.view, view.superview != nil {
self.action.view?.removeFromSuperview() if !transition.animation.isImmediate {
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { finished in
if finished {
view.removeFromSuperview()
view.layer.removeAllAnimations()
}
})
view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
} else {
view.removeFromSuperview()
}
} }
let size = CGSize(width: availableSize.width, height: height) let size = CGSize(width: availableSize.width, height: height)

View File

@ -313,6 +313,7 @@ final class ShareWithPeersScreenComponent: Component {
private var savedSelectedPeers: [EnginePeer.Id] = [] private var savedSelectedPeers: [EnginePeer.Id] = []
private var selectedPeers: [EnginePeer.Id] = [] private var selectedPeers: [EnginePeer.Id] = []
private var selectedGroups: [EnginePeer.Id] = [] private var selectedGroups: [EnginePeer.Id] = []
private var groupPeersMap: [EnginePeer.Id: [EnginePeer.Id]] = [:]
private var peersMap: [EnginePeer.Id: EnginePeer] = [:] private var peersMap: [EnginePeer.Id: EnginePeer] = [:]
@ -635,8 +636,7 @@ final class ShareWithPeersScreenComponent: Component {
} }
progressDisposable.dispose() progressDisposable.dispose()
var peerIds = Set<EnginePeer.Id>() var peerIds = Set<EnginePeer.Id>()
for peer in peers { for peer in peers {
self.peersMap[peer.id] = peer self.peersMap[peer.id] = peer
@ -651,12 +651,15 @@ final class ShareWithPeersScreenComponent: Component {
showCountLimitAlert() showCountLimitAlert()
return return
} }
var groupPeerIds: [EnginePeer.Id] = []
for peer in peers { for peer in peers {
if !existingPeerIds.contains(peer.id) { if !existingPeerIds.contains(peer.id) {
self.selectedPeers.append(peer.id) self.selectedPeers.append(peer.id)
existingPeerIds.insert(peer.id) existingPeerIds.insert(peer.id)
} }
groupPeerIds.append(peer.id)
} }
self.groupPeersMap[peer.id] = groupPeerIds
} else { } else {
self.selectedPeers = self.selectedPeers.filter { !peerIds.contains($0) } self.selectedPeers = self.selectedPeers.filter { !peerIds.contains($0) }
} }
@ -711,7 +714,7 @@ final class ShareWithPeersScreenComponent: Component {
} }
peers.append(EnginePeer(item.peer)) peers.append(EnginePeer(item.peer))
} }
if !peers.isEmpty { if !list.list.isEmpty {
subscriber.putNext(peers) subscriber.putNext(peers)
subscriber.putCompletion() subscriber.putCompletion()
} }
@ -727,6 +730,25 @@ final class ShareWithPeersScreenComponent: Component {
} }
} }
private func updateSelectedGroupPeers() {
var unselectedGroupIds: [EnginePeer.Id] = []
for groupPeerId in self.selectedGroups {
if let groupPeers = self.groupPeersMap[groupPeerId] {
var hasAnyGroupPeerSelected = false
for peerId in groupPeers {
if self.selectedPeers.contains(peerId) {
hasAnyGroupPeerSelected = true
break
}
}
if !hasAnyGroupPeerSelected {
unselectedGroupIds.append(groupPeerId)
}
}
}
self.selectedGroups.removeAll(where: { unselectedGroupIds.contains($0) })
}
private func updateScrolling(transition: Transition) { private func updateScrolling(transition: Transition) {
guard let component = self.component, let environment = self.environment, let itemLayout = self.itemLayout else { guard let component = self.component, let environment = self.environment, let itemLayout = self.itemLayout else {
return return
@ -1024,8 +1046,20 @@ final class ShareWithPeersScreenComponent: Component {
guard let self, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else { guard let self, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else {
return return
} }
let base: EngineStoryPrivacy.Base
if self.selectedCategories.contains(.everyone) {
base = .everyone
} else if self.selectedCategories.contains(.closeFriends) {
base = .closeFriends
} else if self.selectedCategories.contains(.contacts) {
base = .contacts
} else if self.selectedCategories.contains(.selectedContacts) {
base = .nobody
} else {
base = .nobody
}
component.editBlockedPeers( component.editBlockedPeers(
EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []), EngineStoryPrivacy(base: base, additionallyIncludePeers: self.selectedPeers),
self.selectedOptions.contains(.screenshot), self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin) self.selectedOptions.contains(.pin)
) )
@ -1106,6 +1140,7 @@ final class ShareWithPeersScreenComponent: Component {
} else { } else {
if let index = self.selectedPeers.firstIndex(of: peer.id) { if let index = self.selectedPeers.firstIndex(of: peer.id) {
self.selectedPeers.remove(at: index) self.selectedPeers.remove(at: index)
self.updateSelectedGroupPeers()
} else { } else {
self.selectedPeers.append(peer.id) self.selectedPeers.append(peer.id)
} }
@ -1566,6 +1601,7 @@ final class ShareWithPeersScreenComponent: Component {
self.selectedCategories.remove(categoryId) self.selectedCategories.remove(categoryId)
} else if let peerId = tokenId.base as? EnginePeer.Id { } else if let peerId = tokenId.base as? EnginePeer.Id {
self.selectedPeers.removeAll(where: { $0 == peerId }) self.selectedPeers.removeAll(where: { $0 == peerId })
self.updateSelectedGroupPeers()
} }
if self.selectedCategories.isEmpty { if self.selectedCategories.isEmpty {
self.selectedCategories.insert(.everyone) self.selectedCategories.insert(.everyone)
@ -2099,6 +2135,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
context: AccountContext, context: AccountContext,
subject: Subject = .chats(blocked: false), subject: Subject = .chats(blocked: false),
initialPeerIds: Set<EnginePeer.Id> = Set(), initialPeerIds: Set<EnginePeer.Id> = Set(),
savedSelectedPeers: Set<EnginePeer.Id> = Set(),
closeFriends: Signal<[EnginePeer], NoError> = .single([]), closeFriends: Signal<[EnginePeer], NoError> = .single([]),
blockedPeersContext: BlockedPeersContext? = nil blockedPeersContext: BlockedPeersContext? = nil
) { ) {

View File

@ -368,6 +368,7 @@ private final class StoryContainerScreenComponent: Component {
private let inputMediaNodeDataPromise = Promise<ChatEntityKeyboardInputNode.InputData>() private let inputMediaNodeDataPromise = Promise<ChatEntityKeyboardInputNode.InputData>()
private let closeFriendsPromise = Promise<[EnginePeer]>() private let closeFriendsPromise = Promise<[EnginePeer]>()
private var blockedPeers: BlockedPeersContext?
private var availableReactions: StoryAvailableReactions? private var availableReactions: StoryAvailableReactions?
@ -1128,6 +1129,8 @@ private final class StoryContainerScreenComponent: Component {
self.closeFriendsPromise.set( self.closeFriendsPromise.set(
component.context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.CloseFriends()) component.context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.CloseFriends())
) )
self.blockedPeers = BlockedPeersContext(account: component.context.account, subject: .stories)
} }
var update = false var update = false
@ -1478,6 +1481,7 @@ private final class StoryContainerScreenComponent: Component {
}, },
keyboardInputData: self.inputMediaNodeDataPromise.get(), keyboardInputData: self.inputMediaNodeDataPromise.get(),
closeFriends: self.closeFriendsPromise, closeFriends: self.closeFriendsPromise,
blockedPeers: self.blockedPeers,
sharedViewListsContext: self.sharedViewListsContext, sharedViewListsContext: self.sharedViewListsContext,
stealthModeTimeout: stealthModeTimeout stealthModeTimeout: stealthModeTimeout
)), )),

View File

@ -71,6 +71,8 @@ final class StoryItemContentComponent: Component {
private var videoNode: UniversalVideoNode? private var videoNode: UniversalVideoNode?
private var loadingEffectView: StoryItemLoadingEffectView? private var loadingEffectView: StoryItemLoadingEffectView?
private var mediaAreasEffectView: StoryItemLoadingEffectView?
private var currentMessageMedia: EngineMedia? private var currentMessageMedia: EngineMedia?
private var fetchDisposable: Disposable? private var fetchDisposable: Disposable?
private var priorityDisposable: Disposable? private var priorityDisposable: Disposable?
@ -715,7 +717,7 @@ final class StoryItemContentComponent: Component {
if let current = self.loadingEffectView { if let current = self.loadingEffectView {
loadingEffectView = current loadingEffectView = current
} else { } else {
loadingEffectView = StoryItemLoadingEffectView(frame: CGRect()) loadingEffectView = StoryItemLoadingEffectView(effectAlpha: 0.1, duration: 1.0, hasBorder: true, playOnce: false)
self.loadingEffectView = loadingEffectView self.loadingEffectView = loadingEffectView
self.addSubview(loadingEffectView) self.addSubview(loadingEffectView)
} }
@ -727,6 +729,54 @@ final class StoryItemContentComponent: Component {
}) })
} }
if self.contentLoaded {
if reloadMedia {
if let mediaAreasEffectView = self.mediaAreasEffectView {
self.mediaAreasEffectView = nil
mediaAreasEffectView.removeFromSuperview()
}
}
if !component.item.mediaAreas.isEmpty {
let mediaAreasEffectView: StoryItemLoadingEffectView
if let current = self.mediaAreasEffectView {
mediaAreasEffectView = current
} else {
mediaAreasEffectView = StoryItemLoadingEffectView(effectAlpha: 0.25, duration: 1.5, hasBorder: false, playOnce: true)
self.mediaAreasEffectView = mediaAreasEffectView
self.addSubview(mediaAreasEffectView)
}
mediaAreasEffectView.update(size: availableSize, transition: transition)
let maskView: UIView
if let current = mediaAreasEffectView.mask {
maskView = current
} else {
maskView = UIView(frame: CGRect(origin: .zero, size: availableSize))
mediaAreasEffectView.mask = maskView
}
if maskView.subviews.isEmpty {
let referenceSize = availableSize
for mediaArea in component.item.mediaAreas {
let size = CGSize(width: mediaArea.coordinates.width / 100.0 * referenceSize.width, height: mediaArea.coordinates.height / 100.0 * referenceSize.height)
let position = CGPoint(x: mediaArea.coordinates.x / 100.0 * referenceSize.width, y: mediaArea.coordinates.y / 100.0 * referenceSize.height)
let view = UIView()
view.backgroundColor = .white
view.bounds = CGRect(origin: .zero, size: size)
view.center = position
view.layer.cornerRadius = size.height * 0.18
maskView.addSubview(view)
view.transform = CGAffineTransformMakeRotation(mediaArea.coordinates.rotation * Double.pi / 180.0)
}
}
} else if let mediaAreasEffectView = self.mediaAreasEffectView {
self.mediaAreasEffectView = nil
mediaAreasEffectView.removeFromSuperview()
}
}
return availableSize return availableSize
} }
} }

View File

@ -5,6 +5,10 @@ import ComponentFlow
import Display import Display
final class StoryItemLoadingEffectView: UIView { final class StoryItemLoadingEffectView: UIView {
private let effectAlpha: CGFloat
private let duration: Double
private let playOnce: Bool
private let hierarchyTrackingLayer: HierarchyTrackingLayer private let hierarchyTrackingLayer: HierarchyTrackingLayer
private let gradientWidth: CGFloat private let gradientWidth: CGFloat
@ -14,9 +18,13 @@ final class StoryItemLoadingEffectView: UIView {
private let borderContainerView: UIView private let borderContainerView: UIView
private let borderMaskLayer: SimpleShapeLayer private let borderMaskLayer: SimpleShapeLayer
override init(frame: CGRect) { init(effectAlpha: CGFloat, duration: Double, hasBorder: Bool, playOnce: Bool) {
self.hierarchyTrackingLayer = HierarchyTrackingLayer() self.hierarchyTrackingLayer = HierarchyTrackingLayer()
self.effectAlpha = effectAlpha
self.duration = duration
self.playOnce = playOnce
self.gradientWidth = 200.0 self.gradientWidth = 200.0
self.backgroundView = UIImageView() self.backgroundView = UIImageView()
@ -24,7 +32,7 @@ final class StoryItemLoadingEffectView: UIView {
self.borderContainerView = UIView() self.borderContainerView = UIView()
self.borderMaskLayer = SimpleShapeLayer() self.borderMaskLayer = SimpleShapeLayer()
super.init(frame: frame) super.init(frame: .zero)
self.layer.addSublayer(self.hierarchyTrackingLayer) self.layer.addSublayer(self.hierarchyTrackingLayer)
self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
@ -65,13 +73,15 @@ final class StoryItemLoadingEffectView: UIView {
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions()) context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
}) })
} }
self.backgroundView.image = generateGradient(0.1) self.backgroundView.image = generateGradient(self.effectAlpha)
self.addSubview(self.backgroundView) self.addSubview(self.backgroundView)
self.borderGradientView.image = generateGradient(0.2) if hasBorder {
self.borderContainerView.addSubview(self.borderGradientView) self.borderGradientView.image = generateGradient(self.effectAlpha + 0.1)
self.addSubview(self.borderContainerView) self.borderContainerView.addSubview(self.borderGradientView)
self.borderContainerView.layer.mask = self.borderMaskLayer self.addSubview(self.borderContainerView)
self.borderContainerView.layer.mask = self.borderMaskLayer
}
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -83,8 +93,8 @@ final class StoryItemLoadingEffectView: UIView {
return return
} }
let animation = self.backgroundView.layer.makeAnimation(from: 0.0 as NSNumber, to: (size.width + self.gradientWidth + size.width * 0.2) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true) let animation = self.backgroundView.layer.makeAnimation(from: 0.0 as NSNumber, to: (size.width + self.gradientWidth + size.width * 0.2) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: self.duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
animation.repeatCount = Float.infinity animation.repeatCount = self.playOnce ? 1 : Float.infinity
self.backgroundView.layer.add(animation, forKey: "shimmer") self.backgroundView.layer.add(animation, forKey: "shimmer")
self.borderGradientView.layer.add(animation, forKey: "shimmer") self.borderGradientView.layer.add(animation, forKey: "shimmer")
} }

View File

@ -119,6 +119,7 @@ public final class StoryItemSetContainerComponent: Component {
public let toggleAmbientMode: () -> Void public let toggleAmbientMode: () -> Void
public let keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError> public let keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>
public let closeFriends: Promise<[EnginePeer]> public let closeFriends: Promise<[EnginePeer]>
public let blockedPeers: BlockedPeersContext?
let sharedViewListsContext: StoryItemSetViewListComponent.SharedListsContext let sharedViewListsContext: StoryItemSetViewListComponent.SharedListsContext
let stealthModeTimeout: Int32? let stealthModeTimeout: Int32?
@ -154,6 +155,7 @@ public final class StoryItemSetContainerComponent: Component {
toggleAmbientMode: @escaping () -> Void, toggleAmbientMode: @escaping () -> Void,
keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>, keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>,
closeFriends: Promise<[EnginePeer]>, closeFriends: Promise<[EnginePeer]>,
blockedPeers: BlockedPeersContext?,
sharedViewListsContext: StoryItemSetViewListComponent.SharedListsContext, sharedViewListsContext: StoryItemSetViewListComponent.SharedListsContext,
stealthModeTimeout: Int32? stealthModeTimeout: Int32?
) { ) {
@ -188,6 +190,7 @@ public final class StoryItemSetContainerComponent: Component {
self.toggleAmbientMode = toggleAmbientMode self.toggleAmbientMode = toggleAmbientMode
self.keyboardInputData = keyboardInputData self.keyboardInputData = keyboardInputData
self.closeFriends = closeFriends self.closeFriends = closeFriends
self.blockedPeers = blockedPeers
self.sharedViewListsContext = sharedViewListsContext self.sharedViewListsContext = sharedViewListsContext
self.stealthModeTimeout = stealthModeTimeout self.stealthModeTimeout = stealthModeTimeout
} }
@ -2415,7 +2418,8 @@ public final class StoryItemSetContainerComponent: Component {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stories"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stories"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in }, action: { [weak self] _, f in
f(.default) f(.default)
let _ = component.context.engine.privacy.requestUpdatePeerIsBlockedFromStories(peerId: peer.id, isBlocked: false).start()
let _ = component.blockedPeers?.remove(peerId: peer.id).start()
guard let self else { guard let self else {
return return
@ -2428,6 +2432,7 @@ public final class StoryItemSetContainerComponent: Component {
elevatedLayout: false, elevatedLayout: false,
position: .top, position: .top,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true,
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
}))) })))
@ -2436,7 +2441,7 @@ public final class StoryItemSetContainerComponent: Component {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stories"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stories"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in }, action: { [weak self] _, f in
f(.default) f(.default)
let _ = component.context.engine.privacy.requestUpdatePeerIsBlockedFromStories(peerId: peer.id, isBlocked: true).start() let _ = component.blockedPeers?.add(peerId: peer.id).start()
guard let self else { guard let self else {
return return
@ -2448,6 +2453,7 @@ public final class StoryItemSetContainerComponent: Component {
elevatedLayout: false, elevatedLayout: false,
position: .top, position: .top,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true,
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
}))) })))
@ -2484,6 +2490,7 @@ public final class StoryItemSetContainerComponent: Component {
elevatedLayout: false, elevatedLayout: false,
position: .top, position: .top,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true,
action: { [weak self] action in action: { [weak self] action in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return false return false
@ -2530,6 +2537,7 @@ public final class StoryItemSetContainerComponent: Component {
elevatedLayout: false, elevatedLayout: false,
position: .top, position: .top,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true,
action: { [weak self] action in action: { [weak self] action in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return false return false
@ -3171,7 +3179,7 @@ public final class StoryItemSetContainerComponent: Component {
storeAttributedTextInPasteboard(text) storeAttributedTextInPasteboard(text)
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let undoController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in true }) let undoController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, blurred: true, action: { _ in true })
self.sendMessageContext.tooltipScreen?.dismiss() self.sendMessageContext.tooltipScreen?.dismiss()
self.sendMessageContext.tooltipScreen = undoController self.sendMessageContext.tooltipScreen = undoController
component.controller()?.present(undoController, in: .current) component.controller()?.present(undoController, in: .current)
@ -3405,6 +3413,7 @@ public final class StoryItemSetContainerComponent: Component {
}), }),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true,
action: { [weak self] _ in action: { [weak self] _ in
self?.sendMessageContext.tooltipScreen = nil self?.sendMessageContext.tooltipScreen = nil
self?.updateIsProgressPaused() self?.updateIsProgressPaused()
@ -3461,7 +3470,7 @@ public final class StoryItemSetContainerComponent: Component {
controller?.replace(with: c) controller?.replace(with: c)
} }
component.controller()?.push(controller) component.controller()?.push(controller)
}), elevatedLayout: false, animateInAsReplacement: false, action: { _ in true }) }), elevatedLayout: false, animateInAsReplacement: false, blurred: true, action: { _ in true })
component.controller()?.present(undoController, in: .current) component.controller()?.present(undoController, in: .current)
} }
} }
@ -3630,7 +3639,7 @@ public final class StoryItemSetContainerComponent: Component {
} else if privacy.base == .nobody { } else if privacy.base == .nobody {
if !privacy.additionallyIncludePeers.isEmpty { if !privacy.additionallyIncludePeers.isEmpty {
let value = component.strings.Story_PrivacyTooltipSelectedContacts_Contacts(Int32(privacy.additionallyIncludePeers.count)) let value = component.strings.Story_PrivacyTooltipSelectedContacts_Contacts(Int32(privacy.additionallyIncludePeers.count))
text = component.strings.Story_PrivacyTooltipSelectedContactsCount(value).string text = component.strings.Story_PrivacyTooltipSelectedContactsFull(value).string
} else { } else {
text = component.strings.Story_PrivacyTooltipNobody text = component.strings.Story_PrivacyTooltipNobody
} }
@ -3644,6 +3653,7 @@ public final class StoryItemSetContainerComponent: Component {
content: .info(title: nil, text: text, timeout: nil), content: .info(title: nil, text: text, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true,
action: { _ in return false } action: { _ in return false }
) )
self.sendMessageContext.tooltipScreen = controller self.sendMessageContext.tooltipScreen = controller
@ -3666,7 +3676,8 @@ public final class StoryItemSetContainerComponent: Component {
context: context, context: context,
subject: .stories(editing: true), subject: .stories(editing: true),
initialPeerIds: Set(privacy.additionallyIncludePeers), initialPeerIds: Set(privacy.additionallyIncludePeers),
closeFriends: component.closeFriends.get() closeFriends: component.closeFriends.get(),
blockedPeersContext: component.blockedPeers
) )
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
guard let self else { guard let self else {
@ -3725,9 +3736,10 @@ public final class StoryItemSetContainerComponent: Component {
} }
private func openItemPrivacyCategory(privacy: EngineStoryPrivacy, blockedPeers: Bool, completion: @escaping (EngineStoryPrivacy) -> Void) { private func openItemPrivacyCategory(privacy: EngineStoryPrivacy, blockedPeers: Bool, completion: @escaping (EngineStoryPrivacy) -> Void) {
guard let context = self.component?.context else { guard let component = self.component else {
return return
} }
let context = component.context
let subject: ShareWithPeersScreen.StateContext.Subject let subject: ShareWithPeersScreen.StateContext.Subject
if blockedPeers { if blockedPeers {
subject = .chats(blocked: true) subject = .chats(blocked: true)
@ -3736,7 +3748,12 @@ public final class StoryItemSetContainerComponent: Component {
} else { } else {
subject = .contacts(privacy.base) subject = .contacts(privacy.base)
} }
let stateContext = ShareWithPeersScreen.StateContext(context: context, subject: subject, initialPeerIds: Set(privacy.additionallyIncludePeers)) let stateContext = ShareWithPeersScreen.StateContext(
context: context,
subject: subject,
initialPeerIds: Set(privacy.additionallyIncludePeers),
blockedPeersContext: component.blockedPeers
)
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
guard let self else { guard let self else {
return return
@ -3911,32 +3928,38 @@ public final class StoryItemSetContainerComponent: Component {
} }
let subject: Signal<MediaEditorScreen.Subject?, NoError> let subject: Signal<MediaEditorScreen.Subject?, NoError>
subject = getStorySource(engine: component.context.engine, peerId: component.context.account.peerId, id: Int64(item.id))
var duration: Double? |> mapToSignal { source in
let media = item.media._asMedia() if let source {
if let file = media as? TelegramMediaFile { return .single(.draft(source, Int64(item.id)))
duration = file.duration
}
subject = fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .peer(peerReference.id), customUserContentType: .story, mediaReference: .story(peer: peerReference, id: item.id, media: media))
|> mapToSignal { (value, isImage) -> Signal<MediaEditorScreen.Subject?, NoError> in
guard case let .data(data) = value, data.complete else {
return .complete()
}
if let image = UIImage(contentsOfFile: data.path) {
return .single(nil)
|> then(
.single(.image(image, PixelDimensions(image.size), nil, .bottomRight))
|> delay(0.1, queue: Queue.mainQueue())
)
} else { } else {
let symlinkPath = data.path + ".mp4" let media = item.media._asMedia()
if fileSize(symlinkPath) == nil { return fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .peer(peerReference.id), customUserContentType: .story, mediaReference: .story(peer: peerReference, id: item.id, media: media))
let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath) |> mapToSignal { (value, isImage) -> Signal<MediaEditorScreen.Subject?, NoError> in
guard case let .data(data) = value, data.complete else {
return .complete()
}
if let image = UIImage(contentsOfFile: data.path) {
return .single(nil)
|> then(
.single(.image(image, PixelDimensions(image.size), nil, .bottomRight))
|> delay(0.1, queue: Queue.mainQueue())
)
} else {
var duration: Double?
if let file = media as? TelegramMediaFile {
duration = file.duration
}
let symlinkPath = data.path + ".mp4"
if fileSize(symlinkPath) == nil {
let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath)
}
return .single(nil)
|> then(
.single(.video(symlinkPath, nil, false, nil, nil, PixelDimensions(width: 720, height: 1280), duration ?? 0.0, [], .bottomRight))
)
}
} }
return .single(nil)
|> then(
.single(.video(symlinkPath, nil, false, nil, nil, PixelDimensions(width: 720, height: 1280), duration ?? 0.0, [], .bottomRight))
)
} }
} }
@ -4454,6 +4477,7 @@ public final class StoryItemSetContainerComponent: Component {
content: .info(title: nil, text: component.strings.Story_ToastRemovedFromProfileText, timeout: nil), content: .info(title: nil, text: component.strings.Story_ToastRemovedFromProfileText, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true,
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
} else { } else {
@ -4462,6 +4486,7 @@ public final class StoryItemSetContainerComponent: Component {
content: .info(title: component.strings.Story_ToastSavedToProfileTitle, text: component.strings.Story_ToastSavedToProfileText, timeout: nil), content: .info(title: component.strings.Story_ToastSavedToProfileTitle, text: component.strings.Story_ToastSavedToProfileText, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true,
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
} }
@ -4520,6 +4545,7 @@ public final class StoryItemSetContainerComponent: Component {
content: .linkCopied(text: component.strings.Story_ToastLinkCopied), content: .linkCopied(text: component.strings.Story_ToastLinkCopied),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true,
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
} }
@ -4624,6 +4650,7 @@ public final class StoryItemSetContainerComponent: Component {
], title: nil, text: component.strings.StoryFeed_TooltipNotifyOn(component.slice.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil), ], title: nil, text: component.strings.StoryFeed_TooltipNotifyOn(component.slice.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true,
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
} else { } else {
@ -4638,6 +4665,7 @@ public final class StoryItemSetContainerComponent: Component {
], title: nil, text: component.strings.StoryFeed_TooltipNotifyOff(component.slice.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil), ], title: nil, text: component.strings.StoryFeed_TooltipNotifyOff(component.slice.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true,
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
} }
@ -4748,6 +4776,7 @@ public final class StoryItemSetContainerComponent: Component {
content: .linkCopied(text: component.strings.Story_ToastLinkCopied), content: .linkCopied(text: component.strings.Story_ToastLinkCopied),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true,
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
} }
@ -4801,7 +4830,19 @@ public final class StoryItemSetContainerComponent: Component {
return return
} }
let _ = component.context.engine.peers.reportPeerStory(peerId: component.slice.peer.id, storyId: component.slice.item.storyItem.id, reason: reason, message: "").start() let _ = component.context.engine.peers.reportPeerStory(peerId: component.slice.peer.id, storyId: component.slice.item.storyItem.id, reason: reason, message: "").start()
controller.present(UndoOverlayController(presentationData: presentationData, content: .emoji(name: "PoliceCar", text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current) controller.present(
UndoOverlayController(
presentationData: presentationData,
content: .emoji(
name: "PoliceCar",
text: presentationData.strings.Report_Succeed
),
elevatedLayout: false,
blurred: true,
action: { _ in return false }
)
, in: .current
)
} }
) )
}))) })))

View File

@ -1517,7 +1517,7 @@ final class StoryItemSetContainerSendMessage {
let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != component.context.account.peerId let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != component.context.account.peerId
let theme = component.theme let theme = component.theme
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) })
let controller = LocationPickerController(context: component.context, updatedPresentationData: updatedPresentationData, mode: .share(peer: peer, selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self, weak view] location, _, _, _ in let controller = LocationPickerController(context: component.context, updatedPresentationData: updatedPresentationData, mode: .share(peer: peer, selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self, weak view] location, _, _, _, _ in
guard let self, let view else { guard let self, let view else {
return return
} }
@ -3109,11 +3109,11 @@ final class StoryItemSetContainerSendMessage {
let subject = EngineMessage(stableId: 0, stableVersion: 0, id: EngineMessage.Id(peerId: PeerId(0), namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [.geo(TelegramMediaMap(latitude: venue.latitude, longitude: venue.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: venue.venue, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil))], peers: [:], associatedMessages: [:], associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) let subject = EngineMessage(stableId: 0, stableVersion: 0, id: EngineMessage.Id(peerId: PeerId(0), namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [.geo(TelegramMediaMap(latitude: venue.latitude, longitude: venue.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: venue.venue, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil))], peers: [:], associatedMessages: [:], associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
actions.append(ContextMenuAction(content: .textWithIcon(title: "View Location", icon: generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: .white)), action: { [weak controller, weak view] in actions.append(ContextMenuAction(content: .textWithIcon(title: "View Location", icon: generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: .white)), action: { [weak controller, weak view] in
let locationController = LocationViewController(context: component.context, updatedPresentationData: updatedPresentationData, subject: subject, params: LocationViewParams(sendLiveLocation: { _ in }, stopLiveLocation: { _ in }, openUrl: { _ in }, openPeer: { _ in })) let locationController = LocationViewController(context: component.context, updatedPresentationData: updatedPresentationData, subject: subject, disableDismissGesture: true, params: LocationViewParams(sendLiveLocation: { _ in }, stopLiveLocation: { _ in }, openUrl: { _ in }, openPeer: { _ in }))
view?.updateModalTransitionFactor(1.0, transition: .animated(duration: 0.5, curve: .spring)) view?.updateModalTransitionFactor(1.0, transition: .animated(duration: 0.5, curve: .spring))
locationController.dismissed = { [weak view] in locationController.dismissed = { [weak view] in
view?.updateModalTransitionFactor(0.0, transition: .animated(duration: 0.5, curve: .spring)) view?.updateModalTransitionFactor(0.0, transition: .animated(duration: 0.5, curve: .spring))
Queue.mainQueue().after(0.3, { Queue.mainQueue().after(0.5, {
view?.updateIsProgressPaused() view?.updateIsProgressPaused()
}) })
} }
@ -3127,7 +3127,7 @@ final class StoryItemSetContainerSendMessage {
frame = view.controlsContainerView.convert(frame, to: nil) frame = view.controlsContainerView.convert(frame, to: nil)
let node = controller.displayNode let node = controller.displayNode
let menuController = ContextMenuController(actions: actions) let menuController = ContextMenuController(actions: actions, blurred: true)
menuController.centerHorizontally = true menuController.centerHorizontally = true
controller.present( controller.present(
menuController, menuController,

View File

@ -13389,7 +13389,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != strongSelf.context.account.peerId && strongSelf.presentationInterfaceState.subject != .scheduledMessages let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != strongSelf.context.account.peerId && strongSelf.presentationInterfaceState.subject != .scheduledMessages
let controller = LocationPickerController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .share(peer: EnginePeer(peer), selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self] location, _, _, _ in let controller = LocationPickerController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .share(peer: EnginePeer(peer), selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self] location, _, _, _, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -14379,7 +14379,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != strongSelf.context.account.peerId && strongSelf.presentationInterfaceState.subject != .scheduledMessages let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != strongSelf.context.account.peerId && strongSelf.presentationInterfaceState.subject != .scheduledMessages
let controller = LocationPickerController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .share(peer: EnginePeer(peer), selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self] location, _, _, _ in let controller = LocationPickerController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .share(peer: EnginePeer(peer), selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self] location, _, _, _, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -368,7 +368,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
if case .undo = action { if case .undo = action {
var replaceImpl: ((ViewController) -> Void)? var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, action: { let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false)
replaceImpl?(controller) replaceImpl?(controller)
}) })
replaceImpl = { [weak controller] c in replaceImpl = { [weak controller] c in

View File

@ -1548,7 +1548,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
let context = item.context let context = item.context
var replaceImpl: ((ViewController) -> Void)? var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, action: { let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false)
replaceImpl?(controller) replaceImpl?(controller)
}) })
replaceImpl = { [weak controller] c in replaceImpl = { [weak controller] c in

View File

@ -1119,7 +1119,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
}, changeLocation: { }, changeLocation: {
endEditingImpl?() endEditingImpl?()
let controller = LocationPickerController(context: context, mode: .pick, completion: { location, _, _, address in let controller = LocationPickerController(context: context, mode: .pick, completion: { location, _, _, address, _ in
let addressSignal: Signal<String, NoError> let addressSignal: Signal<String, NoError>
if let address = address { if let address = address {
addressSignal = .single(address) addressSignal = .single(address)

View File

@ -7066,7 +7066,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
return return
} }
let controller = LocationPickerController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, mode: .pick, completion: { [weak self] location, _, _, address in let controller = LocationPickerController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, mode: .pick, completion: { [weak self] location, _, _, address, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -201,7 +201,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
let context = strongSelf.context let context = strongSelf.context
var replaceImpl: ((ViewController) -> Void)? var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumLimitController(context: context, subject: .folders, count: strongSelf.tabContainerNode?.filtersCount ?? 0, action: { let controller = context.sharedContext.makePremiumLimitController(context: context, subject: .folders, count: strongSelf.tabContainerNode?.filtersCount ?? 0, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .folders) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .folders, forceDark: false)
replaceImpl?(controller) replaceImpl?(controller)
}) })
replaceImpl = { [weak controller] c in replaceImpl = { [weak controller] c in

View File

@ -293,7 +293,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
} }
var replaceImpl: ((ViewController) -> Void)? var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumLimitController(context: context, subject: .folders, count: strongSelf.controller?.tabContainerNode?.filtersCount ?? 0, action: { let controller = context.sharedContext.makePremiumLimitController(context: context, subject: .folders, count: strongSelf.controller?.tabContainerNode?.filtersCount ?? 0, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .folders) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .folders, forceDark: false)
replaceImpl?(controller) replaceImpl?(controller)
}) })
replaceImpl = { [weak controller] c in replaceImpl = { [weak controller] c in

View File

@ -1732,7 +1732,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return archiveSettingsController(context: context) return archiveSettingsController(context: context)
} }
public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController { public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool) -> ViewController {
let mappedSource: PremiumSource let mappedSource: PremiumSource
switch source { switch source {
case .settings: case .settings:
@ -1780,7 +1780,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
case .stories: case .stories:
mappedSource = .stories mappedSource = .stories
} }
return PremiumIntroScreen(context: context, source: mappedSource) return PremiumIntroScreen(context: context, source: mappedSource, forceDark: forceDark)
} }
public func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> Void) -> ViewController { public func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> Void) -> ViewController {

View File

@ -83,6 +83,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
private var detailsPlaceholderNode: DetailsChatPlaceholderNode? private var detailsPlaceholderNode: DetailsChatPlaceholderNode?
private var applicationInFocusDisposable: Disposable? private var applicationInFocusDisposable: Disposable?
private var storyUploadEventsDisposable: Disposable?
public init(context: AccountContext) { public init(context: AccountContext) {
self.context = context self.context = context
@ -111,6 +112,15 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|> deliverOn(Queue.mainQueue())).start(next: { value in |> deliverOn(Queue.mainQueue())).start(next: { value in
context.sharedContext.mainWindow?.setForceBadgeHidden(!value) context.sharedContext.mainWindow?.setForceBadgeHidden(!value)
}) })
self.storyUploadEventsDisposable = (context.engine.messages.allStoriesUploadEvents()
|> deliverOnMainQueue).start(next: { [weak self] event in
guard let self else {
return
}
let (stableId, id) = event
moveStorySource(engine: self.context.engine, peerId: self.context.account.peerId, from: Int64(stableId), to: Int64(id))
})
} }
} }
@ -122,6 +132,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
self.permissionsDisposable?.dispose() self.permissionsDisposable?.dispose()
self.presentationDataDisposable?.dispose() self.presentationDataDisposable?.dispose()
self.applicationInFocusDisposable?.dispose() self.applicationInFocusDisposable?.dispose()
self.storyUploadEventsDisposable?.dispose()
} }
public func getContactsController() -> ViewController? { public func getContactsController() -> ViewController? {
@ -362,6 +373,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
return return
} }
let context = self.context
if let rootTabController = self.rootTabController { if let rootTabController = self.rootTabController {
if let index = rootTabController.controllers.firstIndex(where: { $0 is ChatListController}) { if let index = rootTabController.controllers.firstIndex(where: { $0 is ChatListController}) {
rootTabController.selectedIndex = index rootTabController.selectedIndex = index
@ -397,7 +409,10 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
if let imageData = compressImageToJPEG(image, quality: 0.7) { if let imageData = compressImageToJPEG(image, quality: 0.7) {
let entities = generateChatInputTextEntities(caption) let entities = generateChatInputTextEntities(caption)
Logger.shared.log("MediaEditor", "Calling uploadStory for image, randomId \(randomId)") Logger.shared.log("MediaEditor", "Calling uploadStory for image, randomId \(randomId)")
self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData, stickers: stickers), mediaAreas: mediaAreas, text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId) let _ = (context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData, stickers: stickers), mediaAreas: mediaAreas, text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId)
|> deliverOnMainQueue).start(next: { stableId in
moveStorySource(engine: context.engine, peerId: context.account.peerId, from: randomId, to: Int64(stableId))
})
completionImpl() completionImpl()
} }
@ -428,7 +443,10 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
} }
Logger.shared.log("MediaEditor", "Calling uploadStory for video, randomId \(randomId)") Logger.shared.log("MediaEditor", "Calling uploadStory for video, randomId \(randomId)")
let entities = generateChatInputTextEntities(caption) let entities = generateChatInputTextEntities(caption)
self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: stickers), mediaAreas: mediaAreas, text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId) let _ = (context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: stickers), mediaAreas: mediaAreas, text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId)
|> deliverOnMainQueue).start(next: { stableId in
moveStorySource(engine: context.engine, peerId: context.account.peerId, from: randomId, to: Int64(stableId))
})
completionImpl() completionImpl()
} }

View File

@ -1029,7 +1029,7 @@ public class TranslateScreen: ViewController {
self.title = presentationData.strings.Translate_Title self.title = presentationData.strings.Translate_Title
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed))
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)

View File

@ -69,17 +69,19 @@ public final class UndoOverlayController: ViewController {
private let animateInAsReplacement: Bool private let animateInAsReplacement: Bool
private var action: (UndoOverlayAction) -> Bool private var action: (UndoOverlayAction) -> Bool
private let blurred: Bool
private var didPlayPresentationAnimation = false private var didPlayPresentationAnimation = false
private var dismissed = false private var dismissed = false
public var keepOnParentDismissal = false public var keepOnParentDismissal = false
public init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, position: Position = .bottom, animateInAsReplacement: Bool = false, action: @escaping (UndoOverlayAction) -> Bool) { public init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, position: Position = .bottom, animateInAsReplacement: Bool = false, blurred: Bool = false, action: @escaping (UndoOverlayAction) -> Bool) {
self.presentationData = presentationData self.presentationData = presentationData
self.content = content self.content = content
self.elevatedLayout = elevatedLayout self.elevatedLayout = elevatedLayout
self.position = position self.position = position
self.animateInAsReplacement = animateInAsReplacement self.animateInAsReplacement = animateInAsReplacement
self.blurred = blurred
self.action = action self.action = action
super.init(navigationBarPresentationData: nil) super.init(navigationBarPresentationData: nil)
@ -92,7 +94,7 @@ public final class UndoOverlayController: ViewController {
} }
override public func loadDisplayNode() { override public func loadDisplayNode() {
self.displayNode = UndoOverlayControllerNode(presentationData: self.presentationData, content: self.content, elevatedLayout: self.elevatedLayout, placementPosition: self.position, action: { [weak self] value in self.displayNode = UndoOverlayControllerNode(presentationData: self.presentationData, content: self.content, elevatedLayout: self.elevatedLayout, placementPosition: self.position, blurred: self.blurred, action: { [weak self] value in
return self?.action(value) ?? false return self?.action(value) ?? false
}, dismiss: { [weak self] in }, dismiss: { [weak self] in
self?.dismiss() self?.dismiss()

View File

@ -47,6 +47,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
private let dismiss: () -> Void private let dismiss: () -> Void
private var content: UndoOverlayContent private var content: UndoOverlayContent
private let blurred: Bool
private let effectView: UIView private let effectView: UIView
@ -60,10 +61,11 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
private var fetchResourceDisposable: Disposable? private var fetchResourceDisposable: Disposable?
init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, placementPosition: UndoOverlayController.Position, action: @escaping (UndoOverlayAction) -> Bool, dismiss: @escaping () -> Void) { init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, placementPosition: UndoOverlayController.Position, blurred: Bool, action: @escaping (UndoOverlayAction) -> Bool, dismiss: @escaping () -> Void) {
self.presentationData = presentationData self.presentationData = presentationData
self.elevatedLayout = elevatedLayout self.elevatedLayout = elevatedLayout
self.placementPosition = placementPosition self.placementPosition = placementPosition
self.blurred = blurred
self.content = content self.content = content
self.action = action self.action = action
@ -1080,7 +1082,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.undoButtonNode = HighlightTrackingButtonNode() self.undoButtonNode = HighlightTrackingButtonNode()
self.panelNode = ASDisplayNode() self.panelNode = ASDisplayNode()
if presentationData.theme.overallDarkAppearance { if presentationData.theme.overallDarkAppearance && !self.blurred {
self.panelNode.backgroundColor = presentationData.theme.rootController.tabBar.backgroundColor self.panelNode.backgroundColor = presentationData.theme.rootController.tabBar.backgroundColor
} else { } else {
self.panelNode.backgroundColor = .clear self.panelNode.backgroundColor = .clear