mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Various improvements
This commit is contained in:
parent
bc793ac286
commit
f306171529
@ -5868,6 +5868,7 @@ Sorry for the inconvenience.";
|
||||
"Chat.PinnedMessagesHiddenText" = "You will see the bar with pinned messages only if a new message is pinned.";
|
||||
|
||||
"OpenFile.PotentiallyDangerousContentAlert" = "Previewing this file can potentially expose your IP address to its sender. Continue?";
|
||||
"OpenFile.Proceed" = "Proceed";
|
||||
|
||||
"Chat.PinnedListPreview.ShowAllMessages" = "Show All Messages";
|
||||
"Chat.PinnedListPreview.UnpinAllMessages" = "Unpin All Messages";
|
||||
@ -5890,3 +5891,10 @@ Sorry for the inconvenience.";
|
||||
"Stats.Message.PrivateShares" = "Private Shares";
|
||||
|
||||
"ChatSettings.WidgetSettings" = "Widget";
|
||||
|
||||
"Conversation.AddCaption" = "Add a Caption";
|
||||
"Conversation.EditCaption" = "Edit Caption";
|
||||
"Conversation.EditThisPhoto" = "Edit This Photo";
|
||||
"Conversation.ReplacePhoto" = "Replace Photo";
|
||||
"Conversation.ReplaceFile" = "Replace File";
|
||||
"Conversation.SendAsNewPhoto" = "Send as New Photo";
|
||||
|
@ -18,8 +18,9 @@ public final class GalleryControllerActionInteraction {
|
||||
public let openBotCommand: (String) -> Void
|
||||
public let addContact: (String) -> Void
|
||||
public let storeMediaPlaybackState: (MessageId, Double?) -> Void
|
||||
public let editMedia: (MessageId) -> Void
|
||||
|
||||
public init(openUrl: @escaping (String, Bool) -> Void, openUrlIn: @escaping (String) -> Void, openPeerMention: @escaping (String) -> Void, openPeer: @escaping (PeerId) -> Void, openHashtag: @escaping (String?, String) -> Void, openBotCommand: @escaping (String) -> Void, addContact: @escaping (String) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void) {
|
||||
public init(openUrl: @escaping (String, Bool) -> Void, openUrlIn: @escaping (String) -> Void, openPeerMention: @escaping (String) -> Void, openPeer: @escaping (PeerId) -> Void, openHashtag: @escaping (String?, String) -> Void, openBotCommand: @escaping (String) -> Void, addContact: @escaping (String) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void, editMedia: @escaping (MessageId) -> Void) {
|
||||
self.openUrl = openUrl
|
||||
self.openUrlIn = openUrlIn
|
||||
self.openPeerMention = openPeerMention
|
||||
@ -28,5 +29,6 @@ public final class GalleryControllerActionInteraction {
|
||||
self.openBotCommand = openBotCommand
|
||||
self.addContact = addContact
|
||||
self.storeMediaPlaybackState = storeMediaPlaybackState
|
||||
self.editMedia = editMedia
|
||||
}
|
||||
}
|
||||
|
@ -264,6 +264,7 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
|
||||
private(set) var emptyNode: ChatListEmptyNode?
|
||||
var emptyShimmerEffectNode: ChatListShimmerNode?
|
||||
private var shimmerNodeOffset: CGFloat = 0.0
|
||||
let listNode: ChatListNode
|
||||
|
||||
private var validLayout: (CGSize, UIEdgeInsets, CGFloat)?
|
||||
@ -285,8 +286,12 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
return
|
||||
}
|
||||
var needsShimmerNode = false
|
||||
var shimmerNodeOffset: CGFloat = 0.0
|
||||
switch isEmptyState {
|
||||
case let .empty(isLoading):
|
||||
case let .empty(isLoading, hasArchiveInfo):
|
||||
if hasArchiveInfo {
|
||||
shimmerNodeOffset = 253.0
|
||||
}
|
||||
if isLoading {
|
||||
needsShimmerNode = true
|
||||
|
||||
@ -326,12 +331,13 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
if needsShimmerNode {
|
||||
strongSelf.shimmerNodeOffset = shimmerNodeOffset
|
||||
if strongSelf.emptyShimmerEffectNode == nil {
|
||||
let emptyShimmerEffectNode = ChatListShimmerNode()
|
||||
strongSelf.emptyShimmerEffectNode = emptyShimmerEffectNode
|
||||
strongSelf.insertSubnode(emptyShimmerEffectNode, belowSubnode: strongSelf.listNode)
|
||||
if let (size, insets, _) = strongSelf.validLayout, let offset = strongSelf.floatingHeaderOffset {
|
||||
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset, transition: .immediate)
|
||||
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: .immediate)
|
||||
}
|
||||
}
|
||||
} else if let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode {
|
||||
@ -349,7 +355,7 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
}
|
||||
strongSelf.floatingHeaderOffset = offset
|
||||
if let (size, insets, _) = strongSelf.validLayout, let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode {
|
||||
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset, transition: transition)
|
||||
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: transition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -403,7 +403,7 @@ public enum ChatListNodeScrollPosition {
|
||||
|
||||
public enum ChatListNodeEmptyState: Equatable {
|
||||
case notEmpty(containsChats: Bool)
|
||||
case empty(isLoading: Bool)
|
||||
case empty(isLoading: Bool, hasArchiveInfo: Bool)
|
||||
}
|
||||
|
||||
public final class ChatListNode: ListView {
|
||||
@ -1411,6 +1411,7 @@ public final class ChatListNode: ListView {
|
||||
|
||||
var isEmpty = false
|
||||
var isLoading = false
|
||||
var hasArchiveInfo = false
|
||||
if transition.chatListView.filteredEntries.isEmpty {
|
||||
isEmpty = true
|
||||
} else {
|
||||
@ -1421,6 +1422,9 @@ public final class ChatListNode: ListView {
|
||||
case .GroupReferenceEntry, .HeaderEntry, .HoleEntry:
|
||||
break
|
||||
default:
|
||||
if case .ArchiveIntro = entry {
|
||||
hasArchiveInfo = true
|
||||
}
|
||||
isEmpty = false
|
||||
break loop1
|
||||
}
|
||||
@ -1441,14 +1445,21 @@ public final class ChatListNode: ListView {
|
||||
if !hasHoles {
|
||||
isLoading = false
|
||||
}
|
||||
} else {
|
||||
for entry in transition.chatListView.filteredEntries.reversed().prefix(2) {
|
||||
if case .ArchiveIntro = entry {
|
||||
hasArchiveInfo = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let isEmptyState: ChatListNodeEmptyState
|
||||
if transition.chatListView.isLoading {
|
||||
isEmptyState = .empty(isLoading: true)
|
||||
isEmptyState = .empty(isLoading: true, hasArchiveInfo: hasArchiveInfo)
|
||||
} else if isEmpty {
|
||||
isEmptyState = .empty(isLoading: isLoading)
|
||||
isEmptyState = .empty(isLoading: isLoading, hasArchiveInfo: false)
|
||||
} else {
|
||||
var containsChats = false
|
||||
loop: for entry in transition.chatListView.filteredEntries {
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
bool TGIsGzippedData(NSData *data) {
|
||||
const UInt8 *bytes = (const UInt8 *)data.bytes;
|
||||
return (data.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b);
|
||||
return data.length >= 2 && ((bytes[0] == 0x1f && bytes[1] == 0x8b) || (bytes[0] == 0x78 && bytes[1] == 0x9c));
|
||||
}
|
||||
|
||||
NSData *TGGZipData(NSData *data, float level) {
|
||||
|
@ -21,6 +21,7 @@ import TextSelectionNode
|
||||
|
||||
private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: .white)
|
||||
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: .white)
|
||||
private let editImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Draw"), color: .white)
|
||||
|
||||
private let backwardImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/BackwardButton"), color: .white)
|
||||
private let forwardImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/ForwardButton"), color: .white)
|
||||
@ -47,7 +48,7 @@ private let dateFont = Font.regular(14.0)
|
||||
|
||||
enum ChatItemGalleryFooterContent: Equatable {
|
||||
case info
|
||||
case fetch(status: MediaResourceStatus)
|
||||
case fetch(status: MediaResourceStatus, seekable: Bool)
|
||||
case playback(paused: Bool, seekable: Bool)
|
||||
|
||||
static func ==(lhs: ChatItemGalleryFooterContent, rhs: ChatItemGalleryFooterContent) -> Bool {
|
||||
@ -58,8 +59,8 @@ enum ChatItemGalleryFooterContent: Equatable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .fetch(lhsStatus):
|
||||
if case let .fetch(rhsStatus) = rhs, lhsStatus == rhsStatus {
|
||||
case let .fetch(lhsStatus, lhsSeekable):
|
||||
if case let .fetch(rhsStatus, rhsSeekable) = rhs, lhsStatus == rhsStatus, lhsSeekable == rhsSeekable {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -116,6 +117,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
private let contentNode: ASDisplayNode
|
||||
private let deleteButton: UIButton
|
||||
private let actionButton: UIButton
|
||||
private let editButton: UIButton
|
||||
private let maskNode: ASDisplayNode
|
||||
private let scrollWrapperNode: CaptionScrollWrapperNode
|
||||
private let scrollNode: ASScrollNode
|
||||
@ -165,11 +167,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.playbackControlButton.isHidden = true
|
||||
self.statusButtonNode.isHidden = true
|
||||
self.statusNode.isHidden = true
|
||||
case let .fetch(status):
|
||||
case let .fetch(status, seekable):
|
||||
self.authorNameNode.isHidden = true
|
||||
self.dateNode.isHidden = true
|
||||
self.backwardButton.isHidden = true
|
||||
self.forwardButton.isHidden = true
|
||||
self.backwardButton.isHidden = !seekable
|
||||
self.forwardButton.isHidden = !seekable
|
||||
if status == .Local {
|
||||
self.playbackControlButton.isHidden = false
|
||||
self.playbackControlButton.setImage(playImage, for: [])
|
||||
@ -217,8 +219,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
}
|
||||
}
|
||||
didSet {
|
||||
if let scrubberView = self.scrubberView {
|
||||
scrubberView.setCollapsed(self.visibilityAlpha < 1.0 ? true : false, animated: false)
|
||||
if let scrubberView = self.scrubberView {
|
||||
scrubberView.setCollapsed(self.visibilityAlpha < 1.0, animated: false)
|
||||
self.view.addSubview(scrubberView)
|
||||
scrubberView.updateScrubbingVisual = { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
@ -253,7 +255,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
override func setVisibilityAlpha(_ alpha: CGFloat, animated: Bool) {
|
||||
self.visibilityAlpha = alpha
|
||||
self.contentNode.alpha = alpha
|
||||
self.scrubberView?.setCollapsed(alpha < 1.0 ? true : false, animated: animated)
|
||||
self.scrubberView?.setCollapsed(alpha < 1.0, animated: animated)
|
||||
}
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, present: @escaping (ViewController, Any?) -> Void = { _, _ in }) {
|
||||
@ -268,9 +270,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
|
||||
self.deleteButton = UIButton()
|
||||
self.actionButton = UIButton()
|
||||
self.editButton = UIButton()
|
||||
|
||||
self.deleteButton.setImage(deleteImage, for: [.normal])
|
||||
self.actionButton.setImage(actionImage, for: [.normal])
|
||||
self.editButton.setImage(editImage, for: [.normal])
|
||||
|
||||
self.scrollWrapperNode = CaptionScrollWrapperNode()
|
||||
self.scrollWrapperNode.clipsToBounds = true
|
||||
@ -340,6 +344,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
|
||||
self.contentNode.view.addSubview(self.deleteButton)
|
||||
self.contentNode.view.addSubview(self.actionButton)
|
||||
self.contentNode.view.addSubview(self.editButton)
|
||||
self.contentNode.addSubnode(self.scrollWrapperNode)
|
||||
self.scrollWrapperNode.addSubnode(self.scrollNode)
|
||||
self.scrollNode.addSubnode(self.textNode)
|
||||
@ -356,6 +361,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
|
||||
self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: [.touchUpInside])
|
||||
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), for: [.touchUpInside])
|
||||
self.editButton.addTarget(self, action: #selector(self.editButtonPressed), for: [.touchUpInside])
|
||||
|
||||
self.backwardButton.addTarget(self, action: #selector(self.backwardButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.forwardButton.addTarget(self, action: #selector(self.forwardButtonPressed), forControlEvents: .touchUpInside)
|
||||
@ -444,6 +450,16 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
|
||||
let canDelete: Bool
|
||||
var canShare = !message.containsSecretMedia && !Namespaces.Message.allScheduled.contains(message.id.namespace)
|
||||
|
||||
var canEdit = false
|
||||
for media in message.media {
|
||||
if media is TelegramMediaImage {
|
||||
canEdit = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
canEdit = canEdit && !message.containsSecretMedia
|
||||
if let peer = message.peers[message.id.peerId] {
|
||||
if peer is TelegramUser || peer is TelegramSecretChat {
|
||||
canDelete = true
|
||||
@ -452,17 +468,21 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if message.flags.contains(.Incoming) {
|
||||
canDelete = channel.hasPermission(.deleteAllMessages)
|
||||
canEdit = canEdit && channel.hasPermission(.editAllMessages)
|
||||
} else {
|
||||
canDelete = true
|
||||
}
|
||||
} else {
|
||||
canDelete = false
|
||||
canEdit = false
|
||||
}
|
||||
} else {
|
||||
canDelete = false
|
||||
canShare = false
|
||||
canEdit = false
|
||||
}
|
||||
|
||||
|
||||
var authorNameText: String?
|
||||
if let author = message.effectiveAuthor {
|
||||
authorNameText = author.displayTitle(strings: self.strings, displayOrder: self.nameOrder)
|
||||
@ -496,10 +516,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
}
|
||||
messageText = galleryCaptionStringWithAppliedEntities(message.text, entities: entities)
|
||||
}
|
||||
|
||||
self.editButton.isHidden = message.containsSecretMedia
|
||||
|
||||
self.actionButton.isHidden = message.containsSecretMedia || Namespaces.Message.allScheduled.contains(message.id.namespace)
|
||||
|
||||
if self.currentMessageText != messageText || canDelete != !self.deleteButton.isHidden || canShare != !self.actionButton.isHidden || self.currentAuthorNameText != authorNameText || self.currentDateText != dateText {
|
||||
if self.currentMessageText != messageText || canDelete != !self.deleteButton.isHidden || canShare != !self.actionButton.isHidden || canEdit != !self.editButton.isHidden || self.currentAuthorNameText != authorNameText || self.currentDateText != dateText {
|
||||
self.currentMessageText = messageText
|
||||
|
||||
if messageText.length == 0 {
|
||||
@ -519,6 +539,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
|
||||
self.deleteButton.isHidden = !canDelete
|
||||
self.actionButton.isHidden = !canShare
|
||||
self.editButton.isHidden = !canEdit
|
||||
|
||||
self.requestLayout?(.immediate)
|
||||
}
|
||||
@ -620,20 +641,22 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
if self.textNode.isHidden || !displayCaption {
|
||||
panelHeight += 8.0
|
||||
} else {
|
||||
scrubberY = panelHeight - bottomInset - 44.0 - 41.0
|
||||
scrubberY = panelHeight - bottomInset - 44.0 - 44.0
|
||||
if contentInset > 0.0 {
|
||||
scrubberY -= contentInset + 3.0
|
||||
scrubberY -= contentInset
|
||||
}
|
||||
}
|
||||
|
||||
let scrubberFrame = CGRect(origin: CGPoint(x: leftInset, y: scrubberY), size: CGSize(width: width - leftInset - rightInset, height: 34.0))
|
||||
scrubberView.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
transition.updateFrame(layer: scrubberView.layer, frame: scrubberFrame)
|
||||
transition.updateBounds(layer: scrubberView.layer, bounds: CGRect(origin: CGPoint(), size: scrubberFrame.size))
|
||||
transition.updatePosition(layer: scrubberView.layer, position: CGPoint(x: scrubberFrame.midX, y: scrubberFrame.midY))
|
||||
}
|
||||
transition.updateAlpha(node: self.textNode, alpha: displayCaption ? 1.0 : 0.0)
|
||||
|
||||
self.actionButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
self.deleteButton.frame = CGRect(origin: CGPoint(x: width - 44.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
self.editButton.frame = CGRect(origin: CGPoint(x: width - 44.0 - 50.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
|
||||
if let image = self.backwardButton.image(for: .normal) {
|
||||
self.backwardButton.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0) - 66.0, y: panelHeight - bottomInset - 44.0 + 7.0), size: image.size)
|
||||
@ -708,6 +731,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.authorNameNode.alpha = 1.0
|
||||
self.deleteButton.alpha = 1.0
|
||||
self.actionButton.alpha = 1.0
|
||||
self.editButton.alpha = 1.0
|
||||
self.backwardButton.alpha = 1.0
|
||||
self.forwardButton.alpha = 1.0
|
||||
self.statusNode.alpha = 1.0
|
||||
@ -730,6 +754,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.authorNameNode.alpha = 0.0
|
||||
self.deleteButton.alpha = 0.0
|
||||
self.actionButton.alpha = 0.0
|
||||
self.editButton.alpha = 0.0
|
||||
self.backwardButton.alpha = 0.0
|
||||
self.forwardButton.alpha = 0.0
|
||||
self.statusNode.alpha = 0.0
|
||||
@ -1049,6 +1074,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
}
|
||||
}
|
||||
|
||||
@objc func editButtonPressed() {
|
||||
guard let message = self.currentMessage else {
|
||||
return
|
||||
}
|
||||
self.controllerInteraction?.editMedia(message.id)
|
||||
}
|
||||
|
||||
@objc func playbackControlPressed() {
|
||||
self.playbackControl?()
|
||||
}
|
||||
|
@ -441,9 +441,9 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
namespaces = .not(Namespaces.Message.allScheduled)
|
||||
}
|
||||
return context.account.postbox.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(message!.index), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, appendMessagesFromTheSameGroup: false, namespaces: namespaces, orderStatistics: [.combinedLocation])
|
||||
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
|
||||
let mapped = GalleryMessageHistoryView.view(view)
|
||||
return .single(mapped)
|
||||
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
|
||||
let mapped = GalleryMessageHistoryView.view(view)
|
||||
return .single(mapped)
|
||||
}
|
||||
} else {
|
||||
return .single(GalleryMessageHistoryView.entries([MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))], false, false))
|
||||
@ -911,6 +911,11 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
if let strongSelf = self {
|
||||
strongSelf.replaceRootController(controller, ready)
|
||||
}
|
||||
}, editMedia: { [weak self] messageId in
|
||||
if let strongSelf = self {
|
||||
strongSelf.dismiss(forceAway: true)
|
||||
strongSelf.actionInteraction?.editMedia(messageId)
|
||||
}
|
||||
})
|
||||
self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction)
|
||||
self.displayNodeDidLoad()
|
||||
|
@ -3,16 +3,19 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
|
||||
public final class GalleryControllerInteraction {
|
||||
public let presentController: (ViewController, ViewControllerPresentationArguments?) -> Void
|
||||
public let dismissController: () -> Void
|
||||
public let replaceRootController: (ViewController, Promise<Bool>?) -> Void
|
||||
public let editMedia: (MessageId) -> Void
|
||||
|
||||
public init(presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, dismissController: @escaping () -> Void, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void) {
|
||||
public init(presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, dismissController: @escaping () -> Void, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void, editMedia: @escaping (MessageId) -> Void) {
|
||||
self.presentController = presentController
|
||||
self.dismissController = dismissController
|
||||
self.replaceRootController = replaceRootController
|
||||
self.editMedia = editMedia
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,9 +125,15 @@ public final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDel
|
||||
self.items = items
|
||||
self.itemNodes = itemNodes
|
||||
}
|
||||
|
||||
var updatedIndexOnly = false
|
||||
if let centralIndexAndProgress = self.centralIndexAndProgress, centralIndexAndProgress.0 != centralIndex, centralIndexAndProgress.1 == progress {
|
||||
updatedIndexOnly = true
|
||||
}
|
||||
|
||||
self.centralIndexAndProgress = (centralIndex, progress)
|
||||
if let size = self.currentLayout {
|
||||
self.updateLayout(size: size, transition: .immediate)
|
||||
self.updateLayout(size: size, transition: updatedIndexOnly ? .animated(duration: 0.2, curve: .spring) : .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -530,7 +530,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
let mediaManager = item.context.sharedContext.mediaManager
|
||||
|
||||
let videoNode = UniversalVideoNode(postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .gallery)
|
||||
|
||||
let videoScale: CGFloat
|
||||
if item.content is WebEmbedVideoContent {
|
||||
videoScale = 1.0
|
||||
@ -578,6 +577,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
self.requiresDownload = true
|
||||
var mediaFileStatus: Signal<MediaResourceStatus?, NoError> = .single(nil)
|
||||
|
||||
var hintSeekable = false
|
||||
if let contentInfo = item.contentInfo, case let .message(message) = contentInfo {
|
||||
if Namespaces.Message.allScheduled.contains(message.id.namespace) {
|
||||
disablePictureInPicture = true
|
||||
@ -611,6 +612,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
}
|
||||
if let file = file {
|
||||
for attribute in file.attributes {
|
||||
if case let .Video(duration, _, _) = attribute, duration >= 30 {
|
||||
hintSeekable = true
|
||||
break
|
||||
}
|
||||
}
|
||||
let status = messageMediaFileStatus(context: item.context, messageId: message.id, file: file)
|
||||
if !isWebpage {
|
||||
self.scrubberView.setFetchStatusSignal(status, strings: self.presentationData.strings, decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator, fileSize: file.size)
|
||||
@ -634,7 +641,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
var initialBuffering = false
|
||||
var playing = false
|
||||
var isPaused = true
|
||||
var seekable = false
|
||||
var seekable = hintSeekable
|
||||
var hasStarted = false
|
||||
var displayProgress = true
|
||||
if let value = value {
|
||||
@ -688,7 +695,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
strongSelf.controlsTimer = nil
|
||||
}
|
||||
}
|
||||
seekable = value.duration >= 30.0
|
||||
if !value.duration.isZero {
|
||||
seekable = value.duration >= 30.0
|
||||
}
|
||||
}
|
||||
|
||||
if strongSelf.isCentral && playing && strongSelf.previousPlaying != true && !disablePlayerControls {
|
||||
@ -749,7 +758,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
if hasStarted || strongSelf.didPause {
|
||||
strongSelf.footerContentNode.content = .playback(paused: true, seekable: seekable)
|
||||
} else if let fetchStatus = fetchStatus, !strongSelf.requiresDownload {
|
||||
strongSelf.footerContentNode.content = .fetch(status: fetchStatus)
|
||||
strongSelf.footerContentNode.content = .fetch(status: fetchStatus, seekable: seekable)
|
||||
}
|
||||
} else {
|
||||
strongSelf.footerContentNode.content = .playback(paused: false, seekable: seekable)
|
||||
@ -773,11 +782,17 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
self._rightBarButtonItems.set(.single(barButtonItems))
|
||||
|
||||
videoNode.playbackCompleted = { [weak videoNode] in
|
||||
videoNode.playbackCompleted = { [weak self, weak videoNode] in
|
||||
Queue.mainQueue().async {
|
||||
item.playbackCompleted()
|
||||
if !isAnimated {
|
||||
if let strongSelf = self, !isAnimated {
|
||||
videoNode?.seek(0.0)
|
||||
|
||||
if strongSelf.actionAtEnd == .stop {
|
||||
strongSelf.updateControlsVisibility(true)
|
||||
strongSelf.controlsTimer?.invalidate()
|
||||
strongSelf.controlsTimer = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -208,6 +208,7 @@ public final class SecretMediaPreviewController: ViewController {
|
||||
}, dismissController: { [weak self] in
|
||||
self?.dismiss(forceAway: true)
|
||||
}, replaceRootController: { _, _ in
|
||||
}, editMedia: { _ in
|
||||
})
|
||||
self.displayNode = SecretMediaPreviewControllerNode(controllerInteraction: controllerInteraction)
|
||||
self.displayNodeDidLoad()
|
||||
|
@ -358,12 +358,13 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
|
||||
if let strongSelf = self {
|
||||
strongSelf.present(controller, in: .window(.root), with: arguments, blockInteraction: true)
|
||||
}
|
||||
}, dismissController: { [weak self] in
|
||||
self?.dismiss(forceAway: true)
|
||||
}, replaceRootController: { [weak self] controller, ready in
|
||||
if let strongSelf = self {
|
||||
strongSelf.replaceRootController(controller, ready)
|
||||
}
|
||||
}, dismissController: { [weak self] in
|
||||
self?.dismiss(forceAway: true)
|
||||
}, replaceRootController: { [weak self] controller, ready in
|
||||
if let strongSelf = self {
|
||||
strongSelf.replaceRootController(controller, ready)
|
||||
}
|
||||
}, editMedia: { _ in
|
||||
})
|
||||
self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction)
|
||||
self.displayNodeDidLoad()
|
||||
|
@ -50,4 +50,6 @@
|
||||
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context items:(NSArray *)items focusItem:(id<TGModernGalleryItem>)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions hasSelectionPanel:(bool)hasSelectionPanel hasCamera:(bool)hasCamera recipientName:(NSString *)recipientName;
|
||||
|
||||
- (void)presentPhotoEditorForItem:(id<TGModernGalleryEditableItem>)item tab:(TGPhotoEditorTab)tab;
|
||||
|
||||
@end
|
||||
|
@ -4,6 +4,6 @@
|
||||
|
||||
+ (void)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo dismissed:(void (^)(void))dismissed;
|
||||
|
||||
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed;
|
||||
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item paint:(bool)paint recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed;
|
||||
|
||||
@end
|
||||
|
@ -69,7 +69,7 @@
|
||||
|
||||
+ (UIImage *)paintIcon
|
||||
{
|
||||
return TGTintedImage([UIImage imageNamed:@"Editor/Drawing"], [self toolbarIconColor]);
|
||||
return TGTintedImage([UIImage imageNamed:@"Editor/BrushSelectedPen"], [self toolbarIconColor]);
|
||||
}
|
||||
|
||||
+ (UIImage *)stickerIcon
|
||||
|
@ -102,7 +102,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed
|
||||
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item paint:(bool)paint recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed
|
||||
{
|
||||
id<LegacyComponentsOverlayWindowManager> windowManager = [context makeOverlayWindowManager];
|
||||
id<LegacyComponentsContext> windowContext = [windowManager context];
|
||||
@ -173,14 +173,6 @@
|
||||
completion(item.asset, editingContext);
|
||||
|
||||
[strongController dismissWhenReadyAnimated:true];
|
||||
|
||||
/*[UIView animateWithDuration:0.3f delay:0.0f options:(7 << 16) animations:^
|
||||
{
|
||||
strongController.view.frame = CGRectOffset(strongController.view.frame, 0, strongController.view.frame.size.height);
|
||||
} completion:^(__unused BOOL finished)
|
||||
{
|
||||
[strongController dismiss];
|
||||
}];*/
|
||||
};
|
||||
|
||||
galleryController.beginTransitionIn = ^UIView *(__unused TGMediaPickerGalleryItem *item, __unused TGModernGalleryItemView *itemView)
|
||||
@ -207,10 +199,14 @@
|
||||
dismissed();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:controller contentController:galleryController];
|
||||
controllerWindow.hidden = false;
|
||||
galleryController.view.clipsToBounds = true;
|
||||
|
||||
// if (paint) {
|
||||
// [model presentPhotoEditorForItem:galleryItem tab:TGPhotoEditorPaintTab];
|
||||
// }
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -59,6 +59,60 @@ public enum LegacyAttachmentMenuMediaEditing {
|
||||
case file
|
||||
}
|
||||
|
||||
public func legacyMediaEditor(context: AccountContext, peer: Peer, media: AnyMediaReference, initialCaption: String, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||
let _ = (fetchMediaData(context: context, postbox: context.account.postbox, mediaReference: media)
|
||||
|> deliverOnMainQueue).start(next: { (value, isImage) in
|
||||
guard case let .data(data) = value, data.complete else {
|
||||
return
|
||||
}
|
||||
|
||||
let item: TGMediaEditableItem & TGMediaSelectableItem
|
||||
if let image = UIImage(contentsOfFile: data.path) {
|
||||
item = TGCameraCapturedPhoto(existing: image)
|
||||
} else {
|
||||
item = TGCameraCapturedVideo(url: URL(fileURLWithPath: data.path))
|
||||
}
|
||||
|
||||
let paintStickersContext = LegacyPaintStickersContext(context: context)
|
||||
paintStickersContext.presentStickersController = { completion in
|
||||
return presentStickers({ file, animated, view, rect in
|
||||
let coder = PostboxEncoder()
|
||||
coder.encodeRootObject(file)
|
||||
completion?(coder.makeData(), animated, view, rect)
|
||||
})
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let recipientName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil)
|
||||
legacyController.blocksBackgroundWhenInOverlay = true
|
||||
legacyController.acceptsFocusWhenInOverlay = true
|
||||
legacyController.statusBar.statusBarStyle = .Ignore
|
||||
legacyController.controllerLoaded = { [weak legacyController] in
|
||||
legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||
}
|
||||
|
||||
let emptyController = LegacyEmptyController(context: legacyController.context)!
|
||||
emptyController.navigationBarShouldBeHidden = true
|
||||
let navigationController = makeLegacyNavigationController(rootController: emptyController)
|
||||
navigationController.setNavigationBarHidden(true, animated: false)
|
||||
legacyController.bind(controller: navigationController)
|
||||
|
||||
legacyController.enableSizeClassSignal = true
|
||||
|
||||
present(legacyController, nil)
|
||||
|
||||
TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: initialCaption, entities: [], withItem: item, paint: true, recipientName: recipientName, stickersContext: paintStickersContext, completion: { result, editingContext in
|
||||
let intent: TGMediaAssetsControllerIntent = TGMediaAssetsControllerSendMediaIntent
|
||||
let signals = TGCameraController.resultSignals(for: nil, editingContext: editingContext, currentItem: result as! TGMediaSelectableItem, storeAssets: false, saveEditedPhotos: false, descriptionGenerator: legacyAssetPickerItemGenerator())
|
||||
sendMessagesWithSignals(signals, false, 0)
|
||||
}, dismissed: { [weak legacyController] in
|
||||
legacyController?.dismiss()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocation: ChatLocation, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, presentationData: PresentationData, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController {
|
||||
let defaultVideoPreset = defaultVideoPresetForContext(context)
|
||||
UserDefaults.standard.set(defaultVideoPreset.rawValue as NSNumber, forKey: "TG_preferredVideoPreset_v0")
|
||||
@ -261,45 +315,14 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocati
|
||||
|
||||
present(legacyController, nil)
|
||||
|
||||
TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: "", entities: [], withItem: item, recipientName: recipientName, stickersContext: paintStickersContext, completion: { result, editingContext in
|
||||
TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: "", entities: [], withItem: item, paint: false, recipientName: recipientName, stickersContext: paintStickersContext, completion: { result, editingContext in
|
||||
let intent: TGMediaAssetsControllerIntent = TGMediaAssetsControllerSendMediaIntent
|
||||
let signals = TGCameraController.resultSignals(for: nil, editingContext: editingContext, currentItem: result as! TGMediaSelectableItem, storeAssets: false, saveEditedPhotos: false, descriptionGenerator: legacyAssetPickerItemGenerator())
|
||||
sendMessagesWithSignals(signals, false, 0)
|
||||
/*
|
||||
[TGCameraController resultSignalsForSelectionContext:nil editingContext:editingContext currentItem:result storeAssets:false saveEditedPhotos:false descriptionGenerator:^id(id result, NSString *caption, NSArray *entities, NSString *hash)
|
||||
{
|
||||
__strong TGModernConversationController *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return nil;
|
||||
|
||||
NSDictionary *desc = [strongSelf _descriptionForItem:result caption:caption entities:entities hash:hash allowRemoteCache:allowRemoteCache];
|
||||
return [strongSelf _descriptionForReplacingMedia:desc message:message];
|
||||
}]]
|
||||
*/
|
||||
//let signals = TGMediaAssetsController.resultSignals(for: nil, editingContext: editingContext, intent: intent, currentItem: result, storeAssets: true, useMediaCache: false, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: saveEditedPhotos)
|
||||
//sendMessagesWithSignals(signals, silentPosting, scheduleTime)
|
||||
}, dismissed: { [weak legacyController] in
|
||||
legacyController?.dismiss()
|
||||
})
|
||||
})
|
||||
/*
|
||||
|
||||
|
||||
bool allowRemoteCache = [strongSelf->_companion controllerShouldCacheServerAssets];
|
||||
[TGPhotoVideoEditor presentWithContext:[TGLegacyComponentsContext shared] controller:strongSelf caption:text entities:entities withItem:item recipientName:[strongSelf->_companion title] completion:^(id result, TGMediaEditingContext *editingContext)
|
||||
{
|
||||
[strongSelf _asyncProcessMediaAssetSignals:[TGCameraController resultSignalsForSelectionContext:nil editingContext:editingContext currentItem:result storeAssets:false saveEditedPhotos:false descriptionGenerator:^id(id result, NSString *caption, NSArray *entities, NSString *hash)
|
||||
{
|
||||
__strong TGModernConversationController *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return nil;
|
||||
|
||||
NSDictionary *desc = [strongSelf _descriptionForItem:result caption:caption entities:entities hash:hash allowRemoteCache:allowRemoteCache];
|
||||
return [strongSelf _descriptionForReplacingMedia:desc message:message];
|
||||
}]];
|
||||
[strongSelf endMessageEditing:true];
|
||||
}];
|
||||
*/
|
||||
})!
|
||||
itemViews.append(editCurrentItem)
|
||||
}
|
||||
|
@ -6,32 +6,6 @@ import LegacyComponents
|
||||
import TelegramPresentationData
|
||||
import LegacyUI
|
||||
|
||||
public func presentLegacyAvatarEditor(theme: PresentationTheme, image: UIImage?, video: URL?, present: (ViewController, Any?) -> Void, imageCompletion: @escaping (UIImage) -> Void, videoCompletion: @escaping (UIImage, URL, TGVideoEditAdjustments?) -> Void) {
|
||||
let legacyController = LegacyController(presentation: .custom, theme: theme)
|
||||
legacyController.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
let emptyController = LegacyEmptyController(context: legacyController.context)!
|
||||
let navigationController = makeLegacyNavigationController(rootController: emptyController)
|
||||
navigationController.setNavigationBarHidden(true, animated: false)
|
||||
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0)
|
||||
|
||||
legacyController.bind(controller: navigationController)
|
||||
|
||||
present(legacyController, nil)
|
||||
|
||||
TGPhotoVideoEditor.present(with: legacyController.context, parentController: emptyController, image: image, video: video, didFinishWithImage: { image in
|
||||
if let image = image {
|
||||
imageCompletion(image)
|
||||
}
|
||||
}, didFinishWithVideo: { image, asset, adjustments in
|
||||
if let image = image {
|
||||
// videoCompletion(image, url, adjustments)
|
||||
}
|
||||
}, dismissed: { [weak legacyController] in
|
||||
legacyController?.dismiss()
|
||||
})
|
||||
}
|
||||
|
||||
public func presentLegacyAvatarPicker(holder: Atomic<NSObject?>, signup: Bool, theme: PresentationTheme, present: (ViewController, Any?) -> Void, openCurrent: (() -> Void)?, completion: @escaping (UIImage) -> Void, videoCompletion: @escaping (UIImage, Any?, TGVideoEditAdjustments?) -> Void = { _, _, _ in}) {
|
||||
let legacyController = LegacyController(presentation: .custom, theme: theme)
|
||||
legacyController.statusBar.statusBarStyle = .Ignore
|
||||
|
@ -177,12 +177,13 @@ class SecureIdDocumentGalleryController: ViewController, StandalonePresentableCo
|
||||
if let strongSelf = self {
|
||||
strongSelf.present(controller, in: .window(.root), with: arguments, blockInteraction: true)
|
||||
}
|
||||
}, dismissController: { [weak self] in
|
||||
self?.dismiss(forceAway: true)
|
||||
}, replaceRootController: { [weak self] controller, ready in
|
||||
if let strongSelf = self {
|
||||
strongSelf.replaceRootController(controller, ready)
|
||||
}
|
||||
}, dismissController: { [weak self] in
|
||||
self?.dismiss(forceAway: true)
|
||||
}, replaceRootController: { [weak self] controller, ready in
|
||||
if let strongSelf = self {
|
||||
strongSelf.replaceRootController(controller, ready)
|
||||
}
|
||||
}, editMedia: { _ in
|
||||
})
|
||||
self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction)
|
||||
self.displayNodeDidLoad()
|
||||
|
@ -517,6 +517,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
|
||||
if let strongSelf = self {
|
||||
strongSelf.replaceRootController(controller, ready)
|
||||
}
|
||||
}, editMedia: { _ in
|
||||
})
|
||||
self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction)
|
||||
self.displayNodeDidLoad()
|
||||
|
@ -522,15 +522,15 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
|
||||
let twoStepAuthDataValue = Promise<TwoStepVerificationAccessConfiguration?>(nil)
|
||||
let hasTwoStepAuthDataValue = twoStepAuthDataValue.get()
|
||||
|> map { data -> Bool? in
|
||||
|> mapToSignal { data -> Signal<Bool?, NoError> in
|
||||
if let data = data {
|
||||
if case .set = data {
|
||||
return true
|
||||
return .single(true)
|
||||
} else {
|
||||
return false
|
||||
return .single(false)
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -312,6 +312,7 @@ public class WallpaperGalleryController: ViewController {
|
||||
}, dismissController: { [weak self] in
|
||||
self?.dismiss(forceAway: true)
|
||||
}, replaceRootController: { controller, ready in
|
||||
}, editMedia: { _ in
|
||||
})
|
||||
self.displayNode = WallpaperGalleryControllerNode(controllerInteraction: controllerInteraction, pageGap: 0.0)
|
||||
self.displayNodeDidLoad()
|
||||
|
@ -296,6 +296,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-374917894] = { return Api.PhotoSize.parse_photoCachedSize($0) }
|
||||
dict[-525288402] = { return Api.PhotoSize.parse_photoStrippedSize($0) }
|
||||
dict[1520986705] = { return Api.PhotoSize.parse_photoSizeProgressive($0) }
|
||||
dict[-668906175] = { return Api.PhotoSize.parse_photoPathSize($0) }
|
||||
dict[-244016606] = { return Api.messages.Stickers.parse_stickersNotModified($0) }
|
||||
dict[-463889475] = { return Api.messages.Stickers.parse_stickers($0) }
|
||||
dict[-1096616924] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) }
|
||||
|
@ -9467,6 +9467,7 @@ public extension Api {
|
||||
case photoCachedSize(type: String, location: Api.FileLocation, w: Int32, h: Int32, bytes: Buffer)
|
||||
case photoStrippedSize(type: String, bytes: Buffer)
|
||||
case photoSizeProgressive(type: String, location: Api.FileLocation, w: Int32, h: Int32, sizes: [Int32])
|
||||
case photoPathSize(type: String, bytes: Buffer)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -9517,6 +9518,13 @@ public extension Api {
|
||||
serializeInt32(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
break
|
||||
case .photoPathSize(let type, let bytes):
|
||||
if boxed {
|
||||
buffer.appendInt32(-668906175)
|
||||
}
|
||||
serializeString(type, buffer: buffer, boxed: false)
|
||||
serializeBytes(bytes, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -9532,6 +9540,8 @@ public extension Api {
|
||||
return ("photoStrippedSize", [("type", type), ("bytes", bytes)])
|
||||
case .photoSizeProgressive(let type, let location, let w, let h, let sizes):
|
||||
return ("photoSizeProgressive", [("type", type), ("location", location), ("w", w), ("h", h), ("sizes", sizes)])
|
||||
case .photoPathSize(let type, let bytes):
|
||||
return ("photoPathSize", [("type", type), ("bytes", bytes)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -9637,6 +9647,20 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_photoPathSize(_ reader: BufferReader) -> PhotoSize? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: Buffer?
|
||||
_2 = parseBytes(reader)
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.PhotoSize.photoPathSize(type: _1!, bytes: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum GlobalPrivacySettings: TypeConstructorDescription {
|
||||
|
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
||||
|
||||
public class Serialization: NSObject, MTSerialization {
|
||||
public func currentLayer() -> UInt {
|
||||
return 120
|
||||
return 121
|
||||
}
|
||||
|
||||
public func parseMessage(_ data: Data!) -> Any! {
|
||||
|
@ -5,7 +5,7 @@ import SwiftSignalKit
|
||||
import SyncCore
|
||||
import MtProtoKit
|
||||
|
||||
func telegramStickerPachThumbnailRepresentationFromApiSize(datacenterId: Int32, size: Api.PhotoSize) -> TelegramMediaImageRepresentation? {
|
||||
func telegramStickerPackThumbnailRepresentationFromApiSize(datacenterId: Int32, size: Api.PhotoSize) -> TelegramMediaImageRepresentation? {
|
||||
switch size {
|
||||
case let .photoCachedSize(_, location, w, h, _):
|
||||
switch location {
|
||||
@ -25,6 +25,8 @@ func telegramStickerPachThumbnailRepresentationFromApiSize(datacenterId: Int32,
|
||||
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, volumeId: volumeId, localId: localId)
|
||||
return TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: sizes)
|
||||
}
|
||||
case let .photoPathSize(_, data):
|
||||
return nil
|
||||
case .photoStrippedSize:
|
||||
return nil
|
||||
case .photoSizeEmpty:
|
||||
@ -49,7 +51,7 @@ extension StickerPackCollectionInfo {
|
||||
|
||||
var thumbnailRepresentation: TelegramMediaImageRepresentation?
|
||||
if let thumb = thumb, let thumbDcId = thumbDcId {
|
||||
thumbnailRepresentation = telegramStickerPachThumbnailRepresentationFromApiSize(datacenterId: thumbDcId, size: thumb)
|
||||
thumbnailRepresentation = telegramStickerPackThumbnailRepresentationFromApiSize(datacenterId: thumbDcId, size: thumb)
|
||||
}
|
||||
|
||||
self.init(id: ItemCollectionId(namespace: namespace, id: id), flags: setFlags, accessHash: accessHash, title: title, shortName: shortName, thumbnail: thumbnailRepresentation, hash: nHash, count: count)
|
||||
|
@ -143,6 +143,8 @@ func telegramMediaFileThumbnailRepresentationsFromApiSizes(datacenterId: Int32,
|
||||
let resource = CloudDocumentSizeMediaResource(datacenterId: datacenterId, documentId: documentId, accessHash: accessHash, sizeSpec: type, volumeId: volumeId, localId: localId, fileReference: fileReference)
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: sizes))
|
||||
}
|
||||
case let .photoPathSize(_, data):
|
||||
immediateThumbnailData = data.makeData()
|
||||
case let .photoStrippedSize(_, data):
|
||||
immediateThumbnailData = data.makeData()
|
||||
case .photoSizeEmpty:
|
||||
|
@ -31,6 +31,8 @@ func telegramMediaImageRepresentationsFromApiSizes(datacenterId: Int32, photoId:
|
||||
}
|
||||
case let .photoStrippedSize(_, data):
|
||||
immediateThumbnailData = data.makeData()
|
||||
case .photoPathSize:
|
||||
break
|
||||
case .photoSizeEmpty:
|
||||
break
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Caption.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Caption.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_menu_font1.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Caption.imageset/ic_menu_font1.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Caption.imageset/ic_menu_font1.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Draw.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Draw.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_menu_brush1.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Draw.imageset/ic_menu_brush1.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Draw.imageset/ic_menu_brush1.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replace.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replace.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_menu_replace.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replace.imageset/ic_menu_replace.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replace.imageset/ic_menu_replace.pdf
vendored
Normal file
Binary file not shown.
@ -1,9 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Media Gallery/Draw.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Media Gallery/Draw.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_editor_brush1.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Media Gallery/Draw.imageset/ic_editor_brush1.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Media Gallery/Draw.imageset/ic_editor_brush1.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
@ -615,6 +615,36 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
storedState = MediaPlaybackStoredState(timestamp: timestamp, playbackRate: .x1)
|
||||
}
|
||||
let _ = updateMediaPlaybackStoredStateInteractively(postbox: strongSelf.context.account.postbox, messageId: messageId, state: storedState).start()
|
||||
}, editMedia: { [weak self] messageId in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> Message? in
|
||||
return transaction.getMessage(messageId)
|
||||
} |> deliverOnMainQueue).start(next: { [weak self] message in
|
||||
guard let strongSelf = self, let message = message else {
|
||||
return
|
||||
}
|
||||
|
||||
var mediaReference: AnyMediaReference?
|
||||
for m in message.media {
|
||||
if let image = m as? TelegramMediaImage {
|
||||
mediaReference = AnyMediaReference.standalone(media: image)
|
||||
}
|
||||
}
|
||||
|
||||
if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] {
|
||||
legacyMediaEditor(context: strongSelf.context, peer: peer, media: mediaReference, initialCaption: message.text, presentStickers: { _ in return nil }, sendMessagesWithSignals: { [weak self] signals, _, _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.interfaceInteraction?.setupEditMessage(messageId, { _ in })
|
||||
strongSelf.editMessageMediaWithLegacySignals(signals!)
|
||||
}
|
||||
}, present: { [weak self] c, a in
|
||||
self?.present(c, in: .window(.root), with: a)
|
||||
})
|
||||
}
|
||||
})
|
||||
})))
|
||||
}, openPeer: { [weak self] id, navigation, fromMessage in
|
||||
self?.openPeer(peerId: id, navigation: navigation, fromMessage: fromMessage)
|
||||
@ -643,7 +673,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
break
|
||||
}
|
||||
}
|
||||
let _ = combineLatest(queue: .mainQueue(), contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: strongSelf.presentationInterfaceState, context: strongSelf.context, messages: updatedMessages, controllerInteraction: strongSelf.controllerInteraction, selectAll: selectAll, interfaceInteraction: strongSelf.interfaceInteraction), loadedStickerPack(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, reference: .animatedEmoji, forceActualized: false), ApplicationSpecificNotice.getChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager)
|
||||
let _ = combineLatest(queue: .mainQueue(), contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: strongSelf.presentationInterfaceState, context: strongSelf.context, messages: updatedMessages, controllerInteraction: strongSelf.controllerInteraction, selectAll: selectAll, interfaceInteraction: strongSelf.interfaceInteraction), loadedStickerPack(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, reference: .animatedEmoji, forceActualized: false), ApplicationSpecificNotice.getChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager)
|
||||
).start(next: { actions, animatedEmojiStickers, chatTextSelectionTips in
|
||||
guard let strongSelf = self, !actions.isEmpty else {
|
||||
return
|
||||
@ -2275,6 +2305,45 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
strongSelf.push(messageStatsController(context: context, messageId: id, cachedPeerData: cachedPeerData))
|
||||
})
|
||||
}, editMessageMedia: { [weak self] messageId, draw in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.chatDisplayNode.dismissInput()
|
||||
|
||||
if draw {
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> Message? in
|
||||
return transaction.getMessage(messageId)
|
||||
} |> deliverOnMainQueue).start(next: { [weak self] message in
|
||||
guard let strongSelf = self, let message = message else {
|
||||
return
|
||||
}
|
||||
|
||||
var mediaReference: AnyMediaReference?
|
||||
for m in message.media {
|
||||
if let image = m as? TelegramMediaImage {
|
||||
mediaReference = AnyMediaReference.standalone(media: image)
|
||||
}
|
||||
}
|
||||
|
||||
if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] {
|
||||
legacyMediaEditor(context: strongSelf.context, peer: peer, media: mediaReference, initialCaption: message.text, presentStickers: { _ in return nil }, sendMessagesWithSignals: { [weak self] signals, _, _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.interfaceInteraction?.setupEditMessage(messageId, { _ in })
|
||||
strongSelf.editMessageMediaWithLegacySignals(signals!)
|
||||
}
|
||||
}, present: { [weak self] c, a in
|
||||
self?.present(c, in: .window(.root), with: a)
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
strongSelf.presentMediaPicker(fileMode: false, editingMedia: true, completion: { signals, _, _ in
|
||||
self?.interfaceInteraction?.setupEditMessage(messageId, { _ in })
|
||||
self?.editMessageMediaWithLegacySignals(signals)
|
||||
})
|
||||
}
|
||||
}, requestMessageUpdate: { [weak self] id in
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
|
||||
|
@ -116,6 +116,7 @@ public final class ChatControllerInteraction {
|
||||
let openMessageReplies: (MessageId, Bool, Bool) -> Void
|
||||
let openReplyThreadOriginalMessage: (Message) -> Void
|
||||
let openMessageStats: (MessageId) -> Void
|
||||
let editMessageMedia: (MessageId, Bool) -> Void
|
||||
|
||||
let requestMessageUpdate: (MessageId) -> Void
|
||||
let cancelInteractiveKeyboardGestures: () -> Void
|
||||
@ -203,6 +204,7 @@ public final class ChatControllerInteraction {
|
||||
openMessageReplies: @escaping (MessageId, Bool, Bool) -> Void,
|
||||
openReplyThreadOriginalMessage: @escaping (Message) -> Void,
|
||||
openMessageStats: @escaping (MessageId) -> Void,
|
||||
editMessageMedia: @escaping (MessageId, Bool) -> Void,
|
||||
requestMessageUpdate: @escaping (MessageId) -> Void,
|
||||
cancelInteractiveKeyboardGestures: @escaping () -> Void,
|
||||
automaticMediaDownloadSettings: MediaAutoDownloadSettings,
|
||||
@ -277,6 +279,7 @@ public final class ChatControllerInteraction {
|
||||
self.openMessageReplies = openMessageReplies
|
||||
self.openReplyThreadOriginalMessage = openReplyThreadOriginalMessage
|
||||
self.openMessageStats = openMessageStats
|
||||
self.editMessageMedia = editMessageMedia
|
||||
|
||||
self.requestMessageUpdate = requestMessageUpdate
|
||||
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
||||
@ -328,6 +331,7 @@ public final class ChatControllerInteraction {
|
||||
}, openMessageReplies: { _, _, _ in
|
||||
}, openReplyThreadOriginalMessage: { _ in
|
||||
}, openMessageStats: { _ in
|
||||
}, editMessageMedia: { _, _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
@ -276,7 +276,7 @@ func updatedChatEditInterfaceMessageState(state: ChatPresentationInterfaceState,
|
||||
return updated
|
||||
}
|
||||
|
||||
func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, messages: [Message], controllerInteraction: ChatControllerInteraction?, selectAll: Bool, interfaceInteraction: ChatPanelInterfaceInteraction?) -> Signal<[ContextMenuItem], NoError> {
|
||||
func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, messages: [Message], controllerInteraction: ChatControllerInteraction?, selectAll: Bool, interfaceInteraction: ChatPanelInterfaceInteraction?) -> Signal<[ContextMenuItem], NoError> {
|
||||
guard let interfaceInteraction = interfaceInteraction, let controllerInteraction = controllerInteraction else {
|
||||
return .single([])
|
||||
}
|
||||
@ -643,12 +643,79 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
|
||||
}
|
||||
|
||||
if data.canEdit && !isPinnedMessages {
|
||||
var mediaReference: AnyMediaReference?
|
||||
for media in message.media {
|
||||
if let image = media as? TelegramMediaImage, let _ = largestImageRepresentation(image.representations) {
|
||||
mediaReference = ImageMediaReference.standalone(media: image).abstract
|
||||
break
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
mediaReference = FileMediaReference.standalone(media: file).abstract
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_MessageDialogEdit, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
interfaceInteraction.setupEditMessage(messages[0].id, { transition in
|
||||
f(.custom(transition))
|
||||
})
|
||||
}, action: { c, f in
|
||||
if let _ = mediaReference?.media as? TelegramMediaImage {
|
||||
var updatedItems: [ContextMenuItem] = []
|
||||
updatedItems.append(.action(ContextMenuActionItem(text: message.text.isEmpty ? chatPresentationInterfaceState.strings.Conversation_AddCaption : chatPresentationInterfaceState.strings.Conversation_EditCaption, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Caption"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
interfaceInteraction.setupEditMessage(messages[0].id, { transition in
|
||||
f(.custom(transition))
|
||||
})
|
||||
})))
|
||||
updatedItems.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_EditThisPhoto, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Draw"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
controllerInteraction.editMessageMedia(messages[0].id, true)
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
updatedItems.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ReplacePhoto, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
controllerInteraction.editMessageMedia(messages[0].id, false)
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
|
||||
updatedItems.append(.separator)
|
||||
updatedItems.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ChatList_Context_Back, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { c, _ in
|
||||
c.setItems(contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: chatPresentationInterfaceState, context: context, messages: messages, controllerInteraction: controllerInteraction, selectAll: selectAll, interfaceInteraction: interfaceInteraction))
|
||||
})))
|
||||
|
||||
c.setItems(.single(updatedItems))
|
||||
} else if let _ = mediaReference?.media as? TelegramMediaFile {
|
||||
var updatedItems: [ContextMenuItem] = []
|
||||
updatedItems.append(.action(ContextMenuActionItem(text: message.text.isEmpty ? chatPresentationInterfaceState.strings.Conversation_AddCaption : chatPresentationInterfaceState.strings.Conversation_EditCaption, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Caption"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
interfaceInteraction.setupEditMessage(messages[0].id, { transition in
|
||||
f(.custom(transition))
|
||||
})
|
||||
})))
|
||||
updatedItems.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ReplaceFile, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
controllerInteraction.editMessageMedia(messages[0].id, false)
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
|
||||
updatedItems.append(.separator)
|
||||
updatedItems.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ChatList_Context_Back, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { c, _ in
|
||||
c.setItems(contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: chatPresentationInterfaceState, context: context, messages: messages, controllerInteraction: controllerInteraction, selectAll: selectAll, interfaceInteraction: interfaceInteraction))
|
||||
})))
|
||||
|
||||
c.setItems(.single(updatedItems))
|
||||
} else {
|
||||
interfaceInteraction.setupEditMessage(messages[0].id, { transition in
|
||||
f(.custom(transition))
|
||||
})
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
|
@ -175,7 +175,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
||||
private var currentSize: CGSize?
|
||||
let imageNode: TransformImageNode
|
||||
var animationNode: AnimatedStickerNode?
|
||||
private var placeholderNode: ShimmerEffectNode?
|
||||
private var placeholderNode: StickerShimmerEffectNode?
|
||||
private var didSetUpAnimationNode = false
|
||||
private var item: ChatMediaInputStickerGridItem?
|
||||
|
||||
@ -202,7 +202,8 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
||||
|
||||
override init() {
|
||||
self.imageNode = TransformImageNode()
|
||||
self.placeholderNode = ShimmerEffectNode()
|
||||
self.placeholderNode = StickerShimmerEffectNode()
|
||||
self.placeholderNode?.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
@ -315,16 +316,16 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
let placeholderFrame = CGRect(origin: CGPoint(x: floor((size.width - boundingSize.width) / 2.0), y: floor((size.height - boundingSize.height) / 2.0)), size: boundingSize)
|
||||
placeholderNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
placeholderNode.frame = placeholderFrame
|
||||
|
||||
let theme = item.theme
|
||||
placeholderNode.update(backgroundColor: theme.chat.inputMediaPanel.stickersBackgroundColor, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputMediaPanel.stickersBackgroundColor, alpha: 0.1), shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3), shapes: [.roundedRect(rect: placeholderFrame, cornerRadius: 10.0)], size: bounds.size)
|
||||
placeholderNode.update(backgroundColor: theme.chat.inputMediaPanel.stickersBackgroundColor, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputMediaPanel.stickersBackgroundColor, alpha: 0.15), shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3), data: item.stickerItem.file.immediateThumbnailData, size: placeholderFrame.size)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
placeholderNode.updateAbsoluteRect(absoluteRect, within: containerSize)
|
||||
placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: absoluteRect.minX + placeholderNode.frame.minX, y: absoluteRect.minY + placeholderNode.frame.minY), size: placeholderNode.frame.size), within: containerSize)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,6 +143,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
private let contextSourceNode: ContextExtractedContentContainingNode
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
let imageNode: TransformImageNode
|
||||
private var placeholderNode: StickerShimmerEffectNode?
|
||||
private var animationNode: GenericAnimatedStickerNode?
|
||||
private var didSetUpAnimationNode = false
|
||||
private var isPlaying = false
|
||||
@ -191,6 +192,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
self.imageNode = TransformImageNode()
|
||||
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
||||
|
||||
self.placeholderNode = StickerShimmerEffectNode()
|
||||
self.placeholderNode?.isUserInteractionEnabled = false
|
||||
|
||||
super.init(layerBacked: false)
|
||||
|
||||
self.containerNode.shouldBegin = { [weak self] location in
|
||||
@ -230,12 +234,28 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var firstTime = true
|
||||
self.imageNode.imageUpdated = { [weak self] image in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if image != nil {
|
||||
strongSelf.removePlaceholder(animated: !firstTime)
|
||||
}
|
||||
firstTime = false
|
||||
}
|
||||
|
||||
self.imageNode.displaysAsynchronously = false
|
||||
self.containerNode.addSubnode(self.contextSourceNode)
|
||||
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||
self.addSubnode(self.containerNode)
|
||||
self.contextSourceNode.contentNode.addSubnode(self.imageNode)
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.contextSourceNode.contentNode.addSubnode(placeholderNode)
|
||||
}
|
||||
|
||||
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
|
||||
|
||||
self.dateAndStatusNode.openReactions = { [weak self] in
|
||||
@ -255,6 +275,20 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func removePlaceholder(animated: Bool) {
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.placeholderNode = nil
|
||||
if !animated {
|
||||
placeholderNode.removeFromSupernode()
|
||||
} else {
|
||||
placeholderNode.alpha = 0.0
|
||||
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in
|
||||
placeholderNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
@ -388,7 +422,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
|
||||
if let animationNode = self.animationNode, !self.animateGreeting {
|
||||
self.contextSourceNode.contentNode.insertSubnode(animationNode, aboveSubnode: self.imageNode)
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.contextSourceNode.contentNode.insertSubnode(animationNode, aboveSubnode: placeholderNode)
|
||||
} else {
|
||||
self.contextSourceNode.contentNode.insertSubnode(animationNode, aboveSubnode: self.imageNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -520,6 +558,20 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
self.updateVisibility()
|
||||
}
|
||||
|
||||
|
||||
private var absoluteRect: (CGRect, CGSize)?
|
||||
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
self.absoluteRect = (rect, containerSize)
|
||||
if !self.contextSourceNode.isExtractedToContextPreview {
|
||||
var rect = rect
|
||||
rect.origin.y = containerSize.height - rect.maxY + self.insets.top
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + placeholderNode.frame.minX, y: rect.minY + placeholderNode.frame.minY), size: placeholderNode.frame.size), within: containerSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, Bool) -> Void) {
|
||||
let displaySize = CGSize(width: 184.0, height: 184.0)
|
||||
let telegramFile = self.telegramFile
|
||||
@ -903,6 +955,20 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.imageNode.frame = updatedContentFrame
|
||||
|
||||
let animationNodeFrame = updatedContentFrame.insetBy(dx: imageInset, dy: imageInset)
|
||||
|
||||
var file: TelegramMediaFile?
|
||||
if let emojiFile = emojiFile {
|
||||
file = emojiFile
|
||||
} else if let telegramFile = telegramFile {
|
||||
file = telegramFile
|
||||
}
|
||||
|
||||
if let immediateThumbnailData = file?.immediateThumbnailData, let placeholderNode = strongSelf.placeholderNode {
|
||||
placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0x748391, alpha: 0.2), shimmeringColor: UIColor(rgb: 0x748391, alpha: 0.35), data: immediateThumbnailData, size: animationNodeFrame.size)
|
||||
placeholderNode.frame = animationNodeFrame
|
||||
strongSelf.animationNode?.isHidden = true
|
||||
}
|
||||
|
||||
if let animationNode = strongSelf.animationNode, let parentNode = strongSelf.greetingStickerParentNode, strongSelf.animateGreeting {
|
||||
strongSelf.animateGreeting = false
|
||||
|
||||
@ -1117,7 +1183,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
switch recognizer.state {
|
||||
case .ended:
|
||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||
if let action = self.gestureRecognized(gesture: gesture, location: location, recognizer: nil) {
|
||||
if let action = self.gestureRecognized(gesture: gesture, location: location, recognizer: recognizer) {
|
||||
if case .doubleTap = gesture {
|
||||
self.containerNode.cancelGesture()
|
||||
}
|
||||
@ -1224,7 +1290,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
item.controllerInteraction.displayDiceTooltip(dice)
|
||||
})
|
||||
} else if let _ = self.emojiFile {
|
||||
if let animationNode = self.animationNode as? AnimatedStickerNode {
|
||||
if let animationNode = self.animationNode as? AnimatedStickerNode, let _ = recognizer {
|
||||
var startTime: Signal<Double, NoError>
|
||||
var shouldPlay = false
|
||||
if !animationNode.isPlaying {
|
||||
@ -1248,86 +1314,63 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
|
||||
if let text = self.item?.message.text, let firstScalar = text.unicodeScalars.first {
|
||||
if beatingHearts.contains(firstScalar.value) || firstScalar.value == peach {
|
||||
return .optionalAction({
|
||||
if shouldPlay {
|
||||
animationNode.play()
|
||||
}
|
||||
return .optionalAction({
|
||||
let _ = startTime.start(next: { [weak self] time in
|
||||
let _ = (appConfiguration
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak animationNode] appConfiguration in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
var haptic: EmojiHaptic
|
||||
if let current = strongSelf.haptic {
|
||||
haptic = current
|
||||
} else {
|
||||
if beatingHearts.contains(firstScalar.value) {
|
||||
haptic = HeartbeatHaptic()
|
||||
} else {
|
||||
haptic = PeachHaptic()
|
||||
}
|
||||
haptic.enabled = true
|
||||
strongSelf.haptic = haptic
|
||||
}
|
||||
if !haptic.active {
|
||||
haptic.start(time: time)
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
return .optionalAction({
|
||||
if shouldPlay {
|
||||
let _ = (appConfiguration
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak animationNode] appConfiguration in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let emojiSounds = AnimatedEmojiSoundsConfiguration.with(appConfiguration: appConfiguration, account: item.context.account)
|
||||
for (emoji, file) in emojiSounds.sounds {
|
||||
if emoji.strippedEmoji == text.strippedEmoji {
|
||||
let mediaManager = item.context.sharedContext.mediaManager
|
||||
let mediaPlayer = MediaPlayer(audioSessionManager: mediaManager.audioSession, postbox: item.context.account.postbox, resourceReference: .standalone(resource: file.resource), streamable: .none, video: false, preferSoftwareDecoding: false, enableSound: true, fetchAutomatically: true, ambient: true)
|
||||
mediaPlayer.togglePlayPause()
|
||||
mediaPlayer.actionAtEnd = .action({ [weak self] in
|
||||
self?.mediaPlayer = nil
|
||||
})
|
||||
strongSelf.mediaPlayer = mediaPlayer
|
||||
|
||||
strongSelf.mediaStatusDisposable.set((mediaPlayer.status
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak animationNode] status in
|
||||
if let strongSelf = self {
|
||||
if firstScalar.value == coffin {
|
||||
var haptic: EmojiHaptic
|
||||
if let current = strongSelf.haptic {
|
||||
haptic = current
|
||||
} else {
|
||||
let emojiSounds = AnimatedEmojiSoundsConfiguration.with(appConfiguration: appConfiguration, account: item.context.account)
|
||||
for (emoji, file) in emojiSounds.sounds {
|
||||
if emoji.strippedEmoji == text.strippedEmoji {
|
||||
let mediaManager = item.context.sharedContext.mediaManager
|
||||
let mediaPlayer = MediaPlayer(audioSessionManager: mediaManager.audioSession, postbox: item.context.account.postbox, resourceReference: .standalone(resource: file.resource), streamable: .none, video: false, preferSoftwareDecoding: false, enableSound: true, fetchAutomatically: true, ambient: true)
|
||||
mediaPlayer.togglePlayPause()
|
||||
mediaPlayer.actionAtEnd = .action({ [weak self] in
|
||||
self?.mediaPlayer = nil
|
||||
})
|
||||
strongSelf.mediaPlayer = mediaPlayer
|
||||
|
||||
strongSelf.mediaStatusDisposable.set((mediaPlayer.status
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak animationNode] status in
|
||||
if let strongSelf = self {
|
||||
if firstScalar.value == coffin || firstScalar.value == peach {
|
||||
var haptic: EmojiHaptic
|
||||
if let current = strongSelf.haptic {
|
||||
haptic = current
|
||||
} else {
|
||||
if beatingHearts.contains(firstScalar.value) {
|
||||
haptic = HeartbeatHaptic()
|
||||
} else if firstScalar.value == coffin {
|
||||
haptic = CoffinHaptic()
|
||||
haptic.enabled = true
|
||||
strongSelf.haptic = haptic
|
||||
}
|
||||
if !haptic.active {
|
||||
haptic.start(time: 0.0)
|
||||
} else {
|
||||
haptic = PeachHaptic()
|
||||
}
|
||||
haptic.enabled = true
|
||||
strongSelf.haptic = haptic
|
||||
}
|
||||
|
||||
switch status.status {
|
||||
case .playing:
|
||||
animationNode?.play()
|
||||
strongSelf.mediaStatusDisposable.set(nil)
|
||||
default:
|
||||
break
|
||||
if !haptic.active {
|
||||
haptic.start(time: 0.0)
|
||||
}
|
||||
}
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
switch status.status {
|
||||
case .playing:
|
||||
animationNode?.play()
|
||||
strongSelf.mediaStatusDisposable.set(nil)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}))
|
||||
return
|
||||
}
|
||||
animationNode?.play()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
animationNode?.play()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -248,6 +248,24 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
|
||||
title = nil
|
||||
messageText = rawText
|
||||
}
|
||||
case .video:
|
||||
let rawText = presentationData.strings.PUSH_CHANNEL_MESSAGE_VIDEOS(Int32(item.messages.count), peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), Int32(item.messages.count))
|
||||
if let index = rawText.firstIndex(of: "|") {
|
||||
title = String(rawText[rawText.startIndex ..< index])
|
||||
messageText = String(rawText[rawText.index(after: index)...])
|
||||
} else {
|
||||
title = nil
|
||||
messageText = rawText
|
||||
}
|
||||
case .file:
|
||||
let rawText = presentationData.strings.PUSH_CHANNEL_MESSAGE_DOCS(Int32(item.messages.count), peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), Int32(item.messages.count))
|
||||
if let index = rawText.firstIndex(of: "|") {
|
||||
title = String(rawText[rawText.startIndex ..< index])
|
||||
messageText = String(rawText[rawText.index(after: index)...])
|
||||
} else {
|
||||
title = nil
|
||||
messageText = rawText
|
||||
}
|
||||
default:
|
||||
let rawText = presentationData.strings.PUSH_CHANNEL_MESSAGES(Int32(item.messages.count), peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), Int32(item.messages.count))
|
||||
if let index = rawText.firstIndex(of: "|") {
|
||||
@ -272,6 +290,24 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
|
||||
title = nil
|
||||
messageText = rawText
|
||||
}
|
||||
case .video:
|
||||
let rawText = presentationData.strings.PUSH_CHAT_MESSAGE_VIDEOS(Int32(item.messages.count), author.compactDisplayTitle, peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), Int32(item.messages.count))
|
||||
if let index = rawText.firstIndex(of: "|") {
|
||||
title = String(rawText[rawText.startIndex ..< index])
|
||||
messageText = String(rawText[rawText.index(after: index)...])
|
||||
} else {
|
||||
title = nil
|
||||
messageText = rawText
|
||||
}
|
||||
case .file:
|
||||
let rawText = presentationData.strings.PUSH_CHAT_MESSAGE_DOCS(Int32(item.messages.count), author.compactDisplayTitle, peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), Int32(item.messages.count))
|
||||
if let index = rawText.firstIndex(of: "|") {
|
||||
title = String(rawText[rawText.startIndex ..< index])
|
||||
messageText = String(rawText[rawText.index(after: index)...])
|
||||
} else {
|
||||
title = nil
|
||||
messageText = rawText
|
||||
}
|
||||
default:
|
||||
let rawText = presentationData.strings.PUSH_CHAT_MESSAGES(Int32(item.messages.count), author.compactDisplayTitle, peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), Int32(item.messages.count))
|
||||
if let index = rawText.firstIndex(of: "|") {
|
||||
@ -293,6 +329,24 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
|
||||
title = nil
|
||||
messageText = rawText
|
||||
}
|
||||
case .video:
|
||||
let rawText = presentationData.strings.PUSH_MESSAGE_VIDEOS(Int32(item.messages.count), peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), Int32(item.messages.count))
|
||||
if let index = rawText.firstIndex(of: "|") {
|
||||
title = String(rawText[rawText.startIndex ..< index])
|
||||
messageText = String(rawText[rawText.index(after: index)...])
|
||||
} else {
|
||||
title = nil
|
||||
messageText = rawText
|
||||
}
|
||||
case .file:
|
||||
let rawText = presentationData.strings.PUSH_MESSAGE_DOCS(Int32(item.messages.count), peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), Int32(item.messages.count))
|
||||
if let index = rawText.firstIndex(of: "|") {
|
||||
title = String(rawText[rawText.startIndex ..< index])
|
||||
messageText = String(rawText[rawText.index(after: index)...])
|
||||
} else {
|
||||
title = nil
|
||||
messageText = rawText
|
||||
}
|
||||
default:
|
||||
let rawText = presentationData.strings.PUSH_MESSAGES(Int32(item.messages.count), peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), Int32(item.messages.count))
|
||||
if let index = rawText.firstIndex(of: "|") {
|
||||
|
@ -455,6 +455,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, openMessageReplies: { _, _, _ in
|
||||
}, openReplyThreadOriginalMessage: { _ in
|
||||
}, openMessageStats: { _ in
|
||||
}, editMessageMedia: { _, _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
||||
|
@ -148,6 +148,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
}, openMessageReplies: { _, _, _ in
|
||||
}, openReplyThreadOriginalMessage: { _ in
|
||||
}, openMessageStats: { _ in
|
||||
}, editMessageMedia: { _, _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
@ -114,7 +114,15 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
let controller = ShareController(context: params.context, subject: .media(.standalone(media: file)), immediateExternalShare: true)
|
||||
params.present(controller, nil)
|
||||
} else if let rootController = params.navigationController?.view.window?.rootViewController {
|
||||
presentDocumentPreviewController(rootController: rootController, theme: presentationData.theme, strings: presentationData.strings, postbox: params.context.account.postbox, file: file)
|
||||
let proceed = {
|
||||
presentDocumentPreviewController(rootController: rootController, theme: presentationData.theme, strings: presentationData.strings, postbox: params.context.account.postbox, file: file)
|
||||
}
|
||||
if file.mimeType.contains("image/svg") {
|
||||
let presentationData = params.context.sharedContext.currentPresentationData.with { $0 }
|
||||
params.present(textAlertController(context: params.context, title: nil, text: presentationData.strings.OpenFile_PotentiallyDangerousContentAlert, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.OpenFile_Proceed, action: { proceed() })] ), nil)
|
||||
} else {
|
||||
proceed()
|
||||
}
|
||||
}
|
||||
return true
|
||||
case let .audio(file):
|
||||
@ -217,9 +225,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
let path = params.context.account.postbox.mediaBox.completedResourcePath(media.resource)
|
||||
var previewTheme: PresentationTheme?
|
||||
if let path = path, let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) {
|
||||
let startTime = CACurrentMediaTime()
|
||||
previewTheme = makePresentationTheme(data: data)
|
||||
print("time \(CACurrentMediaTime() - startTime)")
|
||||
}
|
||||
|
||||
guard let theme = previewTheme else {
|
||||
|
@ -138,6 +138,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}, openMessageReplies: { _, _, _ in
|
||||
}, openReplyThreadOriginalMessage: { _ in
|
||||
}, openMessageStats: { _ in
|
||||
}, editMessageMedia: { _, _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))
|
||||
|
@ -1969,6 +1969,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}, openMessageReplies: { _, _, _ in
|
||||
}, openReplyThreadOriginalMessage: { _ in
|
||||
}, openMessageStats: { _ in
|
||||
}, editMessageMedia: { _, _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
@ -1238,6 +1238,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}, openMessageReplies: { _, _, _ in
|
||||
}, openReplyThreadOriginalMessage: { _ in
|
||||
}, openMessageStats: { _ in
|
||||
}, editMessageMedia: { _, _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
605
submodules/TelegramUI/Sources/StickerShimmerEffectNode.swift
Normal file
605
submodules/TelegramUI/Sources/StickerShimmerEffectNode.swift
Normal file
@ -0,0 +1,605 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Postbox
|
||||
import TelegramPresentationData
|
||||
import GZip
|
||||
|
||||
private final class ShimmerEffectForegroundNode: ASDisplayNode {
|
||||
private var currentBackgroundColor: UIColor?
|
||||
private var currentForegroundColor: UIColor?
|
||||
private let imageNodeContainer: ASDisplayNode
|
||||
private let imageNode: ASImageNode
|
||||
|
||||
private var absoluteLocation: (CGRect, CGSize)?
|
||||
private var isCurrentlyInHierarchy = false
|
||||
private var shouldBeAnimating = false
|
||||
|
||||
override init() {
|
||||
self.imageNodeContainer = ASDisplayNode()
|
||||
self.imageNodeContainer.isLayerBacked = true
|
||||
|
||||
self.imageNode = ASImageNode()
|
||||
self.imageNode.isLayerBacked = true
|
||||
self.imageNode.displaysAsynchronously = false
|
||||
self.imageNode.displayWithoutProcessing = true
|
||||
self.imageNode.contentMode = .scaleToFill
|
||||
|
||||
super.init()
|
||||
|
||||
self.isLayerBacked = true
|
||||
self.clipsToBounds = true
|
||||
|
||||
self.imageNodeContainer.addSubnode(self.imageNode)
|
||||
self.addSubnode(self.imageNodeContainer)
|
||||
}
|
||||
|
||||
override func didEnterHierarchy() {
|
||||
super.didEnterHierarchy()
|
||||
|
||||
self.isCurrentlyInHierarchy = true
|
||||
self.updateAnimation()
|
||||
}
|
||||
|
||||
override func didExitHierarchy() {
|
||||
super.didExitHierarchy()
|
||||
|
||||
self.isCurrentlyInHierarchy = false
|
||||
self.updateAnimation()
|
||||
}
|
||||
|
||||
func update(backgroundColor: UIColor, foregroundColor: UIColor) {
|
||||
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor) {
|
||||
return
|
||||
}
|
||||
self.currentBackgroundColor = backgroundColor
|
||||
self.currentForegroundColor = foregroundColor
|
||||
|
||||
let image = generateImage(CGSize(width: 320.0, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
||||
let peakColor = foregroundColor.cgColor
|
||||
|
||||
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
||||
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
|
||||
})
|
||||
self.imageNode.image = image
|
||||
}
|
||||
|
||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
|
||||
return
|
||||
}
|
||||
let sizeUpdated = self.absoluteLocation?.1 != containerSize
|
||||
let frameUpdated = self.absoluteLocation?.0 != rect
|
||||
self.absoluteLocation = (rect, containerSize)
|
||||
|
||||
if sizeUpdated {
|
||||
if self.shouldBeAnimating {
|
||||
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
||||
self.addImageAnimation()
|
||||
} else {
|
||||
self.updateAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
if frameUpdated {
|
||||
self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateAnimation() {
|
||||
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil
|
||||
if shouldBeAnimating != self.shouldBeAnimating {
|
||||
self.shouldBeAnimating = shouldBeAnimating
|
||||
if shouldBeAnimating {
|
||||
self.addImageAnimation()
|
||||
} else {
|
||||
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addImageAnimation() {
|
||||
guard let containerSize = self.absoluteLocation?.1 else {
|
||||
return
|
||||
}
|
||||
let gradientHeight: CGFloat = 320.0
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(x: -gradientHeight, y: 0.0), size: CGSize(width: gradientHeight, height: containerSize.height))
|
||||
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.width + gradientHeight) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
||||
animation.repeatCount = Float.infinity
|
||||
animation.beginTime = 1.0
|
||||
self.imageNode.layer.add(animation, forKey: "shimmer")
|
||||
}
|
||||
}
|
||||
|
||||
class StickerShimmerEffectNode: ASDisplayNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let effectNode: ShimmerEffectForegroundNode
|
||||
private let foregroundNode: ASImageNode
|
||||
|
||||
private var maskView: UIImageView?
|
||||
|
||||
private var currentData: Data?
|
||||
private var currentBackgroundColor: UIColor?
|
||||
private var currentForegroundColor: UIColor?
|
||||
private var currentShimmeringColor: UIColor?
|
||||
private var currentSize = CGSize()
|
||||
|
||||
override init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.effectNode = ShimmerEffectForegroundNode()
|
||||
self.foregroundNode = ASImageNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.effectNode)
|
||||
self.addSubnode(self.foregroundNode)
|
||||
}
|
||||
|
||||
public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
self.effectNode.updateAbsoluteRect(rect, within: containerSize)
|
||||
}
|
||||
|
||||
public func update(backgroundColor: UIColor?, foregroundColor: UIColor, shimmeringColor: UIColor, data: Data?, size: CGSize) {
|
||||
if self.currentData == data, let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor), let currentShimmeringColor = self.currentShimmeringColor, currentShimmeringColor.isEqual(shimmeringColor), self.currentSize == size {
|
||||
return
|
||||
}
|
||||
|
||||
self.currentBackgroundColor = backgroundColor
|
||||
self.currentForegroundColor = foregroundColor
|
||||
self.currentShimmeringColor = shimmeringColor
|
||||
self.currentData = data
|
||||
self.currentSize = size
|
||||
|
||||
self.backgroundNode.backgroundColor = foregroundColor
|
||||
|
||||
self.effectNode.update(backgroundColor: backgroundColor == nil ? .clear : foregroundColor, foregroundColor: shimmeringColor)
|
||||
|
||||
let image = generateImage(size, rotatedContext: { size, context in
|
||||
if let backgroundColor = backgroundColor {
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.setBlendMode(.copy)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
} else {
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(UIColor.black.cgColor)
|
||||
}
|
||||
|
||||
if let data = data, let unpackedData = TGGUnzipData(data, 5 * 1024 * 1024), let path = String(data: unpackedData, encoding: .utf8) {
|
||||
let reader = PathDataReader(input: path)
|
||||
let segments = reader.read()
|
||||
|
||||
var currentX: Double = 0.0
|
||||
var currentY: Double = 0.0
|
||||
|
||||
let mul: Double = Double(size.width) / 512.0
|
||||
|
||||
for segment in segments {
|
||||
switch segment.type {
|
||||
case .M, .m:
|
||||
let x = segment.data[0]
|
||||
let y = segment.data[1]
|
||||
|
||||
if segment.isAbsolute() {
|
||||
currentX = x
|
||||
currentY = y
|
||||
} else {
|
||||
currentX += x
|
||||
currentY += y
|
||||
}
|
||||
|
||||
context.move(to: CGPoint(x: currentX * mul, y: currentY * mul))
|
||||
case .L, .l:
|
||||
let x = segment.data[0]
|
||||
let y = segment.data[1]
|
||||
|
||||
let effectiveX: Double
|
||||
let effectiveY: Double
|
||||
if segment.isAbsolute() {
|
||||
effectiveX = x
|
||||
effectiveY = y
|
||||
} else {
|
||||
effectiveX = currentX + x
|
||||
effectiveY = currentY + y
|
||||
}
|
||||
|
||||
currentX = effectiveX
|
||||
currentY = effectiveY
|
||||
|
||||
context.addLine(to: CGPoint(x: effectiveX * mul, y: effectiveY * mul))
|
||||
case .C, .c:
|
||||
let x1 = segment.data[0]
|
||||
let y1 = segment.data[1]
|
||||
let x2 = segment.data[2]
|
||||
let y2 = segment.data[3]
|
||||
let x = segment.data[4]
|
||||
let y = segment.data[5]
|
||||
|
||||
let effectiveX1: Double
|
||||
let effectiveY1: Double
|
||||
let effectiveX2: Double
|
||||
let effectiveY2: Double
|
||||
let effectiveX: Double
|
||||
let effectiveY: Double
|
||||
|
||||
if segment.isAbsolute() {
|
||||
effectiveX1 = x1
|
||||
effectiveY1 = y1
|
||||
effectiveX2 = x2
|
||||
effectiveY2 = y2
|
||||
effectiveX = x
|
||||
effectiveY = y
|
||||
} else {
|
||||
effectiveX1 = currentX + x1
|
||||
effectiveY1 = currentY + y1
|
||||
effectiveX2 = currentX + x2
|
||||
effectiveY2 = currentY + y2
|
||||
effectiveX = currentX + x
|
||||
effectiveY = currentY + y
|
||||
}
|
||||
|
||||
currentX = effectiveX
|
||||
currentY = effectiveY
|
||||
|
||||
context.addCurve(to: CGPoint(x: effectiveX * mul, y: effectiveY * mul), control1: CGPoint(x: effectiveX1 * mul, y: effectiveY1 * mul), control2: CGPoint(x: effectiveX2 * mul, y: effectiveY2 * mul))
|
||||
case .z:
|
||||
context.fillPath()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), byRoundingCorners: [.topLeft, .topRight, .bottomLeft, .bottomRight], cornerRadii: CGSize(width: 10.0, height: 10.0))
|
||||
UIGraphicsPushContext(context)
|
||||
path.fill()
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
})
|
||||
|
||||
if backgroundColor == nil {
|
||||
self.foregroundNode.image = nil
|
||||
|
||||
let maskView: UIImageView
|
||||
if let current = self.maskView {
|
||||
maskView = current
|
||||
} else {
|
||||
maskView = UIImageView()
|
||||
maskView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.maskView = maskView
|
||||
self.view.mask = maskView
|
||||
}
|
||||
|
||||
} else {
|
||||
self.foregroundNode.image = image
|
||||
|
||||
if let _ = self.maskView {
|
||||
self.view.mask = nil
|
||||
self.maskView = nil
|
||||
}
|
||||
}
|
||||
|
||||
self.maskView?.image = image
|
||||
|
||||
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.foregroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.effectNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
}
|
||||
|
||||
open class PathSegment: Equatable {
|
||||
public enum SegmentType {
|
||||
case M
|
||||
case L
|
||||
case C
|
||||
case Q
|
||||
case A
|
||||
case z
|
||||
case H
|
||||
case V
|
||||
case S
|
||||
case T
|
||||
case m
|
||||
case l
|
||||
case c
|
||||
case q
|
||||
case a
|
||||
case h
|
||||
case v
|
||||
case s
|
||||
case t
|
||||
case E
|
||||
case e
|
||||
}
|
||||
|
||||
public let type: SegmentType
|
||||
public let data: [Double]
|
||||
|
||||
public init(type: PathSegment.SegmentType = .M, data: [Double] = []) {
|
||||
self.type = type
|
||||
self.data = data
|
||||
}
|
||||
|
||||
open func isAbsolute() -> Bool {
|
||||
switch type {
|
||||
case .M, .L, .H, .V, .C, .S, .Q, .T, .A, .E:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public static func == (lhs: PathSegment, rhs: PathSegment) -> Bool {
|
||||
return lhs.type == rhs.type && lhs.data == rhs.data
|
||||
}
|
||||
}
|
||||
|
||||
private class PathDataReader {
|
||||
private let input: String
|
||||
private var current: UnicodeScalar?
|
||||
private var previous: UnicodeScalar?
|
||||
private var iterator: String.UnicodeScalarView.Iterator
|
||||
|
||||
private static let spaces: Set<UnicodeScalar> = Set("\n\r\t ,".unicodeScalars)
|
||||
|
||||
init(input: String) {
|
||||
self.input = input
|
||||
self.iterator = input.unicodeScalars.makeIterator()
|
||||
}
|
||||
|
||||
public func read() -> [PathSegment] {
|
||||
readNext()
|
||||
var segments = [PathSegment]()
|
||||
while let array = readSegments() {
|
||||
segments.append(contentsOf: array)
|
||||
}
|
||||
return segments
|
||||
}
|
||||
|
||||
private func readSegments() -> [PathSegment]? {
|
||||
if let type = readSegmentType() {
|
||||
let argCount = getArgCount(segment: type)
|
||||
if argCount == 0 {
|
||||
return [PathSegment(type: type)]
|
||||
}
|
||||
var result = [PathSegment]()
|
||||
let data: [Double]
|
||||
if type == .a || type == .A {
|
||||
data = readDataOfASegment()
|
||||
} else {
|
||||
data = readData()
|
||||
}
|
||||
var index = 0
|
||||
var isFirstSegment = true
|
||||
while index < data.count {
|
||||
let end = index + argCount
|
||||
if end > data.count {
|
||||
break
|
||||
}
|
||||
var currentType = type
|
||||
if type == .M && !isFirstSegment {
|
||||
currentType = .L
|
||||
}
|
||||
if type == .m && !isFirstSegment {
|
||||
currentType = .l
|
||||
}
|
||||
result.append(PathSegment(type: currentType, data: Array(data[index..<end])))
|
||||
isFirstSegment = false
|
||||
index = end
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func readData() -> [Double] {
|
||||
var data = [Double]()
|
||||
while true {
|
||||
skipSpaces()
|
||||
if let value = readNum() {
|
||||
data.append(value)
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func readDataOfASegment() -> [Double] {
|
||||
let argCount = getArgCount(segment: .A)
|
||||
var data: [Double] = []
|
||||
var index = 0
|
||||
while true {
|
||||
skipSpaces()
|
||||
let value: Double?
|
||||
let indexMod = index % argCount
|
||||
if indexMod == 3 || indexMod == 4 {
|
||||
value = readFlag()
|
||||
} else {
|
||||
value = readNum()
|
||||
}
|
||||
guard let doubleValue = value else {
|
||||
return data
|
||||
}
|
||||
data.append(doubleValue)
|
||||
index += 1
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
private func skipSpaces() {
|
||||
var currentCharacter = current
|
||||
while let character = currentCharacter, Self.spaces.contains(character) {
|
||||
currentCharacter = readNext()
|
||||
}
|
||||
}
|
||||
|
||||
private func readFlag() -> Double? {
|
||||
guard let ch = current else {
|
||||
return .none
|
||||
}
|
||||
readNext()
|
||||
switch ch {
|
||||
case "0":
|
||||
return 0
|
||||
case "1":
|
||||
return 1
|
||||
default:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func readNum() -> Double? {
|
||||
guard let ch = current else {
|
||||
return .none
|
||||
}
|
||||
|
||||
guard ch >= "0" && ch <= "9" || ch == "." || ch == "-" else {
|
||||
return .none
|
||||
}
|
||||
|
||||
var chars = [ch]
|
||||
var hasDot = ch == "."
|
||||
while let ch = readDigit(&hasDot) {
|
||||
chars.append(ch)
|
||||
}
|
||||
|
||||
var buf = ""
|
||||
buf.unicodeScalars.append(contentsOf: chars)
|
||||
guard let value = Double(buf) else {
|
||||
return .none
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
fileprivate func readDigit(_ hasDot: inout Bool) -> UnicodeScalar? {
|
||||
if let ch = readNext() {
|
||||
if (ch >= "0" && ch <= "9") || ch == "e" || (previous == "e" && ch == "-") {
|
||||
return ch
|
||||
} else if ch == "." && !hasDot {
|
||||
hasDot = true
|
||||
return ch
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fileprivate func isNum(ch: UnicodeScalar, hasDot: inout Bool) -> Bool {
|
||||
switch ch {
|
||||
case "0"..."9":
|
||||
return true
|
||||
case ".":
|
||||
if hasDot {
|
||||
return false
|
||||
}
|
||||
hasDot = true
|
||||
default:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func readNext() -> UnicodeScalar? {
|
||||
previous = current
|
||||
current = iterator.next()
|
||||
return current
|
||||
}
|
||||
|
||||
private func isAcceptableSeparator(_ ch: UnicodeScalar?) -> Bool {
|
||||
if let ch = ch {
|
||||
return "\n\r\t ,".contains(String(ch))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func readSegmentType() -> PathSegment.SegmentType? {
|
||||
while true {
|
||||
if let type = getPathSegmentType() {
|
||||
readNext()
|
||||
return type
|
||||
}
|
||||
if readNext() == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func getPathSegmentType() -> PathSegment.SegmentType? {
|
||||
if let ch = current {
|
||||
switch ch {
|
||||
case "M":
|
||||
return .M
|
||||
case "m":
|
||||
return .m
|
||||
case "L":
|
||||
return .L
|
||||
case "l":
|
||||
return .l
|
||||
case "C":
|
||||
return .C
|
||||
case "c":
|
||||
return .c
|
||||
case "Q":
|
||||
return .Q
|
||||
case "q":
|
||||
return .q
|
||||
case "A":
|
||||
return .A
|
||||
case "a":
|
||||
return .a
|
||||
case "z", "Z":
|
||||
return .z
|
||||
case "H":
|
||||
return .H
|
||||
case "h":
|
||||
return .h
|
||||
case "V":
|
||||
return .V
|
||||
case "v":
|
||||
return .v
|
||||
case "S":
|
||||
return .S
|
||||
case "s":
|
||||
return .s
|
||||
case "T":
|
||||
return .T
|
||||
case "t":
|
||||
return .t
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fileprivate func getArgCount(segment: PathSegment.SegmentType) -> Int {
|
||||
switch segment {
|
||||
case .H, .h, .V, .v:
|
||||
return 1
|
||||
case .M, .m, .L, .l, .T, .t:
|
||||
return 2
|
||||
case .S, .s, .Q, .q:
|
||||
return 4
|
||||
case .C, .c:
|
||||
return 6
|
||||
case .A, .a:
|
||||
return 7
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
@ -222,12 +222,13 @@ class WebSearchGalleryController: ViewController {
|
||||
if let strongSelf = self {
|
||||
strongSelf.present(controller, in: .window(.root), with: arguments, blockInteraction: true)
|
||||
}
|
||||
}, dismissController: { [weak self] in
|
||||
self?.dismiss(forceAway: true)
|
||||
}, replaceRootController: { [weak self] controller, ready in
|
||||
if let strongSelf = self {
|
||||
strongSelf.replaceRootController(controller, ready)
|
||||
}
|
||||
}, dismissController: { [weak self] in
|
||||
self?.dismiss(forceAway: true)
|
||||
}, replaceRootController: { [weak self] controller, ready in
|
||||
if let strongSelf = self {
|
||||
strongSelf.replaceRootController(controller, ready)
|
||||
}
|
||||
}, editMedia: { _ in
|
||||
})
|
||||
self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction)
|
||||
self.displayNodeDidLoad()
|
||||
|
Loading…
x
Reference in New Issue
Block a user