mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '547564eac36f3ef601902f5fc93dc76aac4ef33b'
This commit is contained in:
commit
8c60c011ec
@ -1100,9 +1100,6 @@ public struct StoriesConfiguration {
|
||||
}
|
||||
|
||||
public static func with(appConfiguration: AppConfiguration) -> StoriesConfiguration {
|
||||
//#if DEBUG
|
||||
// return StoriesConfiguration(posting: .premium)
|
||||
//#else
|
||||
if let data = appConfiguration.data, let postingString = data["stories_posting"] as? String {
|
||||
var posting: PostingAvailability
|
||||
switch postingString {
|
||||
@ -1117,7 +1114,6 @@ public struct StoriesConfiguration {
|
||||
} else {
|
||||
return .defaultValue
|
||||
}
|
||||
//#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,6 +97,7 @@ swift_library(
|
||||
"//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState",
|
||||
"//submodules/StickerPackPreviewUI:StickerPackPreviewUI",
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
"//submodules/ImageTransparency",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -464,6 +464,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
||||
self.selectedEntityView = nil
|
||||
self.selectionChanged(nil)
|
||||
self.hasSelectionChanged(false)
|
||||
view.selectionView?.removeFromSuperview()
|
||||
}
|
||||
if animated {
|
||||
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak view] _ in
|
||||
|
@ -3127,6 +3127,12 @@ public final class DrawingToolsInteraction {
|
||||
if let entityView = self.entitiesView.getView(for: entity.uuid) {
|
||||
if let textEntityView = entityView as? DrawingTextEntityView {
|
||||
textEntityView.beginEditing(accessoryView: self.textEditAccessoryView)
|
||||
|
||||
textEntityView.replaceWithImage = { [weak self] image, isSticker in
|
||||
if let self {
|
||||
self.insertEntity(DrawingStickerEntity(content: .image(image, isSticker ? .sticker : .rectangle)), scale: 2.5)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if self.isVideo {
|
||||
entityView.seek(to: 0.0)
|
||||
|
@ -6,6 +6,8 @@ import AccountContext
|
||||
import TextFormat
|
||||
import EmojiTextAttachmentView
|
||||
import MediaEditor
|
||||
import MobileCoreServices
|
||||
import ImageTransparency
|
||||
|
||||
extension DrawingTextEntity.Alignment {
|
||||
var alignment: NSTextAlignment {
|
||||
@ -30,6 +32,7 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
||||
var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
|
||||
|
||||
var textChanged: () -> Void = {}
|
||||
var replaceWithImage: (UIImage, Bool) -> Void = { _, _ in }
|
||||
|
||||
init(context: AccountContext, entity: DrawingTextEntity) {
|
||||
self.textView = DrawingTextView(frame: .zero)
|
||||
@ -64,6 +67,10 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
||||
return EmojiTextAttachmentView(context: context, userLocation: .other, emoji: emoji, file: emoji.file, cache: strongSelf.context.animationCache, renderer: strongSelf.context.animationRenderer, placeholderColor: UIColor.white.withAlphaComponent(0.12), pointSize: CGSize(width: pointSize, height: pointSize))
|
||||
}
|
||||
|
||||
self.textView.onPaste = { [weak self] in
|
||||
return self?.onPaste() ?? false
|
||||
}
|
||||
|
||||
self.update(animated: false)
|
||||
}
|
||||
|
||||
@ -88,6 +95,52 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
||||
self.endEditing()
|
||||
}
|
||||
|
||||
private func onPaste() -> Bool {
|
||||
let pasteboard = UIPasteboard.general
|
||||
|
||||
var images: [UIImage] = []
|
||||
var isPNG = false
|
||||
var isMemoji = false
|
||||
for item in pasteboard.items {
|
||||
if let image = item["com.apple.png-sticker"] as? UIImage {
|
||||
images.append(image)
|
||||
isPNG = true
|
||||
isMemoji = true
|
||||
} else if let image = item[kUTTypePNG as String] as? UIImage {
|
||||
images.append(image)
|
||||
isPNG = true
|
||||
} else if let image = item["com.apple.uikit.image"] as? UIImage {
|
||||
images.append(image)
|
||||
isPNG = true
|
||||
} else if let image = item[kUTTypeJPEG as String] as? UIImage {
|
||||
images.append(image)
|
||||
} else if let image = item[kUTTypeGIF as String] as? UIImage {
|
||||
images.append(image)
|
||||
}
|
||||
}
|
||||
|
||||
if isPNG && images.count == 1, let image = images.first, let cgImage = image.cgImage {
|
||||
let maxSide = max(image.size.width, image.size.height)
|
||||
if maxSide.isZero {
|
||||
return false
|
||||
}
|
||||
let aspectRatio = min(image.size.width, image.size.height) / maxSide
|
||||
if isMemoji || (imageHasTransparency(cgImage) && aspectRatio > 0.2) {
|
||||
self.endEditing(reset: true)
|
||||
self.replaceWithImage(image, true)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if !images.isEmpty, let image = images.first {
|
||||
self.endEditing(reset: true)
|
||||
self.replaceWithImage(image, false)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private var emojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = []
|
||||
func updateEntities() {
|
||||
self.textView.drawingLayoutManager.ensureLayout(for: self.textView.textContainer)
|
||||
@ -589,6 +642,7 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
||||
self.textView.layer.shadowOpacity = 0.0
|
||||
self.textView.layer.shadowRadius = 0.0
|
||||
}
|
||||
self.textView.textAlignment = self.textEntity.alignment.alignment
|
||||
|
||||
self.updateText(keepSelectedRange: afterAppendingEmoji)
|
||||
|
||||
@ -1269,12 +1323,6 @@ final class DrawingTextView: UITextView, NSLayoutManagerDelegate {
|
||||
self.fixTypingAttributes()
|
||||
}
|
||||
|
||||
override func paste(_ sender: Any?) {
|
||||
self.fixTypingAttributes()
|
||||
super.paste(sender)
|
||||
self.fixTypingAttributes()
|
||||
}
|
||||
|
||||
fileprivate func fixTypingAttributes() {
|
||||
var attributes: [NSAttributedString.Key: Any] = [:]
|
||||
if let font = self.font {
|
||||
@ -1346,6 +1394,29 @@ final class DrawingTextView: UITextView, NSLayoutManagerDelegate {
|
||||
|
||||
self.onLayersUpdate?()
|
||||
}
|
||||
|
||||
var onPaste: () -> Bool = { return true }
|
||||
override func paste(_ sender: Any?) {
|
||||
if !self.text.isEmpty || self.onPaste() {
|
||||
self.fixTypingAttributes()
|
||||
super.paste(sender)
|
||||
self.fixTypingAttributes()
|
||||
}
|
||||
}
|
||||
|
||||
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
|
||||
if action == #selector(self.paste(_:)) {
|
||||
if UIPasteboard.general.hasImages && self.text.isEmpty {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if #available(iOS 15.0, *) {
|
||||
if action == #selector(captureTextFromCamera(_:)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return super.canPerformAction(action, withSender: sender)
|
||||
}
|
||||
}
|
||||
|
||||
private var availableFonts: [String: (String, String)] = {
|
||||
|
@ -110,6 +110,8 @@ struct Month: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
private var savedStoriesContentOffset: CGFloat?
|
||||
|
||||
public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
public enum Subject {
|
||||
public enum Media: Equatable {
|
||||
@ -207,7 +209,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
|
||||
private let containerNode: ASDisplayNode
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
private let gridNode: GridNode
|
||||
fileprivate let gridNode: GridNode
|
||||
fileprivate var cameraView: TGAttachmentCameraView?
|
||||
private var cameraActivateAreaNode: AccessibilityAreaNode
|
||||
private var placeholderNode: MediaPickerPlaceholderNode?
|
||||
@ -1076,12 +1078,21 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
}
|
||||
|
||||
private var didRestoreContentOffset = false
|
||||
private func dequeueTransaction() {
|
||||
if self.enqueuedTransactions.isEmpty {
|
||||
return
|
||||
}
|
||||
let transaction = self.enqueuedTransactions.removeFirst()
|
||||
self.gridNode.transaction(GridNodeTransaction(deleteItems: transaction.deletions, insertItems: transaction.insertions, updateItems: transaction.updates, scrollToItem: transaction.scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
|
||||
|
||||
if let subject = self.controller?.subject, case .assets(_, .story) = subject, let contentOffset = savedStoriesContentOffset, !self.didRestoreContentOffset {
|
||||
if contentOffset > 64.0 {
|
||||
self.gridNode.scrollView.setContentOffset(CGPoint(x: 0.0, y: contentOffset), animated: false)
|
||||
self.controller?.requestAttachmentMenuExpansion()
|
||||
}
|
||||
self.didRestoreContentOffset = true
|
||||
}
|
||||
}
|
||||
|
||||
func scrollToTop(animated: Bool = false) {
|
||||
@ -1861,10 +1872,17 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
public override func dismiss(completion: (() -> Void)? = nil) {
|
||||
self.controllerNode.cancelAssetDownloads()
|
||||
|
||||
super.dismiss(animated: flag, completion: completion)
|
||||
if case .assets(_, .story) = self.subject {
|
||||
let contentOffset = self.controllerNode.gridNode.scrollView.contentOffset.y
|
||||
if contentOffset > 100.0 || savedStoriesContentOffset != nil {
|
||||
savedStoriesContentOffset = contentOffset
|
||||
}
|
||||
}
|
||||
|
||||
super.dismiss(completion: completion)
|
||||
}
|
||||
|
||||
@objc private func rightButtonPressed() {
|
||||
|
@ -5744,6 +5744,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
|
||||
return false
|
||||
}
|
||||
|
||||
private var isPanningList = false
|
||||
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
guard let (layout, _) = self.validLayout else {
|
||||
return
|
||||
@ -5765,6 +5766,19 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
|
||||
|
||||
self.controller?.dismissAllTooltips()
|
||||
|
||||
let location = recognizer.location(in: self.listContainer.view)
|
||||
let isPanningList: Bool
|
||||
if self.listNode.frame.contains(location) {
|
||||
if case let .known(value) = contentOffset, value <= 0.5 {
|
||||
isPanningList = false
|
||||
} else {
|
||||
isPanningList = true
|
||||
}
|
||||
} else {
|
||||
isPanningList = false
|
||||
}
|
||||
self.isPanningList = isPanningList
|
||||
|
||||
if case .fullscreen = self.displayMode, case .compact = layout.metrics.widthClass {
|
||||
self.isPanning = true
|
||||
|
||||
@ -5789,9 +5803,11 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
|
||||
return
|
||||
}
|
||||
|
||||
let translateBounds: Bool
|
||||
var translateBounds = false
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
translateBounds = true
|
||||
if !self.isPanningList {
|
||||
translateBounds = true
|
||||
}
|
||||
} else {
|
||||
switch self.displayMode {
|
||||
case let .modal(isExpanded, previousIsFilled):
|
||||
@ -5960,52 +5976,56 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
|
||||
} else {
|
||||
self.panGestureArguments = nil
|
||||
var dismissing = false
|
||||
if bounds.minY < -60 || (bounds.minY < 0.0 && velocity.y > 300.0) {
|
||||
if self.isScheduling {
|
||||
self.dismissScheduled()
|
||||
dismissing = true
|
||||
} else if case .regular = layout.metrics.widthClass {
|
||||
self.controller?.dismiss(closing: false, manual: true)
|
||||
dismissing = true
|
||||
} else {
|
||||
if case .fullscreen = self.displayMode {
|
||||
} else {
|
||||
if case .regular = layout.metrics.widthClass, self.isPanningList {
|
||||
|
||||
} else {
|
||||
if bounds.minY < -60 || (bounds.minY < 0.0 && velocity.y > 300.0) {
|
||||
if self.isScheduling {
|
||||
self.dismissScheduled()
|
||||
dismissing = true
|
||||
} else if case .regular = layout.metrics.widthClass {
|
||||
self.controller?.dismiss(closing: false, manual: true)
|
||||
dismissing = true
|
||||
} else {
|
||||
if case .fullscreen = self.displayMode {
|
||||
} else {
|
||||
self.controller?.dismiss(closing: false, manual: true)
|
||||
dismissing = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !self.isScheduling && (velocity.y < -300.0 || offset < topInset / 2.0) {
|
||||
if velocity.y > -2200.0 && !self.isFullscreen {
|
||||
DispatchQueue.main.async {
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
} else if !self.isScheduling && (velocity.y < -300.0 || offset < topInset / 2.0) {
|
||||
if velocity.y > -2200.0 && !self.isFullscreen {
|
||||
DispatchQueue.main.async {
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
}
|
||||
}
|
||||
|
||||
let initialVelocity: CGFloat = offset.isZero ? 0.0 : abs(velocity.y / offset)
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity))
|
||||
if case .modal = self.displayMode {
|
||||
self.displayMode = .modal(isExpanded: true, isFilled: true)
|
||||
}
|
||||
self.updateDecorationsColors()
|
||||
self.animatingExpansion = true
|
||||
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition)
|
||||
}
|
||||
self.updateDecorationsLayout(transition: transition, completion: {
|
||||
self.animatingExpansion = false
|
||||
})
|
||||
} else if !self.isScheduling {
|
||||
self.updateDecorationsColors()
|
||||
self.animatingExpansion = true
|
||||
self.listNode.scroller.setContentOffset(CGPoint(), animated: false)
|
||||
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
}
|
||||
self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
|
||||
self.animatingExpansion = false
|
||||
})
|
||||
}
|
||||
|
||||
let initialVelocity: CGFloat = offset.isZero ? 0.0 : abs(velocity.y / offset)
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity))
|
||||
if case .modal = self.displayMode {
|
||||
self.displayMode = .modal(isExpanded: true, isFilled: true)
|
||||
}
|
||||
self.updateDecorationsColors()
|
||||
self.animatingExpansion = true
|
||||
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition)
|
||||
}
|
||||
self.updateDecorationsLayout(transition: transition, completion: {
|
||||
self.animatingExpansion = false
|
||||
})
|
||||
} else if !self.isScheduling {
|
||||
self.updateDecorationsColors()
|
||||
self.animatingExpansion = true
|
||||
self.listNode.scroller.setContentOffset(CGPoint(), animated: false)
|
||||
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
}
|
||||
self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
|
||||
self.animatingExpansion = false
|
||||
})
|
||||
}
|
||||
if !dismissing {
|
||||
var bounds = self.contentContainer.bounds
|
||||
|
@ -18,6 +18,7 @@ public struct UserLimitsConfiguration: Equatable {
|
||||
public let maxSharedFolderInviteLinks: Int32
|
||||
public let maxSharedFolderJoin: Int32
|
||||
public let maxStoryCaptionLength: Int32
|
||||
public let maxExpiringStoriesCount: Int32
|
||||
|
||||
public static var defaultValue: UserLimitsConfiguration {
|
||||
return UserLimitsConfiguration(
|
||||
@ -36,7 +37,8 @@ public struct UserLimitsConfiguration: Equatable {
|
||||
maxReactionsPerMessage: 1,
|
||||
maxSharedFolderInviteLinks: 3,
|
||||
maxSharedFolderJoin: 2,
|
||||
maxStoryCaptionLength: 1024
|
||||
maxStoryCaptionLength: 1024,
|
||||
maxExpiringStoriesCount: 100
|
||||
)
|
||||
}
|
||||
|
||||
@ -56,7 +58,8 @@ public struct UserLimitsConfiguration: Equatable {
|
||||
maxReactionsPerMessage: Int32,
|
||||
maxSharedFolderInviteLinks: Int32,
|
||||
maxSharedFolderJoin: Int32,
|
||||
maxStoryCaptionLength: Int32
|
||||
maxStoryCaptionLength: Int32,
|
||||
maxExpiringStoriesCount: Int32
|
||||
) {
|
||||
self.maxPinnedChatCount = maxPinnedChatCount
|
||||
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
||||
@ -74,6 +77,7 @@ public struct UserLimitsConfiguration: Equatable {
|
||||
self.maxSharedFolderInviteLinks = maxSharedFolderInviteLinks
|
||||
self.maxSharedFolderJoin = maxSharedFolderJoin
|
||||
self.maxStoryCaptionLength = maxStoryCaptionLength
|
||||
self.maxExpiringStoriesCount = maxExpiringStoriesCount
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,5 +118,6 @@ extension UserLimitsConfiguration {
|
||||
self.maxSharedFolderInviteLinks = getValue("chatlist_invites_limit", orElse: isPremium ? 100 : 3)
|
||||
self.maxSharedFolderJoin = getValue("chatlists_joined_limit", orElse: isPremium ? 100 : 2)
|
||||
self.maxStoryCaptionLength = getGeneralValue("story_caption_length_limit", orElse: defaultValue.maxStoryCaptionLength)
|
||||
self.maxExpiringStoriesCount = getGeneralValue("story_expiring_limit", orElse: defaultValue.maxExpiringStoriesCount)
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ public enum EngineConfiguration {
|
||||
public let maxSharedFolderInviteLinks: Int32
|
||||
public let maxSharedFolderJoin: Int32
|
||||
public let maxStoryCaptionLength: Int32
|
||||
public let maxExpiringStoriesCount: Int32
|
||||
|
||||
public static var defaultValue: UserLimits {
|
||||
return UserLimits(UserLimitsConfiguration.defaultValue)
|
||||
@ -73,7 +74,8 @@ public enum EngineConfiguration {
|
||||
maxReactionsPerMessage: Int32,
|
||||
maxSharedFolderInviteLinks: Int32,
|
||||
maxSharedFolderJoin: Int32,
|
||||
maxStoryCaptionLength: Int32
|
||||
maxStoryCaptionLength: Int32,
|
||||
maxExpiringStoriesCount: Int32
|
||||
) {
|
||||
self.maxPinnedChatCount = maxPinnedChatCount
|
||||
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
||||
@ -91,6 +93,7 @@ public enum EngineConfiguration {
|
||||
self.maxSharedFolderInviteLinks = maxSharedFolderInviteLinks
|
||||
self.maxSharedFolderJoin = maxSharedFolderJoin
|
||||
self.maxStoryCaptionLength = maxStoryCaptionLength
|
||||
self.maxExpiringStoriesCount = maxExpiringStoriesCount
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,7 +146,8 @@ public extension EngineConfiguration.UserLimits {
|
||||
maxReactionsPerMessage: userLimitsConfiguration.maxReactionsPerMessage,
|
||||
maxSharedFolderInviteLinks: userLimitsConfiguration.maxSharedFolderInviteLinks,
|
||||
maxSharedFolderJoin: userLimitsConfiguration.maxSharedFolderJoin,
|
||||
maxStoryCaptionLength: userLimitsConfiguration.maxStoryCaptionLength
|
||||
maxStoryCaptionLength: userLimitsConfiguration.maxStoryCaptionLength,
|
||||
maxExpiringStoriesCount: userLimitsConfiguration.maxExpiringStoriesCount
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -56,5 +56,23 @@ public extension TelegramEngine.EngineData.Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct CloseFriends: TelegramEngineDataItem, PostboxViewDataItem {
|
||||
public typealias Result = Array<EnginePeer>
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
var key: PostboxViewKey {
|
||||
return .contacts(accountPeerId: nil, includePresences: false)
|
||||
}
|
||||
|
||||
func extract(view: PostboxView) -> Result {
|
||||
guard let view = view as? ContactPeersView else {
|
||||
preconditionFailure()
|
||||
}
|
||||
return view.peers.filter { $0.isCloseFriend }.map(EnginePeer.init)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -189,6 +189,15 @@ public extension Peer {
|
||||
}
|
||||
}
|
||||
|
||||
var isCloseFriend: Bool {
|
||||
switch self {
|
||||
case let user as TelegramUser:
|
||||
return user.flags.contains(.isCloseFriend)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var isCopyProtectionEnabled: Bool {
|
||||
switch self {
|
||||
case let group as TelegramGroup:
|
||||
|
@ -245,7 +245,16 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
func setupRecentAssetSubscription() {
|
||||
let mediaAssetsContext = MediaAssetsContext()
|
||||
self.mediaAssetsContext = mediaAssetsContext
|
||||
self.lastGalleryAssetsDisposable = (mediaAssetsContext.recentAssets()
|
||||
|
||||
self.lastGalleryAssetsDisposable = (
|
||||
mediaAssetsContext.mediaAccess()
|
||||
|> mapToSignal { [weak mediaAssetsContext] status in
|
||||
if case .authorized = status, let mediaAssetsContext {
|
||||
return mediaAssetsContext.recentAssets()
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> map { fetchResult in
|
||||
return fetchResult?.lastObject
|
||||
}
|
||||
@ -258,6 +267,13 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
})
|
||||
}
|
||||
|
||||
func requestMediaAccess(completion: @escaping () -> Void) {
|
||||
guard let mediaAssetsContext = self.mediaAssetsContext else {
|
||||
return
|
||||
}
|
||||
mediaAssetsContext.requestMediaAccess(completion: completion)
|
||||
}
|
||||
|
||||
func setupVolumeButtonsHandler() {
|
||||
guard self.volumeButtonsListener == nil else {
|
||||
return
|
||||
@ -687,11 +703,13 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
}
|
||||
state.togglePosition(animateFlipAction)
|
||||
},
|
||||
galleryTapped: {
|
||||
galleryTapped: { [weak state] in
|
||||
guard let controller = environment.controller() as? CameraScreen else {
|
||||
return
|
||||
}
|
||||
controller.presentGallery()
|
||||
state?.requestMediaAccess {
|
||||
controller.presentGallery()
|
||||
}
|
||||
},
|
||||
swipeHintUpdated: { [weak state] hint in
|
||||
if let state {
|
||||
@ -1350,15 +1368,15 @@ public class CameraScreen: ViewController {
|
||||
)
|
||||
|> filter { $0 && $1 }
|
||||
|> take(1)).start(next: { [weak self] _, _ in
|
||||
self?.mainPreviewView.removePlaceholder(delay: 0.15)
|
||||
self?.additionalPreviewView.removePlaceholder(delay: 0.15)
|
||||
self?.mainPreviewView.removePlaceholder(delay: 0.35)
|
||||
self?.additionalPreviewView.removePlaceholder(delay: 0.35)
|
||||
})
|
||||
} else {
|
||||
let _ = (self.mainPreviewView.isPreviewing
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
self?.mainPreviewView.removePlaceholder(delay: 0.15)
|
||||
self?.mainPreviewView.removePlaceholder(delay: 0.35)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@ -1563,6 +1581,9 @@ public class CameraScreen: ViewController {
|
||||
if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer {
|
||||
return false
|
||||
}
|
||||
if gestureRecognizer is UIPinchGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -95,8 +95,13 @@ public final class MediaAssetsContext: NSObject, PHPhotoLibraryChangeObserver {
|
||||
)
|
||||
}
|
||||
|
||||
public func requestMediaAccess() -> Void {
|
||||
public func requestMediaAccess(completion: @escaping () -> Void = {}) -> Void {
|
||||
PHPhotoLibrary.requestAuthorization { [weak self] status in
|
||||
Queue.mainQueue().async {
|
||||
if case .authorized = status {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
self?.mediaAccessSink.putNext(status)
|
||||
}
|
||||
}
|
||||
|
@ -3799,7 +3799,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
contextGesture.activatedAfterCompletion = { [weak self] point, wasTap in
|
||||
guard let `self` = self, let component = self.component else {
|
||||
guard let self, let component = self.component, !self.isSearchActivated else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -302,22 +302,4 @@ public final class DrawingTextEntity: DrawingEntity, Codable {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// public weak var currentEntityView: DrawingEntityView?
|
||||
// public func makeView(context: AccountContext) -> DrawingEntityView {
|
||||
// let entityView = DrawingTextEntityView(context: context, entity: self)
|
||||
// self.currentEntityView = entityView
|
||||
// return entityView
|
||||
// }
|
||||
//
|
||||
// public func prepareForRender() {
|
||||
// self.renderImage = (self.currentEntityView as? DrawingTextEntityView)?.getRenderImage()
|
||||
// self.renderSubEntities = (self.currentEntityView as? DrawingTextEntityView)?.getRenderSubEntities()
|
||||
//
|
||||
// if case .none = self.animation {
|
||||
// self.renderAnimationFrames = nil
|
||||
// } else {
|
||||
// self.renderAnimationFrames = (self.currentEntityView as? DrawingTextEntityView)?.getRenderAnimationFrames()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@ -1038,13 +1038,15 @@ final class MediaEditorScreenComponent: Component {
|
||||
isGeneralThreadClosed: nil
|
||||
)
|
||||
|
||||
let heightAndOverflow = inputMediaNode.updateLayout(width: availableSize.width, leftInset: 0.0, rightInset: 0.0, bottomInset: component.bottomSafeInset, standardInputHeight: environment.deviceMetrics.standardInputHeight(inLandscape: false), inputHeight: environment.inputHeight, maximumHeight: availableSize.height, inputPanelHeight: 0.0, transition: .immediate, interfaceState: presentationInterfaceState, layoutMetrics: environment.metrics, deviceMetrics: environment.deviceMetrics, isVisible: true, isExpanded: false)
|
||||
let availableInputMediaWidth = previewSize.width
|
||||
let heightAndOverflow = inputMediaNode.updateLayout(width: availableInputMediaWidth, leftInset: 0.0, rightInset: 0.0, bottomInset: component.bottomSafeInset, standardInputHeight: environment.deviceMetrics.standardInputHeight(inLandscape: false), inputHeight: environment.inputHeight, maximumHeight: availableSize.height, inputPanelHeight: 0.0, transition: .immediate, interfaceState: presentationInterfaceState, layoutMetrics: environment.metrics, deviceMetrics: environment.deviceMetrics, isVisible: true, isExpanded: false)
|
||||
let inputNodeHeight = heightAndOverflow.0
|
||||
let inputNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - inputNodeHeight), size: CGSize(width: availableSize.width, height: inputNodeHeight))
|
||||
let inputNodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - availableInputMediaWidth) / 2.0), y: availableSize.height - inputNodeHeight), size: CGSize(width: availableInputMediaWidth, height: inputNodeHeight))
|
||||
transition.setFrame(layer: inputMediaNode.layer, frame: inputNodeFrame)
|
||||
|
||||
inputHeight = heightAndOverflow.0
|
||||
keyboardHeight = max(keyboardHeight, heightAndOverflow.0)
|
||||
if inputNodeHeight > 0.0 {
|
||||
inputHeight = inputNodeHeight
|
||||
}
|
||||
} else if let inputMediaNode = self.inputMediaNode {
|
||||
self.inputMediaNode = nil
|
||||
|
||||
@ -1074,6 +1076,8 @@ final class MediaEditorScreenComponent: Component {
|
||||
})
|
||||
}
|
||||
|
||||
keyboardHeight = inputHeight
|
||||
|
||||
let nextInputMode: MessageInputPanelComponent.InputMode
|
||||
switch self.currentInputMode {
|
||||
case .text:
|
||||
@ -2087,6 +2091,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
} else {
|
||||
self.mediaEditor?.play()
|
||||
}
|
||||
} else if self.mediaEditor?.sourceIsVideo == true {
|
||||
if isInteracting {
|
||||
self.mediaEditor?.stop()
|
||||
} else {
|
||||
self.mediaEditor?.play()
|
||||
}
|
||||
}
|
||||
self.isInteractingWithEntities = isInteracting
|
||||
if !isInteracting {
|
||||
@ -3234,6 +3244,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
public var dismissed: () -> Void = { }
|
||||
public var willDismiss: () -> Void = { }
|
||||
|
||||
private var closeFriends = Promise<[EnginePeer]>()
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
public init(
|
||||
@ -3309,6 +3321,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
|
||||
let dropInteraction = UIDropInteraction(delegate: self)
|
||||
self.displayNode.view.addInteraction(dropInteraction)
|
||||
|
||||
Queue.mainQueue().after(1.0) {
|
||||
self.closeFriends.set(self.context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.CloseFriends()))
|
||||
}
|
||||
}
|
||||
|
||||
func openPrivacySettings(_ privacy: MediaEditorResultPrivacy? = nil, completion: @escaping () -> Void = {}) {
|
||||
@ -3321,7 +3337,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
let text = self.getCaption().string
|
||||
let mentions = generateTextEntities(text, enabledTypes: [.mention], currentEntities: []).map { (text as NSString).substring(with: NSRange(location: $0.range.lowerBound + 1, length: $0.range.upperBound - $0.range.lowerBound - 1)) }
|
||||
|
||||
let stateContext = ShareWithPeersScreen.StateContext(context: self.context, subject: .stories(editing: false), initialPeerIds: Set(privacy.privacy.additionallyIncludePeers))
|
||||
let stateContext = ShareWithPeersScreen.StateContext(
|
||||
context: self.context,
|
||||
subject: .stories(editing: false),
|
||||
initialPeerIds: Set(privacy.privacy.additionallyIncludePeers),
|
||||
closeFriends: self.closeFriends.get()
|
||||
)
|
||||
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
|
@ -646,15 +646,15 @@ private final class MediaToolsScreenComponent: Component {
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
startValue: 0.0
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .sharpen,
|
||||
title: "Sharpen",
|
||||
value: mediaEditor?.getToolValue(.sharpen) as? Float ?? 0.0,
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
startValue: 0.0
|
||||
)
|
||||
// AdjustmentTool(
|
||||
// key: .sharpen,
|
||||
// title: "Sharpen",
|
||||
// value: mediaEditor?.getToolValue(.sharpen) as? Float ?? 0.0,
|
||||
// minValue: 0.0,
|
||||
// maxValue: 1.0,
|
||||
// startValue: 0.0
|
||||
// )
|
||||
]
|
||||
|
||||
if !component.mediaEditor.sourceIsVideo {
|
||||
|
@ -305,6 +305,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
private var ignoreScrolling: Bool = false
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
private var savedSelectedPeers: [EnginePeer.Id] = []
|
||||
private var selectedPeers: [EnginePeer.Id] = []
|
||||
private var selectedCategories = Set<CategoryId>()
|
||||
private var selectedOptions = Set<OptionId>()
|
||||
@ -586,6 +587,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
visibleBounds.size.height += itemLayout.topInset
|
||||
|
||||
var visibleFrame = self.scrollView.frame
|
||||
visibleFrame.origin.x = 0.0
|
||||
visibleFrame.origin.y -= itemLayout.topInset
|
||||
visibleFrame.size.height += itemLayout.topInset
|
||||
|
||||
@ -675,7 +677,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
if minSectionHeader == nil {
|
||||
minSectionHeader = sectionHeaderView
|
||||
}
|
||||
sectionHeaderTransition.setFrame(view: sectionHeaderView, frame: sectionHeaderFrame)
|
||||
sectionHeaderTransition.setFrame(view: sectionHeaderView, frame: sectionHeaderFrame.offsetBy(dx: self.scrollView.frame.minX, dy: 0.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -721,7 +723,14 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
}
|
||||
if self.selectedCategories.contains(categoryId) {
|
||||
} else {
|
||||
self.selectedPeers = []
|
||||
if self.selectedCategories.contains(.selectedContacts) {
|
||||
self.savedSelectedPeers = self.selectedPeers
|
||||
}
|
||||
if categoryId == .selectedContacts {
|
||||
self.selectedPeers = self.savedSelectedPeers
|
||||
} else {
|
||||
self.selectedPeers = []
|
||||
}
|
||||
|
||||
self.selectedCategories.removeAll()
|
||||
self.selectedCategories.insert(categoryId)
|
||||
@ -1742,13 +1751,16 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
public final class State {
|
||||
let peers: [EnginePeer]
|
||||
let presences: [EnginePeer.Id: EnginePeer.Presence]
|
||||
let closeFriendsPeers: [EnginePeer]
|
||||
|
||||
fileprivate init(
|
||||
peers: [EnginePeer],
|
||||
presences: [EnginePeer.Id: EnginePeer.Presence]
|
||||
presences: [EnginePeer.Id: EnginePeer.Presence],
|
||||
closeFriendsPeers: [EnginePeer]
|
||||
) {
|
||||
self.peers = peers
|
||||
self.presences = presences
|
||||
self.closeFriendsPeers = closeFriendsPeers
|
||||
}
|
||||
}
|
||||
|
||||
@ -1778,28 +1790,33 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
public init(
|
||||
context: AccountContext,
|
||||
subject: Subject = .chats,
|
||||
initialPeerIds: Set<EnginePeer.Id> = Set()
|
||||
initialPeerIds: Set<EnginePeer.Id> = Set(),
|
||||
closeFriends: Signal<[EnginePeer], NoError> = .single([])
|
||||
) {
|
||||
self.subject = subject
|
||||
self.initialPeerIds = initialPeerIds
|
||||
|
||||
switch subject {
|
||||
case .stories:
|
||||
var signals: [Signal<EnginePeer?, NoError>] = []
|
||||
var peerSignals: [Signal<EnginePeer?, NoError>] = []
|
||||
if initialPeerIds.count < 3 {
|
||||
for peerId in initialPeerIds {
|
||||
signals.append(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)))
|
||||
peerSignals.append(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)))
|
||||
}
|
||||
}
|
||||
self.stateDisposable = (combineLatest(signals)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||
|
||||
let peers = combineLatest(peerSignals)
|
||||
|
||||
self.stateDisposable = combineLatest(queue: Queue.mainQueue(), peers, closeFriends)
|
||||
.start(next: { [weak self] peers, closeFriends in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let state = State(
|
||||
peers: peers.compactMap { $0 },
|
||||
presences: [:]
|
||||
presences: [:],
|
||||
closeFriendsPeers: closeFriends
|
||||
)
|
||||
self.stateValue = state
|
||||
self.stateSubject.set(.single(state))
|
||||
@ -1831,7 +1848,8 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
|
||||
let state = State(
|
||||
peers: peers,
|
||||
presences: presences
|
||||
presences: presences,
|
||||
closeFriendsPeers: []
|
||||
)
|
||||
self.stateValue = state
|
||||
self.stateSubject.set(.single(state))
|
||||
@ -1885,7 +1903,8 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
|
||||
let state = State(
|
||||
peers: peers,
|
||||
presences: contactList.presences
|
||||
presences: contactList.presences,
|
||||
closeFriendsPeers: []
|
||||
)
|
||||
|
||||
self.stateValue = state
|
||||
@ -1893,8 +1912,17 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
|
||||
self.readySubject.set(true)
|
||||
})
|
||||
case let .search(query, _):
|
||||
self.stateDisposable = (context.engine.contacts.searchLocalPeers(query: query)
|
||||
case let .search(query, onlyContacts):
|
||||
let signal: Signal<[EngineRenderedPeer], NoError>
|
||||
if onlyContacts {
|
||||
signal = context.engine.contacts.searchContacts(query: query)
|
||||
|> map { result in
|
||||
return result.0.map { EngineRenderedPeer(peer: $0) }
|
||||
}
|
||||
} else {
|
||||
signal = context.engine.contacts.searchLocalPeers(query: query)
|
||||
}
|
||||
self.stateDisposable = (signal
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||
guard let self else {
|
||||
return
|
||||
@ -1914,7 +1942,8 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
return false
|
||||
}
|
||||
},
|
||||
presences: [:]
|
||||
presences: [:],
|
||||
closeFriendsPeers: []
|
||||
)
|
||||
self.stateValue = state
|
||||
self.stateSubject.set(.single(state))
|
||||
@ -1990,12 +2019,20 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
actionTitle: contactsSubtitle
|
||||
))
|
||||
|
||||
var closeFriendsSubtitle = "edit list"
|
||||
if let peers = stateContext.stateValue?.closeFriendsPeers, !peers.isEmpty {
|
||||
if peers.count > 2 {
|
||||
closeFriendsSubtitle = "\(peers.count) people"
|
||||
} else {
|
||||
closeFriendsSubtitle = String(peers.map { $0.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) }.joined(separator: ", "))
|
||||
}
|
||||
}
|
||||
categoryItems.append(ShareWithPeersScreenComponent.CategoryItem(
|
||||
id: .closeFriends,
|
||||
title: "Close Friends",
|
||||
icon: "Call/StarHighlighted",
|
||||
iconColor: .green,
|
||||
actionTitle: "edit list"
|
||||
actionTitle: closeFriendsSubtitle
|
||||
))
|
||||
|
||||
var selectedContactsSubtitle = "choose"
|
||||
|
@ -363,6 +363,7 @@ private final class StoryContainerScreenComponent: Component {
|
||||
private let audioModePromise = ValuePromise<StoryContentItem.AudioMode>(.ambient, ignoreRepeated: true)
|
||||
|
||||
private let inputMediaNodeDataPromise = Promise<ChatEntityKeyboardInputNode.InputData>()
|
||||
private let closeFriendsPromise = Promise<[EnginePeer]>()
|
||||
|
||||
private var availableReactions: StoryAvailableReactions?
|
||||
|
||||
@ -1078,6 +1079,10 @@ private final class StoryContainerScreenComponent: Component {
|
||||
sendGif: nil
|
||||
)
|
||||
)
|
||||
|
||||
self.closeFriendsPromise.set(
|
||||
component.context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.CloseFriends())
|
||||
)
|
||||
}
|
||||
|
||||
var update = false
|
||||
@ -1398,6 +1403,7 @@ private final class StoryContainerScreenComponent: Component {
|
||||
self.state?.updated(transition: .immediate)
|
||||
},
|
||||
keyboardInputData: self.inputMediaNodeDataPromise.get(),
|
||||
closeFriends: self.closeFriendsPromise.get(),
|
||||
sharedViewListsContext: self.sharedViewListsContext
|
||||
)),
|
||||
environment: {},
|
||||
|
@ -103,6 +103,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
public let controller: () -> ViewController?
|
||||
public let toggleAmbientMode: () -> Void
|
||||
public let keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>
|
||||
public let closeFriends: Signal<[EnginePeer], NoError>
|
||||
let sharedViewListsContext: StoryItemSetViewListComponent.SharedListsContext
|
||||
|
||||
init(
|
||||
@ -135,6 +136,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
controller: @escaping () -> ViewController?,
|
||||
toggleAmbientMode: @escaping () -> Void,
|
||||
keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>,
|
||||
closeFriends: Signal<[EnginePeer], NoError>,
|
||||
sharedViewListsContext: StoryItemSetViewListComponent.SharedListsContext
|
||||
) {
|
||||
self.context = context
|
||||
@ -166,6 +168,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
self.controller = controller
|
||||
self.toggleAmbientMode = toggleAmbientMode
|
||||
self.keyboardInputData = keyboardInputData
|
||||
self.closeFriends = closeFriends
|
||||
self.sharedViewListsContext = sharedViewListsContext
|
||||
}
|
||||
|
||||
@ -3173,16 +3176,23 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
|
||||
private func openItemPrivacySettings(initialPrivacy: EngineStoryPrivacy? = nil) {
|
||||
guard let context = self.component?.context else {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
let context = component.context
|
||||
|
||||
let privacy = initialPrivacy ?? self.component?.slice.item.storyItem.privacy
|
||||
guard let privacy else {
|
||||
return
|
||||
}
|
||||
|
||||
let stateContext = ShareWithPeersScreen.StateContext(context: context, subject: .stories(editing: true), initialPeerIds: Set(privacy.additionallyIncludePeers))
|
||||
let stateContext = ShareWithPeersScreen.StateContext(
|
||||
context: context,
|
||||
subject: .stories(editing: true),
|
||||
initialPeerIds: Set(privacy.additionallyIncludePeers),
|
||||
closeFriends: component.closeFriends
|
||||
)
|
||||
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
|
@ -287,7 +287,6 @@ public final class TextFieldComponent: Component {
|
||||
return false
|
||||
}
|
||||
|
||||
var images: [UIImage] = []
|
||||
if let data = pasteboard.data(forPasteboardType: "com.compuserve.gif") {
|
||||
component.paste(.gif(data))
|
||||
return false
|
||||
@ -295,6 +294,7 @@ public final class TextFieldComponent: Component {
|
||||
component.paste(.video(data))
|
||||
return false
|
||||
} else {
|
||||
var images: [UIImage] = []
|
||||
var isPNG = false
|
||||
var isMemoji = false
|
||||
for item in pasteboard.items {
|
||||
@ -332,6 +332,7 @@ public final class TextFieldComponent: Component {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -542,7 +542,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
animationSpacing = 8.0
|
||||
}
|
||||
|
||||
let containerWidth = max(100.0, min(layout.size.width, 614.0) - sideInset * 2.0)
|
||||
let containerWidth = max(100.0, min(layout.size.width - sideInset * 2.0, 614.0))
|
||||
|
||||
var actionSize: CGSize = .zero
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user