Various improvements

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

View File

@ -9738,6 +9738,7 @@ Sorry for the inconvenience.";
"Story.PrivacyTooltipSelectedContacts.Contacts_1" = "1 contact";
"Story.PrivacyTooltipSelectedContacts.Contacts_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.";

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

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

View File

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

View File

@ -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 {

View File

@ -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()
}

View File

@ -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 {

View File

@ -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()

View File

@ -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 {

View File

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

View File

@ -1353,7 +1353,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
}
controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text, customUndoText: nil, timeout: nil), elevatedLayout: true, animateInAsReplacement: false, action: { action in
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
}

View File

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

View File

@ -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

View File

@ -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)

View File

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

View File

@ -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
}
}

View File

@ -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)
}
}
}

View File

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

View File

@ -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)
}

View File

@ -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()

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -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

View File

@ -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)

View File

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

View File

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

View File

@ -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
}
}

View File

@ -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")
}

View File

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

View File

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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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()
}

View File

@ -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)

View File

@ -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()

View File

@ -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