mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
6016ae3b98
commit
74faad7a03
@ -9738,6 +9738,7 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Story.PrivacyTooltipSelectedContacts.Contacts_1" = "1 contact";
|
||||
"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.GrayListSelected" = "[%@]() will never see any of your stories.";
|
||||
|
@ -894,7 +894,7 @@ public protocol SharedAccountContext: AnyObject {
|
||||
|
||||
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 makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, action: @escaping () -> Void) -> ViewController
|
||||
|
||||
|
@ -2634,7 +2634,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
location: .point(location, .top),
|
||||
shouldDismissOnTouch: { [weak self] point, containerFrame in
|
||||
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)
|
||||
return .dismiss(consume: true)
|
||||
} else {
|
||||
|
@ -1562,7 +1562,7 @@ public final class ChatListNode: ListView {
|
||||
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)
|
||||
}, openChatFolderUpdates: { [weak self] in
|
||||
guard let self else {
|
||||
|
@ -137,9 +137,11 @@ public final class PagerComponentPanelEnvironment<TopPanelEnvironment>: Equatabl
|
||||
|
||||
public struct PagerComponentPanelState {
|
||||
public var topPanelHeight: CGFloat
|
||||
public var scrollingPanelOffsetToTopEdge: CGFloat
|
||||
|
||||
public init(topPanelHeight: CGFloat) {
|
||||
public init(topPanelHeight: CGFloat, scrollingPanelOffsetToTopEdge: CGFloat) {
|
||||
self.topPanelHeight = topPanelHeight
|
||||
self.scrollingPanelOffsetToTopEdge = scrollingPanelOffsetToTopEdge
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,6 +207,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
public let contentIdUpdated: (AnyHashable) -> Void
|
||||
public let panelHideBehavior: PagerComponentPanelHideBehavior
|
||||
public let clipContentToTopPanel: Bool
|
||||
public let isExpanded: Bool
|
||||
|
||||
public init(
|
||||
isContentInFocus: Bool,
|
||||
@ -225,7 +228,8 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
isTopPanelHiddenUpdated: @escaping (Bool, Transition) -> Void,
|
||||
contentIdUpdated: @escaping (AnyHashable) -> Void,
|
||||
panelHideBehavior: PagerComponentPanelHideBehavior,
|
||||
clipContentToTopPanel: Bool
|
||||
clipContentToTopPanel: Bool,
|
||||
isExpanded: Bool
|
||||
) {
|
||||
self.isContentInFocus = isContentInFocus
|
||||
self.contentInsets = contentInsets
|
||||
@ -246,6 +250,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
self.contentIdUpdated = contentIdUpdated
|
||||
self.panelHideBehavior = panelHideBehavior
|
||||
self.clipContentToTopPanel = clipContentToTopPanel
|
||||
self.isExpanded = isExpanded
|
||||
}
|
||||
|
||||
public static func ==(lhs: PagerComponent, rhs: PagerComponent) -> Bool {
|
||||
@ -288,7 +293,9 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
if lhs.clipContentToTopPanel != rhs.clipContentToTopPanel {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.isExpanded != rhs.isExpanded {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -534,6 +541,11 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
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
|
||||
if let centralId = centralId, let index = component.contents.firstIndex(where: { $0.id == centralId }) {
|
||||
if let paneTransitionGestureState = self.paneTransitionGestureState {
|
||||
@ -745,7 +757,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
effectiveTopPanelHeight = 0.0
|
||||
case .show, .hideOnScroll:
|
||||
if component.externalTopPanelContainer != nil {
|
||||
effectiveTopPanelHeight = topPanelHeight
|
||||
effectiveTopPanelHeight = component.isExpanded ? 0.0 : topPanelHeight
|
||||
} else {
|
||||
effectiveTopPanelHeight = 0.0
|
||||
}
|
||||
@ -923,7 +935,8 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
if let panelStateUpdated = component.panelStateUpdated {
|
||||
panelStateUpdated(
|
||||
PagerComponentPanelState(
|
||||
topPanelHeight: topPanelHeight
|
||||
topPanelHeight: topPanelHeight,
|
||||
scrollingPanelOffsetToTopEdge: scrollingPanelOffsetToTopEdge
|
||||
),
|
||||
panelStateTransition
|
||||
)
|
||||
|
@ -22,7 +22,7 @@ final class ContextMenuActionNode: ASDisplayNode {
|
||||
|
||||
var dismiss: (() -> Void)?
|
||||
|
||||
init(action: ContextMenuAction) {
|
||||
init(action: ContextMenuAction, blurred: Bool) {
|
||||
self.actionArea = AccessibilityAreaNode()
|
||||
self.actionArea.accessibilityTraits = .button
|
||||
|
||||
@ -66,7 +66,10 @@ final class ContextMenuActionNode: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = UIColor(rgb: 0x2f2f2f)
|
||||
if !blurred {
|
||||
self.backgroundColor = UIColor(rgb: 0x2f2f2f)
|
||||
}
|
||||
|
||||
if let textNode = self.textNode {
|
||||
self.addSubnode(textNode)
|
||||
}
|
||||
@ -75,7 +78,11 @@ final class ContextMenuActionNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
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.addSubnode(self.actionArea)
|
||||
|
@ -20,16 +20,18 @@ public final class ContextMenuContainerNode: ASDisplayNode {
|
||||
|
||||
public var relativeArrowPosition: (CGFloat, Bool)?
|
||||
|
||||
//private let effectView: UIVisualEffectView
|
||||
private var effectView: UIVisualEffectView?
|
||||
|
||||
override public init() {
|
||||
//self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
|
||||
|
||||
public init(blurred: Bool) {
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = UIColor(rgb: 0x8c8e8e)
|
||||
//self.view.addSubview(self.effectView)
|
||||
//self.effectView.mask = self.maskView
|
||||
if blurred {
|
||||
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||
self.view.addSubview(effectView)
|
||||
self.effectView = effectView
|
||||
} else {
|
||||
self.backgroundColor = UIColor(rgb: 0x8c8e8e)
|
||||
}
|
||||
self.view.mask = self.maskView
|
||||
}
|
||||
|
||||
@ -46,7 +48,7 @@ public final class ContextMenuContainerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
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)
|
||||
if self.cachedMaskParams != maskParams {
|
||||
|
@ -27,16 +27,18 @@ public final class ContextMenuController: ViewController, KeyShortcutResponder,
|
||||
private let actions: [ContextMenuAction]
|
||||
private let catchTapsOutside: Bool
|
||||
private let hasHapticFeedback: Bool
|
||||
private let blurred: Bool
|
||||
|
||||
private var layout: ContainerViewLayout?
|
||||
|
||||
public var centerHorizontally = false
|
||||
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.catchTapsOutside = catchTapsOutside
|
||||
self.hasHapticFeedback = hasHapticFeedback
|
||||
self.blurred = blurred
|
||||
|
||||
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?.presentingViewController?.dismiss(animated: false)
|
||||
})
|
||||
}, catchTapsOutside: self.catchTapsOutside, hasHapticFeedback: self.hasHapticFeedback)
|
||||
}, catchTapsOutside: self.catchTapsOutside, hasHapticFeedback: self.hasHapticFeedback, blurred: self.blurred)
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
|
@ -145,16 +145,16 @@ final class ContextMenuNode: ASDisplayNode {
|
||||
|
||||
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.dismiss = dismiss
|
||||
self.catchTapsOutside = catchTapsOutside
|
||||
|
||||
self.containerNode = ContextMenuContainerNode()
|
||||
self.containerNode = ContextMenuContainerNode(blurred: blurred)
|
||||
self.scrollNode = ContextMenuContentScrollNode()
|
||||
|
||||
self.actionNodes = actions.map { action in
|
||||
return ContextMenuActionNode(action: action)
|
||||
return ContextMenuActionNode(action: action, blurred: blurred)
|
||||
}
|
||||
|
||||
if hasHapticFeedback {
|
||||
|
@ -33,7 +33,7 @@ final class TooltipControllerNode: ASDisplayNode {
|
||||
self.dismissByTapOutside = dismissByTapOutside
|
||||
self.dismissByTapOutsideSource = dismissByTapOutsideSource
|
||||
|
||||
self.containerNode = ContextMenuContainerNode()
|
||||
self.containerNode = ContextMenuContainerNode(blurred: false)
|
||||
self.containerNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
|
||||
|
||||
self.imageNode = ASImageNode()
|
||||
|
@ -3,6 +3,10 @@ import UIKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import StickerResources
|
||||
import MediaEditor
|
||||
|
||||
private func generateIcon(style: DrawingLocationEntity.Style) -> UIImage? {
|
||||
@ -50,6 +54,12 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
|
||||
|
||||
let textView: DrawingTextView
|
||||
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) {
|
||||
self.backgroundView = UIView()
|
||||
@ -79,6 +89,7 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
|
||||
self.textView.textContainer.lineBreakMode = .byTruncatingTail
|
||||
|
||||
self.iconView = UIImageView()
|
||||
self.imageNode = TransformImageNode()
|
||||
|
||||
super.init(context: context, entity: entity)
|
||||
|
||||
@ -89,6 +100,8 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
|
||||
self.addSubview(self.iconView)
|
||||
|
||||
self.update(animated: false)
|
||||
|
||||
self.setup()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -100,7 +113,14 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
|
||||
self.textView.setNeedsLayersUpdate()
|
||||
var result = self.textView.sizeThatFits(CGSize(width: self.locationEntity.width, height: .greatestFiniteMagnitude))
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
@ -117,9 +137,23 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let iconSize = min(76.0, floor(self.bounds.height * 0.6))
|
||||
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))
|
||||
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)
|
||||
let iconSize: CGFloat
|
||||
let iconOffset: CGFloat
|
||||
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.blurredBackgroundView.frame = self.bounds
|
||||
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)
|
||||
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
|
||||
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
@ -240,6 +274,52 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
|
||||
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() {
|
||||
guard let selectionView = self.selectionView as? DrawingLocationEntititySelectionView else {
|
||||
return
|
||||
@ -276,6 +356,34 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
|
||||
UIGraphicsEndImageContext()
|
||||
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 {
|
||||
|
@ -112,6 +112,8 @@ private final class StickerSelectionComponent: Component {
|
||||
private var searchVisible = false
|
||||
private var forceUpdate = false
|
||||
|
||||
private var topPanelScrollingOffset: CGFloat = 0.0
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.keyboardView = ComponentView<Empty>()
|
||||
self.keyboardClippingView = UIView()
|
||||
@ -253,7 +255,13 @@ private final class StickerSelectionComponent: Component {
|
||||
externalTopPanelContainer: self.panelHostView,
|
||||
externalBottomPanelContainer: nil,
|
||||
displayTopPanelBackground: .blur,
|
||||
topPanelExtensionUpdated: { _, _ in },
|
||||
topPanelExtensionUpdated: { _, _ in
|
||||
},
|
||||
topPanelScrollingOffset: { [weak self] offset, transition in
|
||||
if let self {
|
||||
self.topPanelScrollingOffset = offset
|
||||
}
|
||||
},
|
||||
hideInputUpdated: { [weak self] _, searchVisible, transition in
|
||||
guard let self else {
|
||||
return
|
||||
@ -263,7 +271,6 @@ private final class StickerSelectionComponent: Component {
|
||||
self.state?.updated(transition: transition)
|
||||
},
|
||||
hideTopPanelUpdated: { _, _ in
|
||||
print()
|
||||
},
|
||||
switchToTextInput: {},
|
||||
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)))
|
||||
self.panelBackgroundView.update(size: self.panelBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition)
|
||||
|
||||
transition.setAlpha(view: self.panelBackgroundView, alpha: self.searchVisible ? 0.0 : 1.0)
|
||||
transition.setAlpha(view: self.panelSeparatorView, alpha: self.searchVisible ? 0.0 : 1.0)
|
||||
let topPanelAlpha: CGFloat
|
||||
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)))
|
||||
}
|
||||
|
@ -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
|
||||
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)
|
||||
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
|
||||
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)
|
||||
return true
|
||||
}
|
||||
|
@ -3,10 +3,10 @@ import Contacts
|
||||
import CoreLocation
|
||||
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
|
||||
let geocoder = CLGeocoder()
|
||||
geocoder.geocodeAddressString(address) { (placemarks, _) in
|
||||
geocoder.geocodeAddressString(address, in: nil, preferredLocale: locale) { placemarks, _ in
|
||||
subscriber.putNext(placemarks)
|
||||
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
|
||||
let geocoder = CLGeocoder()
|
||||
geocoder.geocodePostalAddress(address, completionHandler: { placemarks, _ in
|
||||
geocoder.geocodePostalAddress(address, preferredLocale: locale) { placemarks, _ in
|
||||
if let location = placemarks?.first?.location {
|
||||
subscriber.putNext((location.coordinate.latitude, location.coordinate.longitude))
|
||||
} else {
|
||||
subscriber.putNext(nil)
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
}
|
||||
return ActionDisposable {
|
||||
geocoder.cancelGeocode()
|
||||
}
|
||||
@ -34,9 +34,11 @@ public func geocodeLocation(address: CNPostalAddress) -> Signal<(Double, Double)
|
||||
}
|
||||
|
||||
public struct ReverseGeocodedPlacemark {
|
||||
public let name: String?
|
||||
public let street: String?
|
||||
public let city: String?
|
||||
public let country: String?
|
||||
public let countryCode: String?
|
||||
|
||||
public var compactDisplayAddress: String? {
|
||||
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
|
||||
let geocoder = CLGeocoder()
|
||||
let locale = Locale(identifier: "en-US")
|
||||
geocoder.reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude), preferredLocale: locale, completionHandler: { placemarks, _ in
|
||||
if let placemarks = placemarks, let placemark = placemarks.first {
|
||||
let result: ReverseGeocodedPlacemark
|
||||
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 {
|
||||
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.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
|
||||
}
|
||||
}
|
||||
|
@ -171,6 +171,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
||||
var isDragging = false
|
||||
var beganInteractiveDragging: (() -> Void)?
|
||||
var endedInteractiveDragging: ((CLLocationCoordinate2D) -> Void)?
|
||||
var disableHorizontalTransitionGesture = false
|
||||
|
||||
var annotationSelected: ((LocationPinAnnotation?) -> Void)?
|
||||
var userLocationAnnotationSelected: (() -> Void)?
|
||||
@ -249,6 +250,11 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
self.mapView?.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
|
||||
return self?.disableHorizontalTransitionGesture == true
|
||||
}
|
||||
|
||||
self.mapView?.delegate = self
|
||||
self.mapView?.mapType = self.mapMode.mapType
|
||||
self.mapView?.isRotateEnabled = self.isRotateEnabled
|
||||
|
@ -9,15 +9,14 @@ import SegmentedControlNode
|
||||
final class LocationOptionsNode: ASDisplayNode {
|
||||
private var presentationData: PresentationData
|
||||
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let segmentedControlNode: SegmentedControlNode
|
||||
|
||||
init(presentationData: PresentationData, updateMapMode: @escaping (LocationMapMode) -> Void) {
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.opaqueBackgroundColor
|
||||
self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor)
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
|
||||
|
||||
@ -45,13 +44,15 @@ final class LocationOptionsNode: ASDisplayNode {
|
||||
|
||||
func updatePresentationData(_ 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.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.presentationData.theme))
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
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))
|
||||
|
||||
let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: size.width - 16.0 - leftInset - rightInset), transition: .immediate)
|
||||
|
@ -18,7 +18,7 @@ public enum LocationPickerMode {
|
||||
}
|
||||
|
||||
class LocationPickerInteraction {
|
||||
let sendLocation: (CLLocationCoordinate2D, String?) -> Void
|
||||
let sendLocation: (CLLocationCoordinate2D, String?, String?) -> Void
|
||||
let sendLiveLocation: (CLLocationCoordinate2D) -> Void
|
||||
let sendVenue: (TelegramMediaMap, Int64?, String?) -> Void
|
||||
let toggleMapModeSelection: () -> Void
|
||||
@ -33,7 +33,7 @@ class LocationPickerInteraction {
|
||||
let openHomeWorkInfo: () -> 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.sendLiveLocation = sendLiveLocation
|
||||
self.sendVenue = sendVenue
|
||||
@ -65,7 +65,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab
|
||||
private let mode: LocationPickerMode
|
||||
private let source: Source
|
||||
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 presentationDataDisposable: Disposable?
|
||||
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 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.mode = mode
|
||||
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)
|
||||
}
|
||||
|
||||
self.interaction = LocationPickerInteraction(sendLocation: { [weak self] coordinate, name in
|
||||
self.interaction = LocationPickerInteraction(sendLocation: { [weak self] coordinate, name, countryCode in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.completion(locationWithTimeout(coordinate, nil), nil, nil, name)
|
||||
strongSelf.completion(locationWithTimeout(coordinate, nil), nil, nil, name, countryCode)
|
||||
strongSelf.dismiss()
|
||||
}, sendLiveLocation: { [weak self] coordinate in
|
||||
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
|
||||
controller?.dismissAnimated()
|
||||
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()
|
||||
}
|
||||
}),
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor1Hour, color: .accent, action: { [weak self, weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
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()
|
||||
}
|
||||
}),
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor8Hours, color: .accent, action: { [weak self, weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
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()
|
||||
}
|
||||
})
|
||||
@ -185,9 +185,9 @@ public final class LocationPickerController: ViewController, AttachmentContainab
|
||||
}
|
||||
let venueType = venue.venue?.type ?? ""
|
||||
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 {
|
||||
completion(venue, queryId, resultId, nil)
|
||||
completion(venue, queryId, resultId, nil, nil)
|
||||
}
|
||||
strongSelf.dismiss()
|
||||
}, toggleMapModeSelection: { [weak self] in
|
||||
@ -412,7 +412,7 @@ private final class LocationPickerContext: AttachmentMediaPickerContext {
|
||||
public func storyLocationPickerController(
|
||||
context: AccountContext,
|
||||
location: CLLocationCoordinate2D?,
|
||||
completion: @escaping (TelegramMediaMap, Int64?, String?, String?) -> Void
|
||||
completion: @escaping (TelegramMediaMap, Int64?, String?, String?, String?) -> Void
|
||||
) -> ViewController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
|
||||
@ -420,8 +420,8 @@ public func storyLocationPickerController(
|
||||
return nil
|
||||
})
|
||||
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
|
||||
completion(location, queryId, resultId, address)
|
||||
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, countryCode)
|
||||
})
|
||||
present(locationPickerController, locationPickerController.mediaPickerContext)
|
||||
}
|
||||
|
@ -36,8 +36,8 @@ private enum LocationPickerEntryId: Hashable {
|
||||
}
|
||||
|
||||
private enum LocationPickerEntry: Comparable, Identifiable {
|
||||
case city(PresentationTheme, String, String, TelegramMediaMap?, Int64?, String?, CLLocationCoordinate2D?, String?)
|
||||
case location(PresentationTheme, String, String, TelegramMediaMap?, Int64?, String?, CLLocationCoordinate2D?, String?, Bool)
|
||||
case city(PresentationTheme, String, String, TelegramMediaMap?, Int64?, String?, CLLocationCoordinate2D?, String?, String?)
|
||||
case location(PresentationTheme, String, String, TelegramMediaMap?, Int64?, String?, CLLocationCoordinate2D?, String?, String?, Bool)
|
||||
case liveLocation(PresentationTheme, String, String, CLLocationCoordinate2D?)
|
||||
case header(PresentationTheme, String)
|
||||
case venue(PresentationTheme, TelegramMediaMap?, Int64?, String?, Int)
|
||||
@ -62,14 +62,14 @@ private enum LocationPickerEntry: Comparable, Identifiable {
|
||||
|
||||
static func ==(lhs: LocationPickerEntry, rhs: LocationPickerEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .city(lhsTheme, lhsTitle, lhsSubtitle, lhsVenue, lhsQueryId, lhsResultId, lhsCoordinate, lhsName):
|
||||
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 {
|
||||
case let .city(lhsTheme, lhsTitle, lhsSubtitle, lhsVenue, lhsQueryId, lhsResultId, lhsCoordinate, lhsName, lhsCountryCode):
|
||||
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
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .location(lhsTheme, lhsTitle, lhsSubtitle, lhsVenue, lhsQueryId, lhsResultId, lhsCoordinate, lhsName, 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 {
|
||||
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, 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
|
||||
} else {
|
||||
return false
|
||||
@ -147,7 +147,7 @@ private enum LocationPickerEntry: Comparable, Identifiable {
|
||||
|
||||
func item(engine: TelegramEngine, presentationData: PresentationData, interaction: LocationPickerInteraction?) -> ListViewItem {
|
||||
switch self {
|
||||
case let .city(_, title, subtitle, _, _, _, coordinate, name):
|
||||
case let .city(_, title, subtitle, _, _, _, coordinate, name, countryCode):
|
||||
let icon: LocationActionListItemIcon
|
||||
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))
|
||||
@ -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: {
|
||||
if let coordinate = coordinate {
|
||||
interaction?.sendLocation(coordinate, name)
|
||||
interaction?.sendLocation(coordinate, name, countryCode)
|
||||
}
|
||||
}, highlighted: { highlighted in
|
||||
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
|
||||
if let venue = venue {
|
||||
icon = .venue(venue)
|
||||
@ -172,7 +172,7 @@ private enum LocationPickerEntry: Comparable, Identifiable {
|
||||
if let venue = venue {
|
||||
interaction?.sendVenue(venue, queryId, resultId)
|
||||
} else if let coordinate = coordinate {
|
||||
interaction?.sendLocation(coordinate, name)
|
||||
interaction?.sendLocation(coordinate, name, countryCode)
|
||||
}
|
||||
}, highlighted: { highlighted in
|
||||
if isTop {
|
||||
@ -262,6 +262,8 @@ struct LocationPickerState {
|
||||
var selectedLocation: LocationPickerLocation
|
||||
var city: String?
|
||||
var street: String?
|
||||
var countryCode: String?
|
||||
var isStreet: Bool
|
||||
var forceSelection: Bool
|
||||
var searchingVenuesAround: Bool
|
||||
|
||||
@ -271,6 +273,7 @@ struct LocationPickerState {
|
||||
self.selectedLocation = .none
|
||||
self.city = nil
|
||||
self.street = nil
|
||||
self.isStreet = false
|
||||
self.forceSelection = false
|
||||
self.searchingVenuesAround = false
|
||||
}
|
||||
@ -587,7 +590,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
case .pick:
|
||||
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:
|
||||
let title: String
|
||||
switch strongSelf.mode {
|
||||
@ -600,7 +603,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
case .pick:
|
||||
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):
|
||||
let title: String
|
||||
switch strongSelf.mode {
|
||||
@ -609,7 +612,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
case .pick:
|
||||
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:
|
||||
let title: String
|
||||
var coordinate = userLocation?.coordinate
|
||||
@ -630,13 +633,13 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
}
|
||||
if source == .story {
|
||||
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 != "" {
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
if let strongSelf = self {
|
||||
var address = placemark?.fullAddress ?? ""
|
||||
@ -785,8 +789,9 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
}
|
||||
var cityName: String?
|
||||
var streetName: String?
|
||||
if let city = placemark?.city, let country = placemark?.country {
|
||||
cityName = "\(city), \(country)"
|
||||
let countryCode = placemark?.countryCode
|
||||
if let city = placemark?.city, let countryCode = placemark?.countryCode {
|
||||
cityName = "\(city), \(displayCountryName(countryCode, locale: locale))"
|
||||
} else {
|
||||
cityName = ""
|
||||
}
|
||||
@ -796,6 +801,10 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
} else {
|
||||
streetName = street
|
||||
}
|
||||
} else if let name = placemark?.name {
|
||||
streetName = name
|
||||
} else if let country = placemark?.country, cityName == "" {
|
||||
streetName = country
|
||||
} else {
|
||||
streetName = ""
|
||||
}
|
||||
@ -807,6 +816,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
state.selectedLocation = .location(coordinate, address)
|
||||
state.city = cityName
|
||||
state.street = streetName
|
||||
state.countryCode = countryCode
|
||||
state.isStreet = placemark?.street != nil
|
||||
return state
|
||||
}
|
||||
}
|
||||
@ -814,7 +825,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
} else {
|
||||
let coordinate = controller.initialLocation ?? userLocation?.coordinate
|
||||
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
|
||||
if let strongSelf = self {
|
||||
var address = placemark?.fullAddress ?? ""
|
||||
@ -823,8 +834,9 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
}
|
||||
var cityName: String?
|
||||
var streetName: String?
|
||||
if let city = placemark?.city, let country = placemark?.country {
|
||||
cityName = "\(city), \(country)"
|
||||
let countryCode = placemark?.countryCode
|
||||
if let city = placemark?.city, let countryCode = placemark?.countryCode {
|
||||
cityName = "\(city), \(displayCountryName(countryCode, locale: locale))"
|
||||
} else {
|
||||
cityName = ""
|
||||
}
|
||||
@ -834,6 +846,10 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
} else {
|
||||
streetName = street
|
||||
}
|
||||
} else if let name = placemark?.name {
|
||||
streetName = name
|
||||
} else if let country = placemark?.country, cityName == "" {
|
||||
streetName = country
|
||||
} else {
|
||||
streetName = ""
|
||||
}
|
||||
@ -844,6 +860,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
var state = state
|
||||
state.city = cityName
|
||||
state.street = streetName
|
||||
state.countryCode = countryCode
|
||||
state.isStreet = placemark?.street != nil
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
@ -173,12 +173,13 @@ final class LocationSearchContainerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|> 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)
|
||||
|> afterCompleted {
|
||||
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())
|
||||
|> delay(0.1, queue: Queue.concurrentDefaultQueue())
|
||||
|> beforeStarted {
|
||||
@ -219,7 +220,7 @@ final class LocationSearchContainerNode: ASDisplayNode {
|
||||
} else {
|
||||
return .single(nil)
|
||||
|> afterCompleted {
|
||||
isSearching.set(true)
|
||||
isSearching.set(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ public final class LocationViewController: ViewController {
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
private var showAll: Bool
|
||||
private let disableDismissGesture: Bool
|
||||
|
||||
private let locationManager = LocationManager()
|
||||
private var permissionDisposable: Disposable?
|
||||
@ -87,10 +88,11 @@ public final class LocationViewController: ViewController {
|
||||
|
||||
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.subject = subject
|
||||
self.showAll = params.showAll
|
||||
self.disableDismissGesture = disableDismissGesture
|
||||
|
||||
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
@ -498,6 +500,8 @@ public final class LocationViewController: ViewController {
|
||||
}
|
||||
strongSelf.controllerNode.showAll()
|
||||
}
|
||||
|
||||
self.controllerNode.headerNode.mapNode.disableHorizontalTransitionGesture = self.disableDismissGesture
|
||||
}
|
||||
|
||||
private func updateRightBarButton() {
|
||||
|
@ -208,7 +208,7 @@ public func archiveSettingsController(context: AccountContext) -> ViewController
|
||||
guard let controller else {
|
||||
return
|
||||
}
|
||||
let premiumController = context.sharedContext.makePremiumIntroController(context: context, source: .settings)
|
||||
let premiumController = context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false)
|
||||
controller.push(premiumController)
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
|
||||
self.containerNode = ASDisplayNode()
|
||||
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.textNode = ImmediateTextNode()
|
||||
|
@ -194,6 +194,11 @@ final class PendingStoryManager {
|
||||
|
||||
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 var allStoriesUploadProgressValue: Float? = nil
|
||||
var allStoriesUploadProgress: Signal<Float?, NoError> {
|
||||
@ -272,6 +277,10 @@ final class PendingStoryManager {
|
||||
private func update(localState: Stories.LocalState) {
|
||||
if let currentPendingItemContext = self.currentPendingItemContext, !localState.items.contains(where: { $0.randomId == currentPendingItemContext.item.randomId }) {
|
||||
self.currentPendingItemContext = nil
|
||||
self.queue.after(0.1, {
|
||||
let _ = currentPendingItemContext
|
||||
print(currentPendingItemContext)
|
||||
})
|
||||
}
|
||||
|
||||
if self.currentPendingItemContext == nil, let firstItem = localState.items.first {
|
||||
@ -304,7 +313,10 @@ final class PendingStoryManager {
|
||||
currentPendingItemContext.progress = progress
|
||||
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
|
||||
break
|
||||
}
|
||||
@ -363,6 +375,12 @@ final class PendingStoryManager {
|
||||
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) {
|
||||
let queue = Queue.mainQueue()
|
||||
|
@ -796,10 +796,10 @@ private func apiInputPrivacyRules(privacy: EngineStoryPrivacy, transaction: Tran
|
||||
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 _ = (account.postbox.transaction { transaction in
|
||||
return (account.postbox.transaction { transaction in
|
||||
var currentState: Stories.LocalState
|
||||
if let value = transaction.getLocalStoryState()?.get(Stories.LocalState.self) {
|
||||
currentState = value
|
||||
@ -825,7 +825,8 @@ func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, media
|
||||
randomId: randomId
|
||||
))
|
||||
transaction.setLocalStoryState(state: CodableEntry(currentState))
|
||||
}).start()
|
||||
return stableId
|
||||
})
|
||||
}
|
||||
|
||||
func _internal_cancelStoryUpload(account: Account, stableId: Int32) {
|
||||
|
@ -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) {
|
||||
_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 uploadStory(media: EngineStoryInputMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) -> Signal<Int32, NoError> {
|
||||
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? {
|
||||
|
@ -72,7 +72,12 @@ public final class BlockedPeersContext {
|
||||
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
|
||||
|> mapToSignal { result -> Signal<(peers: [RenderedPeer], canLoadMore: Bool, totalCount: Int?), NoError> 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> {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
let validIds = Set(peerIds)
|
||||
var peersToRemove: [EnginePeer.Id] = []
|
||||
for peer in self._state.peers {
|
||||
if !validIds.contains(peer.peerId) {
|
||||
peersToRemove.append(peer.peerId)
|
||||
let network = self.account.network
|
||||
let subject = self.subject
|
||||
let currentPeers = self._state.peers
|
||||
|
||||
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>] = []
|
||||
for peerId in peersToRemove {
|
||||
updateSignals.append(self.remove(peerId: peerId) |> mapError { _ in .generic })
|
||||
}
|
||||
for peerId in peerIds {
|
||||
updateSignals.append(self.add(peerId: peerId))
|
||||
}
|
||||
return combineLatest(updateSignals)
|
||||
|> mapToSignal { _ in
|
||||
return .never()
|
||||
|> castError(BlockedPeersContextAddError.self)
|
||||
|> mapToSignal { peers -> Signal<Never, BlockedPeersContextAddError> in
|
||||
let inputPeers = peers.compactMap { apiInputPeer($0) }
|
||||
return network.request(Api.functions.contacts.setBlocked(flags: flags, id: inputPeers, limit: Int32(peers.count)))
|
||||
|> mapError { _ -> BlockedPeersContextAddError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Never, BlockedPeersContextAddError> in
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,7 +265,6 @@ public final class BlockedPeersContext {
|
||||
|> castError(BlockedPeersContextAddError.self)
|
||||
}
|
||||
|> deliverOnMainQueue
|
||||
|
||||
|> mapToSignal { peer -> Signal<Never, BlockedPeersContextAddError> in
|
||||
guard let strongSelf = self, let peer = peer else {
|
||||
return .complete()
|
||||
|
@ -1251,6 +1251,7 @@ final class AvatarEditorScreenComponent: Component {
|
||||
externalBottomPanelContainer: nil,
|
||||
displayTopPanelBackground: .blur,
|
||||
topPanelExtensionUpdated: { _, _ in },
|
||||
topPanelScrollingOffset: { _, _ in },
|
||||
hideInputUpdated: { _, _, _ in },
|
||||
hideTopPanelUpdated: { [weak self] hideTopPanel, transition in
|
||||
if let strongSelf = self {
|
||||
|
@ -1942,6 +1942,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
strongSelf.topBackgroundExtensionUpdated?(transition.containedViewLayoutTransition)
|
||||
}
|
||||
},
|
||||
topPanelScrollingOffset: { _, _ in },
|
||||
hideInputUpdated: { [weak self] hideInput, adjustLayout, transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
@ -174,6 +174,7 @@ public final class EmojiStatusSelectionComponent: Component {
|
||||
externalBottomPanelContainer: nil,
|
||||
displayTopPanelBackground: .blur,
|
||||
topPanelExtensionUpdated: { _, _ in },
|
||||
topPanelScrollingOffset: { _, _ in },
|
||||
hideInputUpdated: { _, _, _ in },
|
||||
hideTopPanelUpdated: { [weak self] hideTopPanel, transition in
|
||||
guard let strongSelf = self else {
|
||||
|
@ -6509,8 +6509,44 @@ public final class EmojiPagerContentComponent: Component {
|
||||
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
|
||||
if let customContentView = component.inputInteractionHolder.inputInteraction?.customContentView {
|
||||
if let customContentView = component.inputInteractionHolder.inputInteraction?.customContentView, !self.isSearchActivated {
|
||||
var customContentViewTransition = transition
|
||||
if let _ = self.visibleCustomContentView {
|
||||
|
||||
@ -6519,6 +6555,11 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.visibleCustomContentView = customContentView
|
||||
self.scrollView.addSubview(customContentView)
|
||||
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 customContentViewSize = customContentView.update(theme: keyboardChildEnvironment.theme, useOpaqueTheme: useOpaqueTheme, availableSize: availableCustomContentSize, transition: customContentViewTransition)
|
||||
@ -6528,8 +6569,17 @@ public final class EmojiPagerContentComponent: Component {
|
||||
} else {
|
||||
if let visibleCustomContentView = self.visibleCustomContentView {
|
||||
self.visibleCustomContentView = nil
|
||||
visibleCustomContentView.removeFromSuperview()
|
||||
visibleCustomContentView.tintContainerView.removeFromSuperview()
|
||||
if animateContentCrossfade {
|
||||
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(
|
||||
layoutType: component.itemLayoutType,
|
||||
width: availableSize.width,
|
||||
@ -6562,18 +6610,6 @@ public final class EmojiPagerContentComponent: Component {
|
||||
customLayout: component.inputInteractionHolder.inputInteraction?.customLayout
|
||||
)
|
||||
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.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))
|
||||
|
||||
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)
|
||||
|
||||
if resetScrolling {
|
||||
itemTransition = .immediate
|
||||
}
|
||||
|
||||
|
||||
let warpHeight: CGFloat = 50.0
|
||||
var topWarpInset = pagerEnvironment.containerInsets.top
|
||||
if self.isSearchActivated {
|
||||
@ -6898,14 +6919,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
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
|
||||
|
||||
if animateContentCrossfade {
|
||||
|
@ -501,6 +501,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
|
||||
externalBottomPanelContainer: nil,
|
||||
displayTopPanelBackground: .blur,
|
||||
topPanelExtensionUpdated: { _, _ in },
|
||||
topPanelScrollingOffset: { _, _ in },
|
||||
hideInputUpdated: { _, _, _ in },
|
||||
hideTopPanelUpdated: { _, _ in
|
||||
},
|
||||
|
@ -108,6 +108,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
public let externalBottomPanelContainer: PagerExternalTopPanelContainer?
|
||||
public let displayTopPanelBackground: DisplayTopPanelBackground
|
||||
public let topPanelExtensionUpdated: (CGFloat, Transition) -> Void
|
||||
public let topPanelScrollingOffset: (CGFloat, Transition) -> Void
|
||||
public let hideInputUpdated: (Bool, Bool, Transition) -> Void
|
||||
public let hideTopPanelUpdated: (Bool, Transition) -> Void
|
||||
public let switchToTextInput: () -> Void
|
||||
@ -141,6 +142,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
externalBottomPanelContainer: PagerExternalTopPanelContainer?,
|
||||
displayTopPanelBackground: DisplayTopPanelBackground,
|
||||
topPanelExtensionUpdated: @escaping (CGFloat, Transition) -> Void,
|
||||
topPanelScrollingOffset: @escaping (CGFloat, Transition) -> Void,
|
||||
hideInputUpdated: @escaping (Bool, Bool, Transition) -> Void,
|
||||
hideTopPanelUpdated: @escaping (Bool, Transition) -> Void,
|
||||
switchToTextInput: @escaping () -> Void,
|
||||
@ -173,6 +175,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
self.externalBottomPanelContainer = externalBottomPanelContainer
|
||||
self.displayTopPanelBackground = displayTopPanelBackground
|
||||
self.topPanelExtensionUpdated = topPanelExtensionUpdated
|
||||
self.topPanelScrollingOffset = topPanelScrollingOffset
|
||||
self.hideInputUpdated = hideInputUpdated
|
||||
self.hideTopPanelUpdated = hideTopPanelUpdated
|
||||
self.switchToTextInput = switchToTextInput
|
||||
@ -272,6 +275,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
private var topPanelExtension: CGFloat?
|
||||
private var isTopPanelExpanded: Bool = false
|
||||
private var isTopPanelHidden: Bool = false
|
||||
private var topPanelScrollingOffset: CGFloat?
|
||||
|
||||
public var centralId: AnyHashable? {
|
||||
if let pagerView = self.pagerView.findTaggedView(tag: PagerComponentViewTag()) as? PagerComponent<EntityKeyboardChildEnvironment, EntityKeyboardTopContainerPanelEnvironment>.View {
|
||||
@ -776,6 +780,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
return
|
||||
}
|
||||
strongSelf.topPanelExtensionUpdated(height: panelState.topPanelHeight, transition: transition)
|
||||
strongSelf.topPanelScrollingOffset(offset: panelState.scrollingPanelOffsetToTopEdge, transition: transition)
|
||||
},
|
||||
isTopPanelExpandedUpdated: { [weak self] isExpanded, transition in
|
||||
guard let strongSelf = self else {
|
||||
@ -796,7 +801,8 @@ public final class EntityKeyboardComponent: Component {
|
||||
component.contentIdUpdated(id)
|
||||
},
|
||||
panelHideBehavior: panelHideBehavior,
|
||||
clipContentToTopPanel: component.clipContentToTopPanel
|
||||
clipContentToTopPanel: component.clipContentToTopPanel,
|
||||
isExpanded: component.isExpanded
|
||||
)),
|
||||
environment: {
|
||||
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) {
|
||||
if self.isTopPanelExpanded != isExpanded {
|
||||
self.isTopPanelExpanded = isExpanded
|
||||
|
@ -352,6 +352,7 @@ private final class TopicIconSelectionComponent: Component {
|
||||
externalBottomPanelContainer: nil,
|
||||
displayTopPanelBackground: .blur,
|
||||
topPanelExtensionUpdated: { _, _ in },
|
||||
topPanelScrollingOffset: { _, _ in },
|
||||
hideInputUpdated: { _, _, _ in },
|
||||
hideTopPanelUpdated: { _, _ in },
|
||||
switchToTextInput: {},
|
||||
|
@ -12,6 +12,7 @@ public final class DrawingLocationEntity: DrawingEntity, Codable {
|
||||
case title
|
||||
case style
|
||||
case location
|
||||
case icon
|
||||
case queryId
|
||||
case resultId
|
||||
case referenceDrawingSize
|
||||
@ -38,6 +39,7 @@ public final class DrawingLocationEntity: DrawingEntity, Codable {
|
||||
public var title: String
|
||||
public var style: Style
|
||||
public var location: TelegramMediaMap
|
||||
public var icon: TelegramMediaFile?
|
||||
public var queryId: Int64?
|
||||
public var resultId: String?
|
||||
public var color: DrawingColor = .clear
|
||||
@ -64,12 +66,13 @@ public final class DrawingLocationEntity: DrawingEntity, Codable {
|
||||
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.title = title
|
||||
self.style = style
|
||||
self.location = location
|
||||
self.icon = icon
|
||||
self.queryId = queryId
|
||||
self.resultId = resultId
|
||||
|
||||
@ -92,6 +95,10 @@ public final class DrawingLocationEntity: DrawingEntity, Codable {
|
||||
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.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.style, forKey: .style)
|
||||
|
||||
let encoder = PostboxEncoder()
|
||||
var encoder = PostboxEncoder()
|
||||
encoder.encodeRootObject(self.location)
|
||||
let locationData = encoder.makeData()
|
||||
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.resultId, forKey: .resultId)
|
||||
|
||||
@ -130,7 +144,7 @@ public final class DrawingLocationEntity: DrawingEntity, Codable {
|
||||
}
|
||||
|
||||
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.position = self.position
|
||||
newEntity.width = self.width
|
||||
|
@ -2707,38 +2707,97 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
controller.push(galleryController)
|
||||
}
|
||||
|
||||
private let staticEmojiPack = Promise<LoadedStickerPack>()
|
||||
private var didSetupStaticEmojiPack = false
|
||||
|
||||
func presentLocationPicker(_ existingEntity: DrawingLocationEntity? = nil) {
|
||||
guard let controller = self.controller else {
|
||||
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?
|
||||
if let subject = self.subject, case let .asset(asset) = subject {
|
||||
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 {
|
||||
let title: String
|
||||
if let venueTitle = location.venue?.title {
|
||||
title = venueTitle
|
||||
let emojiFile: Signal<TelegramMediaFile?, NoError>
|
||||
if let countryCode {
|
||||
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 {
|
||||
title = address ?? "Location"
|
||||
emojiFile = .single(nil)
|
||||
}
|
||||
let position = existingEntity?.position
|
||||
let scale = existingEntity?.scale ?? 1.0
|
||||
if let existingEntity {
|
||||
self.entitiesView.remove(uuid: existingEntity.uuid, animated: true)
|
||||
}
|
||||
self.interaction?.insertEntity(
|
||||
DrawingLocationEntity(
|
||||
title: title,
|
||||
style: existingEntity?.style ?? .white,
|
||||
location: location,
|
||||
queryId: queryId,
|
||||
resultId: resultId
|
||||
),
|
||||
scale: scale,
|
||||
position: position
|
||||
)
|
||||
|
||||
let _ = emojiFile.start(next: { [weak self] emojiFile in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let title: String
|
||||
if let venueTitle = location.venue?.title {
|
||||
title = venueTitle
|
||||
} else {
|
||||
title = address ?? "Location"
|
||||
}
|
||||
let position = existingEntity?.position
|
||||
let scale = existingEntity?.scale ?? 1.0
|
||||
if let existingEntity {
|
||||
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
|
||||
@ -2915,11 +2974,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
}
|
||||
controller.presentGallery = { [weak self] in
|
||||
if let self {
|
||||
self.stickerScreen = nil
|
||||
self.presentGallery()
|
||||
}
|
||||
}
|
||||
controller.presentLocationPicker = { [weak self, weak controller] in
|
||||
if let self {
|
||||
self.stickerScreen = nil
|
||||
controller?.dismiss(animated: true)
|
||||
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
|
||||
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)
|
||||
}
|
||||
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
|
||||
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)
|
||||
}
|
||||
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
|
||||
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)
|
||||
}
|
||||
return false }
|
||||
@ -3668,7 +3729,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
if saveDraft {
|
||||
self.saveDraft(id: nil)
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
try? data.write(to: URL(fileURLWithPath: draft.fullPath()))
|
||||
if let id {
|
||||
saveStorySource(engine: context.engine, item: draft, id: id)
|
||||
saveStorySource(engine: context.engine, item: draft, peerId: context.account.peerId, id: id)
|
||||
} else {
|
||||
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)
|
||||
try? FileManager.default.moveItem(atPath: videoPath, toPath: draft.fullPath())
|
||||
if let id {
|
||||
saveStorySource(engine: context.engine, item: draft, id: id)
|
||||
saveStorySource(engine: context.engine, item: draft, peerId: context.account.peerId, id: id)
|
||||
} else {
|
||||
addStoryDraft(engine: context.engine, item: draft)
|
||||
}
|
||||
@ -3811,7 +3872,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
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] = []
|
||||
for entity in codableEntities {
|
||||
switch entity {
|
||||
|
@ -4,35 +4,40 @@ import Postbox
|
||||
import TelegramCore
|
||||
import TelegramUIPreferences
|
||||
import MediaEditor
|
||||
import AccountContext
|
||||
|
||||
|
||||
public func saveStorySource(engine: TelegramEngine, item: MediaEditorDraft, id: Int64) {
|
||||
let key = EngineDataBuffer(length: 8)
|
||||
key.setInt64(0, value: id)
|
||||
public func saveStorySource(engine: TelegramEngine, item: MediaEditorDraft, peerId: EnginePeer.Id, id: Int64) {
|
||||
let key = EngineDataBuffer(length: 16)
|
||||
key.setInt64(0, value: peerId.toInt64())
|
||||
key.setInt64(8, value: id)
|
||||
let _ = engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: key, item: item).start()
|
||||
}
|
||||
|
||||
public func removeStorySource(engine: TelegramEngine, id: Int64) {
|
||||
let key = EngineDataBuffer(length: 8)
|
||||
key.setInt64(0, value: id)
|
||||
public func removeStorySource(engine: TelegramEngine, peerId: EnginePeer.Id, id: Int64) {
|
||||
let key = EngineDataBuffer(length: 16)
|
||||
key.setInt64(0, value: peerId.toInt64())
|
||||
key.setInt64(8, value: id)
|
||||
let _ = engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: key).start()
|
||||
}
|
||||
|
||||
public func getStorySource(engine: TelegramEngine, id: Int64) -> Signal<MediaEditorDraft?, NoError> {
|
||||
let key = EngineDataBuffer(length: 8)
|
||||
key.setInt64(0, value: id)
|
||||
public func getStorySource(engine: TelegramEngine, peerId: EnginePeer.Id, id: Int64) -> Signal<MediaEditorDraft?, NoError> {
|
||||
let key = EngineDataBuffer(length: 16)
|
||||
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))
|
||||
|> map { result -> MediaEditorDraft? in
|
||||
return result?.get(MediaEditorDraft.self)
|
||||
}
|
||||
}
|
||||
|
||||
public func moveStorySource(engine: TelegramEngine, from fromId: Int64, to toId: Int64) {
|
||||
let fromKey = EngineDataBuffer(length: 8)
|
||||
fromKey.setInt64(0, value: fromId)
|
||||
public func moveStorySource(engine: TelegramEngine, peerId: EnginePeer.Id, from fromId: Int64, to toId: Int64) {
|
||||
let fromKey = EngineDataBuffer(length: 16)
|
||||
fromKey.setInt64(0, value: peerId.toInt64())
|
||||
fromKey.setInt64(8, value: fromId)
|
||||
|
||||
let toKey = EngineDataBuffer(length: 8)
|
||||
toKey.setInt64(0, value: toId)
|
||||
let toKey = EngineDataBuffer(length: 16)
|
||||
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))
|
||||
|> mapToSignal { item -> Signal<Never, NoError> in
|
||||
|
@ -277,10 +277,8 @@ final class StickersResultPanelComponent: Component {
|
||||
guard let self, let component = self.component else {
|
||||
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)
|
||||
// let controller = PremiumIntroScreen(context: component.context, source: .stickers)
|
||||
// controllerInteraction.navigationController()?.pushViewController(controller)
|
||||
}))
|
||||
} else {
|
||||
return nil
|
||||
|
@ -123,12 +123,26 @@ final class SectionHeaderComponent: Component {
|
||||
if let view = self.action.view {
|
||||
if view.superview == nil {
|
||||
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)
|
||||
view.frame = actionFrame
|
||||
}
|
||||
} else if self.action.view?.superview != nil {
|
||||
self.action.view?.removeFromSuperview()
|
||||
} else if let view = self.action.view, view.superview != nil {
|
||||
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)
|
||||
|
@ -313,6 +313,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
private var savedSelectedPeers: [EnginePeer.Id] = []
|
||||
private var selectedPeers: [EnginePeer.Id] = []
|
||||
private var selectedGroups: [EnginePeer.Id] = []
|
||||
private var groupPeersMap: [EnginePeer.Id: [EnginePeer.Id]] = [:]
|
||||
|
||||
private var peersMap: [EnginePeer.Id: EnginePeer] = [:]
|
||||
|
||||
@ -635,8 +636,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
}
|
||||
|
||||
progressDisposable.dispose()
|
||||
|
||||
|
||||
|
||||
var peerIds = Set<EnginePeer.Id>()
|
||||
for peer in peers {
|
||||
self.peersMap[peer.id] = peer
|
||||
@ -651,12 +651,15 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
showCountLimitAlert()
|
||||
return
|
||||
}
|
||||
var groupPeerIds: [EnginePeer.Id] = []
|
||||
for peer in peers {
|
||||
if !existingPeerIds.contains(peer.id) {
|
||||
self.selectedPeers.append(peer.id)
|
||||
existingPeerIds.insert(peer.id)
|
||||
}
|
||||
groupPeerIds.append(peer.id)
|
||||
}
|
||||
self.groupPeersMap[peer.id] = groupPeerIds
|
||||
} else {
|
||||
self.selectedPeers = self.selectedPeers.filter { !peerIds.contains($0) }
|
||||
}
|
||||
@ -711,7 +714,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
}
|
||||
peers.append(EnginePeer(item.peer))
|
||||
}
|
||||
if !peers.isEmpty {
|
||||
if !list.list.isEmpty {
|
||||
subscriber.putNext(peers)
|
||||
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) {
|
||||
guard let component = self.component, let environment = self.environment, let itemLayout = self.itemLayout else {
|
||||
return
|
||||
@ -1024,8 +1046,20 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
guard let self, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else {
|
||||
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(
|
||||
EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []),
|
||||
EngineStoryPrivacy(base: base, additionallyIncludePeers: self.selectedPeers),
|
||||
self.selectedOptions.contains(.screenshot),
|
||||
self.selectedOptions.contains(.pin)
|
||||
)
|
||||
@ -1106,6 +1140,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
} else {
|
||||
if let index = self.selectedPeers.firstIndex(of: peer.id) {
|
||||
self.selectedPeers.remove(at: index)
|
||||
self.updateSelectedGroupPeers()
|
||||
} else {
|
||||
self.selectedPeers.append(peer.id)
|
||||
}
|
||||
@ -1566,6 +1601,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
self.selectedCategories.remove(categoryId)
|
||||
} else if let peerId = tokenId.base as? EnginePeer.Id {
|
||||
self.selectedPeers.removeAll(where: { $0 == peerId })
|
||||
self.updateSelectedGroupPeers()
|
||||
}
|
||||
if self.selectedCategories.isEmpty {
|
||||
self.selectedCategories.insert(.everyone)
|
||||
@ -2099,6 +2135,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
context: AccountContext,
|
||||
subject: Subject = .chats(blocked: false),
|
||||
initialPeerIds: Set<EnginePeer.Id> = Set(),
|
||||
savedSelectedPeers: Set<EnginePeer.Id> = Set(),
|
||||
closeFriends: Signal<[EnginePeer], NoError> = .single([]),
|
||||
blockedPeersContext: BlockedPeersContext? = nil
|
||||
) {
|
||||
|
@ -368,6 +368,7 @@ private final class StoryContainerScreenComponent: Component {
|
||||
|
||||
private let inputMediaNodeDataPromise = Promise<ChatEntityKeyboardInputNode.InputData>()
|
||||
private let closeFriendsPromise = Promise<[EnginePeer]>()
|
||||
private var blockedPeers: BlockedPeersContext?
|
||||
|
||||
private var availableReactions: StoryAvailableReactions?
|
||||
|
||||
@ -1128,6 +1129,8 @@ private final class StoryContainerScreenComponent: Component {
|
||||
self.closeFriendsPromise.set(
|
||||
component.context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.CloseFriends())
|
||||
)
|
||||
|
||||
self.blockedPeers = BlockedPeersContext(account: component.context.account, subject: .stories)
|
||||
}
|
||||
|
||||
var update = false
|
||||
@ -1478,6 +1481,7 @@ private final class StoryContainerScreenComponent: Component {
|
||||
},
|
||||
keyboardInputData: self.inputMediaNodeDataPromise.get(),
|
||||
closeFriends: self.closeFriendsPromise,
|
||||
blockedPeers: self.blockedPeers,
|
||||
sharedViewListsContext: self.sharedViewListsContext,
|
||||
stealthModeTimeout: stealthModeTimeout
|
||||
)),
|
||||
|
@ -71,6 +71,8 @@ final class StoryItemContentComponent: Component {
|
||||
private var videoNode: UniversalVideoNode?
|
||||
private var loadingEffectView: StoryItemLoadingEffectView?
|
||||
|
||||
private var mediaAreasEffectView: StoryItemLoadingEffectView?
|
||||
|
||||
private var currentMessageMedia: EngineMedia?
|
||||
private var fetchDisposable: Disposable?
|
||||
private var priorityDisposable: Disposable?
|
||||
@ -715,7 +717,7 @@ final class StoryItemContentComponent: Component {
|
||||
if let current = self.loadingEffectView {
|
||||
loadingEffectView = current
|
||||
} else {
|
||||
loadingEffectView = StoryItemLoadingEffectView(frame: CGRect())
|
||||
loadingEffectView = StoryItemLoadingEffectView(effectAlpha: 0.1, duration: 1.0, hasBorder: true, playOnce: false)
|
||||
self.loadingEffectView = 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
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,10 @@ import ComponentFlow
|
||||
import Display
|
||||
|
||||
final class StoryItemLoadingEffectView: UIView {
|
||||
private let effectAlpha: CGFloat
|
||||
private let duration: Double
|
||||
private let playOnce: Bool
|
||||
|
||||
private let hierarchyTrackingLayer: HierarchyTrackingLayer
|
||||
|
||||
private let gradientWidth: CGFloat
|
||||
@ -14,9 +18,13 @@ final class StoryItemLoadingEffectView: UIView {
|
||||
private let borderContainerView: UIView
|
||||
private let borderMaskLayer: SimpleShapeLayer
|
||||
|
||||
override init(frame: CGRect) {
|
||||
init(effectAlpha: CGFloat, duration: Double, hasBorder: Bool, playOnce: Bool) {
|
||||
self.hierarchyTrackingLayer = HierarchyTrackingLayer()
|
||||
|
||||
self.effectAlpha = effectAlpha
|
||||
self.duration = duration
|
||||
self.playOnce = playOnce
|
||||
|
||||
self.gradientWidth = 200.0
|
||||
self.backgroundView = UIImageView()
|
||||
|
||||
@ -24,7 +32,7 @@ final class StoryItemLoadingEffectView: UIView {
|
||||
self.borderContainerView = UIView()
|
||||
self.borderMaskLayer = SimpleShapeLayer()
|
||||
|
||||
super.init(frame: frame)
|
||||
super.init(frame: .zero)
|
||||
|
||||
self.layer.addSublayer(self.hierarchyTrackingLayer)
|
||||
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())
|
||||
})
|
||||
}
|
||||
self.backgroundView.image = generateGradient(0.1)
|
||||
self.backgroundView.image = generateGradient(self.effectAlpha)
|
||||
self.addSubview(self.backgroundView)
|
||||
|
||||
self.borderGradientView.image = generateGradient(0.2)
|
||||
self.borderContainerView.addSubview(self.borderGradientView)
|
||||
self.addSubview(self.borderContainerView)
|
||||
self.borderContainerView.layer.mask = self.borderMaskLayer
|
||||
if hasBorder {
|
||||
self.borderGradientView.image = generateGradient(self.effectAlpha + 0.1)
|
||||
self.borderContainerView.addSubview(self.borderGradientView)
|
||||
self.addSubview(self.borderContainerView)
|
||||
self.borderContainerView.layer.mask = self.borderMaskLayer
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -83,8 +93,8 @@ final class StoryItemLoadingEffectView: UIView {
|
||||
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)
|
||||
animation.repeatCount = Float.infinity
|
||||
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 = self.playOnce ? 1 : Float.infinity
|
||||
self.backgroundView.layer.add(animation, forKey: "shimmer")
|
||||
self.borderGradientView.layer.add(animation, forKey: "shimmer")
|
||||
}
|
||||
|
@ -119,6 +119,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
public let toggleAmbientMode: () -> Void
|
||||
public let keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>
|
||||
public let closeFriends: Promise<[EnginePeer]>
|
||||
public let blockedPeers: BlockedPeersContext?
|
||||
let sharedViewListsContext: StoryItemSetViewListComponent.SharedListsContext
|
||||
let stealthModeTimeout: Int32?
|
||||
|
||||
@ -154,6 +155,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
toggleAmbientMode: @escaping () -> Void,
|
||||
keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>,
|
||||
closeFriends: Promise<[EnginePeer]>,
|
||||
blockedPeers: BlockedPeersContext?,
|
||||
sharedViewListsContext: StoryItemSetViewListComponent.SharedListsContext,
|
||||
stealthModeTimeout: Int32?
|
||||
) {
|
||||
@ -188,6 +190,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
self.toggleAmbientMode = toggleAmbientMode
|
||||
self.keyboardInputData = keyboardInputData
|
||||
self.closeFriends = closeFriends
|
||||
self.blockedPeers = blockedPeers
|
||||
self.sharedViewListsContext = sharedViewListsContext
|
||||
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)
|
||||
}, action: { [weak self] _, f in
|
||||
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 {
|
||||
return
|
||||
@ -2428,6 +2432,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
elevatedLayout: false,
|
||||
position: .top,
|
||||
animateInAsReplacement: false,
|
||||
blurred: true,
|
||||
action: { _ in return false }
|
||||
), nil)
|
||||
})))
|
||||
@ -2436,7 +2441,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stories"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
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 {
|
||||
return
|
||||
@ -2448,6 +2453,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
elevatedLayout: false,
|
||||
position: .top,
|
||||
animateInAsReplacement: false,
|
||||
blurred: true,
|
||||
action: { _ in return false }
|
||||
), nil)
|
||||
})))
|
||||
@ -2484,6 +2490,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
elevatedLayout: false,
|
||||
position: .top,
|
||||
animateInAsReplacement: false,
|
||||
blurred: true,
|
||||
action: { [weak self] action in
|
||||
guard let self, let component = self.component else {
|
||||
return false
|
||||
@ -2530,6 +2537,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
elevatedLayout: false,
|
||||
position: .top,
|
||||
animateInAsReplacement: false,
|
||||
blurred: true,
|
||||
action: { [weak self] action in
|
||||
guard let self, let component = self.component else {
|
||||
return false
|
||||
@ -3171,7 +3179,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
storeAttributedTextInPasteboard(text)
|
||||
|
||||
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 = undoController
|
||||
component.controller()?.present(undoController, in: .current)
|
||||
@ -3405,6 +3413,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
blurred: true,
|
||||
action: { [weak self] _ in
|
||||
self?.sendMessageContext.tooltipScreen = nil
|
||||
self?.updateIsProgressPaused()
|
||||
@ -3461,7 +3470,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -3630,7 +3639,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
} else if privacy.base == .nobody {
|
||||
if !privacy.additionallyIncludePeers.isEmpty {
|
||||
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 {
|
||||
text = component.strings.Story_PrivacyTooltipNobody
|
||||
}
|
||||
@ -3644,6 +3653,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
content: .info(title: nil, text: text, timeout: nil),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
blurred: true,
|
||||
action: { _ in return false }
|
||||
)
|
||||
self.sendMessageContext.tooltipScreen = controller
|
||||
@ -3666,7 +3676,8 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
context: context,
|
||||
subject: .stories(editing: true),
|
||||
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
|
||||
guard let self else {
|
||||
@ -3725,9 +3736,10 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
let context = component.context
|
||||
let subject: ShareWithPeersScreen.StateContext.Subject
|
||||
if blockedPeers {
|
||||
subject = .chats(blocked: true)
|
||||
@ -3736,7 +3748,12 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
} else {
|
||||
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
|
||||
guard let self else {
|
||||
return
|
||||
@ -3911,32 +3928,38 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
|
||||
let subject: Signal<MediaEditorScreen.Subject?, NoError>
|
||||
|
||||
var duration: Double?
|
||||
let media = item.media._asMedia()
|
||||
if let file = media as? TelegramMediaFile {
|
||||
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())
|
||||
)
|
||||
subject = getStorySource(engine: component.context.engine, peerId: component.context.account.peerId, id: Int64(item.id))
|
||||
|> mapToSignal { source in
|
||||
if let source {
|
||||
return .single(.draft(source, Int64(item.id)))
|
||||
} else {
|
||||
let symlinkPath = data.path + ".mp4"
|
||||
if fileSize(symlinkPath) == nil {
|
||||
let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath)
|
||||
let media = item.media._asMedia()
|
||||
return 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 {
|
||||
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),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
blurred: true,
|
||||
action: { _ in return false }
|
||||
), nil)
|
||||
} else {
|
||||
@ -4462,6 +4486,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
content: .info(title: component.strings.Story_ToastSavedToProfileTitle, text: component.strings.Story_ToastSavedToProfileText, timeout: nil),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
blurred: true,
|
||||
action: { _ in return false }
|
||||
), nil)
|
||||
}
|
||||
@ -4520,6 +4545,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
content: .linkCopied(text: component.strings.Story_ToastLinkCopied),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
blurred: true,
|
||||
action: { _ in return false }
|
||||
), 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),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
blurred: true,
|
||||
action: { _ in return false }
|
||||
), nil)
|
||||
} 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),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
blurred: true,
|
||||
action: { _ in return false }
|
||||
), nil)
|
||||
}
|
||||
@ -4748,6 +4776,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
content: .linkCopied(text: component.strings.Story_ToastLinkCopied),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
blurred: true,
|
||||
action: { _ in return false }
|
||||
), nil)
|
||||
}
|
||||
@ -4801,7 +4830,19 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
return
|
||||
}
|
||||
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
|
||||
)
|
||||
}
|
||||
)
|
||||
})))
|
||||
|
@ -1517,7 +1517,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != component.context.account.peerId
|
||||
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 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 {
|
||||
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: [:])
|
||||
|
||||
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))
|
||||
locationController.dismissed = { [weak view] in
|
||||
view?.updateModalTransitionFactor(0.0, transition: .animated(duration: 0.5, curve: .spring))
|
||||
Queue.mainQueue().after(0.3, {
|
||||
Queue.mainQueue().after(0.5, {
|
||||
view?.updateIsProgressPaused()
|
||||
})
|
||||
}
|
||||
@ -3127,7 +3127,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
frame = view.controlsContainerView.convert(frame, to: nil)
|
||||
|
||||
let node = controller.displayNode
|
||||
let menuController = ContextMenuController(actions: actions)
|
||||
let menuController = ContextMenuController(actions: actions, blurred: true)
|
||||
menuController.centerHorizontally = true
|
||||
controller.present(
|
||||
menuController,
|
||||
|
@ -13389,7 +13389,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -14379,7 +14379,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
@ -368,7 +368,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
if case .undo = action {
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
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 = { [weak controller] c in
|
||||
|
@ -1548,7 +1548,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
let context = item.context
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
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 = { [weak controller] c in
|
||||
|
@ -1119,7 +1119,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
}, changeLocation: {
|
||||
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>
|
||||
if let address = address {
|
||||
addressSignal = .single(address)
|
||||
|
@ -7066,7 +7066,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
||||
let context = strongSelf.context
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
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 = { [weak controller] c in
|
||||
|
@ -293,7 +293,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
}
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
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 = { [weak controller] c in
|
||||
|
@ -1732,7 +1732,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
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
|
||||
switch source {
|
||||
case .settings:
|
||||
@ -1780,7 +1780,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
case .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 {
|
||||
|
@ -83,6 +83,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
private var detailsPlaceholderNode: DetailsChatPlaceholderNode?
|
||||
|
||||
private var applicationInFocusDisposable: Disposable?
|
||||
private var storyUploadEventsDisposable: Disposable?
|
||||
|
||||
public init(context: AccountContext) {
|
||||
self.context = context
|
||||
@ -111,6 +112,15 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
|> deliverOn(Queue.mainQueue())).start(next: { value in
|
||||
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.presentationDataDisposable?.dispose()
|
||||
self.applicationInFocusDisposable?.dispose()
|
||||
self.storyUploadEventsDisposable?.dispose()
|
||||
}
|
||||
|
||||
public func getContactsController() -> ViewController? {
|
||||
@ -362,6 +373,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
return
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
if let rootTabController = self.rootTabController {
|
||||
if let index = rootTabController.controllers.firstIndex(where: { $0 is ChatListController}) {
|
||||
rootTabController.selectedIndex = index
|
||||
@ -397,7 +409,10 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
if let imageData = compressImageToJPEG(image, quality: 0.7) {
|
||||
let entities = generateChatInputTextEntities(caption)
|
||||
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()
|
||||
}
|
||||
@ -428,7 +443,10 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
}
|
||||
Logger.shared.log("MediaEditor", "Calling uploadStory for video, randomId \(randomId)")
|
||||
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()
|
||||
}
|
||||
|
@ -1029,7 +1029,7 @@ public class TranslateScreen: ViewController {
|
||||
|
||||
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)
|
||||
|
||||
|
@ -69,17 +69,19 @@ public final class UndoOverlayController: ViewController {
|
||||
private let animateInAsReplacement: Bool
|
||||
private var action: (UndoOverlayAction) -> Bool
|
||||
|
||||
private let blurred: Bool
|
||||
private var didPlayPresentationAnimation = false
|
||||
private var dismissed = 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.content = content
|
||||
self.elevatedLayout = elevatedLayout
|
||||
self.position = position
|
||||
self.animateInAsReplacement = animateInAsReplacement
|
||||
self.blurred = blurred
|
||||
self.action = action
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
@ -92,7 +94,7 @@ public final class UndoOverlayController: ViewController {
|
||||
}
|
||||
|
||||
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
|
||||
}, dismiss: { [weak self] in
|
||||
self?.dismiss()
|
||||
|
@ -47,6 +47,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
private let dismiss: () -> Void
|
||||
|
||||
private var content: UndoOverlayContent
|
||||
private let blurred: Bool
|
||||
|
||||
private let effectView: UIView
|
||||
|
||||
@ -60,10 +61,11 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
|
||||
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.elevatedLayout = elevatedLayout
|
||||
self.placementPosition = placementPosition
|
||||
self.blurred = blurred
|
||||
self.content = content
|
||||
|
||||
self.action = action
|
||||
@ -1080,7 +1082,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.undoButtonNode = HighlightTrackingButtonNode()
|
||||
|
||||
self.panelNode = ASDisplayNode()
|
||||
if presentationData.theme.overallDarkAppearance {
|
||||
if presentationData.theme.overallDarkAppearance && !self.blurred {
|
||||
self.panelNode.backgroundColor = presentationData.theme.rootController.tabBar.backgroundColor
|
||||
} else {
|
||||
self.panelNode.backgroundColor = .clear
|
||||
|
Loading…
x
Reference in New Issue
Block a user