diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index a43e57a77b..f1ae79757f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -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."; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index c873578ba6..08ca9bed05 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -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 diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 21c0036f32..2e0a40829d 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -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 { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index e2a8b69837..c9fd03b977 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -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 { diff --git a/submodules/Components/PagerComponent/Sources/PagerComponent.swift b/submodules/Components/PagerComponent/Sources/PagerComponent.swift index 2a36a49474..c0330219e4 100644 --- a/submodules/Components/PagerComponent/Sources/PagerComponent.swift +++ b/submodules/Components/PagerComponent/Sources/PagerComponent.swift @@ -137,9 +137,11 @@ public final class PagerComponentPanelEnvironment: 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 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 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 Bool { @@ -288,7 +293,9 @@ public final class PagerComponent 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) diff --git a/submodules/Display/Source/ContextMenuContainerNode.swift b/submodules/Display/Source/ContextMenuContainerNode.swift index ecbbabef41..85472178db 100644 --- a/submodules/Display/Source/ContextMenuContainerNode.swift +++ b/submodules/Display/Source/ContextMenuContainerNode.swift @@ -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 { diff --git a/submodules/Display/Source/ContextMenuController.swift b/submodules/Display/Source/ContextMenuController.swift index 69c34e56ff..b19ae87d7b 100644 --- a/submodules/Display/Source/ContextMenuController.swift +++ b/submodules/Display/Source/ContextMenuController.swift @@ -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() } diff --git a/submodules/Display/Source/ContextMenuNode.swift b/submodules/Display/Source/ContextMenuNode.swift index 7dbf503bc9..57c63520ad 100644 --- a/submodules/Display/Source/ContextMenuNode.swift +++ b/submodules/Display/Source/ContextMenuNode.swift @@ -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 { diff --git a/submodules/Display/Source/TooltipControllerNode.swift b/submodules/Display/Source/TooltipControllerNode.swift index ae7ee4cdf2..c21deca4ff 100644 --- a/submodules/Display/Source/TooltipControllerNode.swift +++ b/submodules/Display/Source/TooltipControllerNode.swift @@ -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() diff --git a/submodules/DrawingUI/Sources/DrawingLocationEntity.swift b/submodules/DrawingUI/Sources/DrawingLocationEntity.swift index 5edc40be4b..4be482a2ff 100644 --- a/submodules/DrawingUI/Sources/DrawingLocationEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingLocationEntity.swift @@ -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 { diff --git a/submodules/DrawingUI/Sources/StickerPickerScreen.swift b/submodules/DrawingUI/Sources/StickerPickerScreen.swift index 26852d7f4e..cb9f051bcd 100644 --- a/submodules/DrawingUI/Sources/StickerPickerScreen.swift +++ b/submodules/DrawingUI/Sources/StickerPickerScreen.swift @@ -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() 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))) } diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index cd3fb84aa2..6b0deaf947 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -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 } diff --git a/submodules/Geocoding/Sources/Geocoding.swift b/submodules/Geocoding/Sources/Geocoding.swift index 01f9f939f7..c0745657f3 100644 --- a/submodules/Geocoding/Sources/Geocoding.swift +++ b/submodules/Geocoding/Sources/Geocoding.swift @@ -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 { +public func reverseGeocodeLocation(latitude: Double, longitude: Double, locale: Locale? = nil) -> Signal { 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 + } +} diff --git a/submodules/LocationUI/Sources/LocationMapNode.swift b/submodules/LocationUI/Sources/LocationMapNode.swift index 5bdf3a7ef7..def8ec6830 100644 --- a/submodules/LocationUI/Sources/LocationMapNode.swift +++ b/submodules/LocationUI/Sources/LocationMapNode.swift @@ -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 diff --git a/submodules/LocationUI/Sources/LocationOptionsNode.swift b/submodules/LocationUI/Sources/LocationOptionsNode.swift index c67170e9a6..50fe8aae00 100644 --- a/submodules/LocationUI/Sources/LocationOptionsNode.swift +++ b/submodules/LocationUI/Sources/LocationOptionsNode.swift @@ -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) diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index a3c0b2d8f0..5317fa1122 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -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)? @@ -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)? = 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)? = 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, .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) } diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index a68645f6a0..a405045440 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -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 } } diff --git a/submodules/LocationUI/Sources/LocationSearchContainerNode.swift b/submodules/LocationUI/Sources/LocationSearchContainerNode.swift index ab3720cf62..1ae77009a7 100644 --- a/submodules/LocationUI/Sources/LocationSearchContainerNode.swift +++ b/submodules/LocationUI/Sources/LocationSearchContainerNode.swift @@ -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) } } } diff --git a/submodules/LocationUI/Sources/LocationViewController.swift b/submodules/LocationUI/Sources/LocationViewController.swift index 248efc4415..37301ffdd2 100644 --- a/submodules/LocationUI/Sources/LocationViewController.swift +++ b/submodules/LocationUI/Sources/LocationViewController.swift @@ -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)? = nil, subject: EngineMessage, params: LocationViewParams) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = 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() { diff --git a/submodules/SettingsUI/Sources/ArchiveSettingsController.swift b/submodules/SettingsUI/Sources/ArchiveSettingsController.swift index 33779e65da..c675aaba98 100644 --- a/submodules/SettingsUI/Sources/ArchiveSettingsController.swift +++ b/submodules/SettingsUI/Sources/ArchiveSettingsController.swift @@ -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) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift index 44b91a3feb..54a23f5311 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift @@ -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() diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/PendingStoryManager.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/PendingStoryManager.swift index a9e40234a5..6b8ad1b433 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/PendingStoryManager.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/PendingStoryManager.swift @@ -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(nil) private var allStoriesUploadProgressValue: Float? = nil var allStoriesUploadProgress: Signal { @@ -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() diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index 3e2a7fe6d2..0e88881799 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -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 { 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) { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 628ee37757..c48b0965a3 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -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 { + 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? { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/BlockedPeersContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/BlockedPeersContext.swift index 423bf138e2..c7d2f7f683 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/BlockedPeersContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/BlockedPeersContext.swift @@ -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 { 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() + var validPeerIds = Set() + var allPeerIds = Set() + + 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] = [] - 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 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 in + return .complete() + } } } @@ -209,7 +265,6 @@ public final class BlockedPeersContext { |> castError(BlockedPeersContextAddError.self) } |> deliverOnMainQueue - |> mapToSignal { peer -> Signal in guard let strongSelf = self, let peer = peer else { return .complete() diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift index 3ee179fae5..5692d8f23b 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift @@ -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 { diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index fee8d9f5e6..1e309d573c 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -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 diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index 419db7c137..d0f5381b10 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -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 { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 7b8316becd..bd33ff2c47 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -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 { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift index 0c721d3a65..9763f7dba3 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift @@ -501,6 +501,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode externalBottomPanelContainer: nil, displayTopPanelBackground: .blur, topPanelExtensionUpdated: { _, _ in }, + topPanelScrollingOffset: { _, _ in }, hideInputUpdated: { _, _, _ in }, hideTopPanelUpdated: { _, _ in }, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift index 8c97b2bbce..8d63911b0c 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift @@ -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.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 diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index 7f584ee820..d1f3df7b5b 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -352,6 +352,7 @@ private final class TopicIconSelectionComponent: Component { externalBottomPanelContainer: nil, displayTopPanelBackground: .blur, topPanelExtensionUpdated: { _, _ in }, + topPanelScrollingOffset: { _, _ in }, hideInputUpdated: { _, _, _ in }, hideTopPanelUpdated: { _, _ in }, switchToTextInput: {}, diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingLocationEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingLocationEntity.swift index 7851d40e70..028925d55f 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingLocationEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingLocationEntity.swift @@ -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 diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 0a02fc1b5f..d3e01101f6 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -2707,38 +2707,97 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate controller.push(galleryController) } + private let staticEmojiPack = Promise() + 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 + 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 { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StorySource.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StorySource.swift index 4a07354d0c..eecf938e89 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StorySource.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StorySource.swift @@ -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 { - let key = EngineDataBuffer(length: 8) - key.setInt64(0, value: id) +public func getStorySource(engine: TelegramEngine, peerId: EnginePeer.Id, id: Int64) -> Signal { + 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 in diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/StickersResultPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/StickersResultPanelComponent.swift index a18b70d09a..6f2351860a 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/StickersResultPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/StickersResultPanelComponent.swift @@ -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 diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/SectionHeaderComponent.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/SectionHeaderComponent.swift index e394f4bbf3..a62d388824 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/SectionHeaderComponent.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/SectionHeaderComponent.swift @@ -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) diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 01136c8f93..7f15255b22 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -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() 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 = Set(), + savedSelectedPeers: Set = Set(), closeFriends: Signal<[EnginePeer], NoError> = .single([]), blockedPeersContext: BlockedPeersContext? = nil ) { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index d825a85f43..2cfffc77b7 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -368,6 +368,7 @@ private final class StoryContainerScreenComponent: Component { private let inputMediaNodeDataPromise = Promise() 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 )), diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index 05a8222a14..f76433a039 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -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 } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemLoadingEffectView.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemLoadingEffectView.swift index 95aa3ad5cd..7f32b7a753 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemLoadingEffectView.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemLoadingEffectView.swift @@ -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") } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 673e2b5490..1e01a3b5f4 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -119,6 +119,7 @@ public final class StoryItemSetContainerComponent: Component { public let toggleAmbientMode: () -> Void public let keyboardInputData: Signal 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, 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 - - 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 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 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 + ) } ) }))) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 19190a9b39..2ec11e67af 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -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) = (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, diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 82088b7cc3..f013410035 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -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 } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index 072e82c3ff..aac98745ab 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index b7ae99114b..35f63b5bfa 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/CreateGroupController.swift b/submodules/TelegramUI/Sources/CreateGroupController.swift index 7256e89676..0add97cc14 100644 --- a/submodules/TelegramUI/Sources/CreateGroupController.swift +++ b/submodules/TelegramUI/Sources/CreateGroupController.swift @@ -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 if let address = address { addressSignal = .single(address) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index d9851bec50..0d0921ecc7 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -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 } diff --git a/submodules/TelegramUI/Sources/PeerSelectionController.swift b/submodules/TelegramUI/Sources/PeerSelectionController.swift index 653c2ecda1..f910318e17 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionController.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionController.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index 176d2a929a..6329e07c74 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 75829a7fc1..3a473e34a0 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 98bb07754c..12b17f29f6 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -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() } diff --git a/submodules/TranslateUI/Sources/TranslateScreen.swift b/submodules/TranslateUI/Sources/TranslateScreen.swift index 66cd55f4be..e8641b9441 100644 --- a/submodules/TranslateUI/Sources/TranslateScreen.swift +++ b/submodules/TranslateUI/Sources/TranslateScreen.swift @@ -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) diff --git a/submodules/UndoUI/Sources/UndoOverlayController.swift b/submodules/UndoUI/Sources/UndoOverlayController.swift index 9c34660759..921ce3ad37 100644 --- a/submodules/UndoUI/Sources/UndoOverlayController.swift +++ b/submodules/UndoUI/Sources/UndoOverlayController.swift @@ -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() diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 0ef17499da..3b3df76ba3 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -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