mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Cherry pick more fixes
This commit is contained in:
parent
5126be83b3
commit
b8cd4b5b61
@ -523,6 +523,23 @@ private final class CachedChatListSearchResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class CachedCustomTextEntities {
|
||||||
|
let text: String
|
||||||
|
let textEntities: [MessageTextEntity]
|
||||||
|
|
||||||
|
init(text: String, textEntities: [MessageTextEntity]) {
|
||||||
|
self.text = text
|
||||||
|
self.textEntities = textEntities
|
||||||
|
}
|
||||||
|
|
||||||
|
func matches(text: String) -> Bool {
|
||||||
|
if self.text != text {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private let playIconImage = UIImage(bundleImageName: "Chat List/MiniThumbnailPlay")?.precomposed()
|
private let playIconImage = UIImage(bundleImageName: "Chat List/MiniThumbnailPlay")?.precomposed()
|
||||||
|
|
||||||
private final class ChatListMediaPreviewNode: ASDisplayNode {
|
private final class ChatListMediaPreviewNode: ASDisplayNode {
|
||||||
@ -611,6 +628,8 @@ private final class ChatListMediaPreviewNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let loginCodeRegex = try? NSRegularExpression(pattern: "[\\d\\-]{5,7}", options: [])
|
||||||
|
|
||||||
class ChatListItemNode: ItemListRevealOptionsItemNode {
|
class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||||
final class TopicItemNode: ASDisplayNode {
|
final class TopicItemNode: ASDisplayNode {
|
||||||
let topicTitleNode: TextNode
|
let topicTitleNode: TextNode
|
||||||
@ -924,6 +943,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
private var cachedChatListText: (String, String)?
|
private var cachedChatListText: (String, String)?
|
||||||
private var cachedChatListSearchResult: CachedChatListSearchResult?
|
private var cachedChatListSearchResult: CachedChatListSearchResult?
|
||||||
|
private var cachedCustomTextEntities: CachedCustomTextEntities?
|
||||||
|
|
||||||
var layoutParams: (ChatListItem, first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool, ListViewItemLayoutParams, countersSize: CGFloat)?
|
var layoutParams: (ChatListItem, first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool, ListViewItemLayoutParams, countersSize: CGFloat)?
|
||||||
|
|
||||||
@ -1492,6 +1512,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let currentItem = self.layoutParams?.0
|
let currentItem = self.layoutParams?.0
|
||||||
let currentChatListText = self.cachedChatListText
|
let currentChatListText = self.cachedChatListText
|
||||||
let currentChatListSearchResult = self.cachedChatListSearchResult
|
let currentChatListSearchResult = self.cachedChatListSearchResult
|
||||||
|
let currentCustomTextEntities = self.cachedCustomTextEntities
|
||||||
|
|
||||||
return { item, params, first, last, firstWithHeader, nextIsPinned in
|
return { item, params, first, last, firstWithHeader, nextIsPinned in
|
||||||
let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0))
|
let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0))
|
||||||
@ -1769,6 +1790,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
var chatListText: (String, String)?
|
var chatListText: (String, String)?
|
||||||
var chatListSearchResult: CachedChatListSearchResult?
|
var chatListSearchResult: CachedChatListSearchResult?
|
||||||
|
var customTextEntities: CachedCustomTextEntities?
|
||||||
|
|
||||||
let contentImageSide: CGFloat = max(10.0, min(20.0, floor(item.presentationData.fontSize.baseDisplaySize * 18.0 / 17.0)))
|
let contentImageSide: CGFloat = max(10.0, min(20.0, floor(item.presentationData.fontSize.baseDisplaySize * 18.0 / 17.0)))
|
||||||
let contentImageSize = CGSize(width: contentImageSide, height: contentImageSide)
|
let contentImageSize = CGSize(width: contentImageSide, height: contentImageSide)
|
||||||
@ -1841,7 +1863,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
authorAttributedString = NSAttributedString(string: peerText, font: textFont, textColor: theme.authorNameColor)
|
authorAttributedString = NSAttributedString(string: peerText, font: textFont, textColor: theme.authorNameColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
let entities = (message._asMessage().textEntitiesAttribute?.entities ?? []).filter { entity in
|
var entities = (message._asMessage().textEntitiesAttribute?.entities ?? []).filter { entity in
|
||||||
switch entity.type {
|
switch entity.type {
|
||||||
case .Spoiler, .CustomEmoji:
|
case .Spoiler, .CustomEmoji:
|
||||||
return true
|
return true
|
||||||
@ -1851,6 +1873,23 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if message.id.peerId.namespace == Namespaces.Peer.CloudUser && message.id.peerId.id._internalGetInt64Value() == 777000 {
|
||||||
|
if let cached = currentCustomTextEntities, cached.matches(text: message.text) {
|
||||||
|
customTextEntities = cached
|
||||||
|
} else if let matches = loginCodeRegex?.matches(in: message.text, options: [], range: NSMakeRange(0, (message.text as NSString).length)) {
|
||||||
|
var entities: [MessageTextEntity] = []
|
||||||
|
if let first = matches.first {
|
||||||
|
entities.append(MessageTextEntity(range: first.range.location ..< first.range.location + first.range.length, type: .Spoiler))
|
||||||
|
}
|
||||||
|
customTextEntities = CachedCustomTextEntities(text: message.text, textEntities: entities)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let customTextEntities, !customTextEntities.textEntities.isEmpty {
|
||||||
|
entities.append(contentsOf: customTextEntities.textEntities)
|
||||||
|
}
|
||||||
|
|
||||||
let messageString: NSAttributedString
|
let messageString: NSAttributedString
|
||||||
if !message.text.isEmpty && entities.count > 0 {
|
if !message.text.isEmpty && entities.count > 0 {
|
||||||
var messageText = message.text
|
var messageText = message.text
|
||||||
@ -2560,6 +2599,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.currentItemHeight = itemHeight
|
strongSelf.currentItemHeight = itemHeight
|
||||||
strongSelf.cachedChatListText = chatListText
|
strongSelf.cachedChatListText = chatListText
|
||||||
strongSelf.cachedChatListSearchResult = chatListSearchResult
|
strongSelf.cachedChatListSearchResult = chatListSearchResult
|
||||||
|
strongSelf.cachedCustomTextEntities = customTextEntities
|
||||||
strongSelf.onlineIsVoiceChat = onlineIsVoiceChat
|
strongSelf.onlineIsVoiceChat = onlineIsVoiceChat
|
||||||
|
|
||||||
var animateOnline = animateOnline
|
var animateOnline = animateOnline
|
||||||
|
@ -476,9 +476,19 @@ public class CheckLayer: CALayer {
|
|||||||
|
|
||||||
context.strokePath()
|
context.strokePath()
|
||||||
case let .counter(number):
|
case let .counter(number):
|
||||||
let text = NSAttributedString(string: "\(number)", font: Font.with(size: 16.0, design: .round, weight: .regular, traits: []), textColor: parameters.theme.strokeColor.withMultipliedAlpha(parameters.animationProgress))
|
let fontSize: CGFloat
|
||||||
|
let string = "\(number)"
|
||||||
|
switch string.count {
|
||||||
|
case 1:
|
||||||
|
fontSize = 16.0
|
||||||
|
case 2:
|
||||||
|
fontSize = 15.0
|
||||||
|
default:
|
||||||
|
fontSize = 13.0
|
||||||
|
}
|
||||||
|
let text = NSAttributedString(string: string, font: Font.with(size: fontSize, design: .round, weight: .medium, traits: []), textColor: parameters.theme.strokeColor.withMultipliedAlpha(parameters.animationProgress))
|
||||||
let textRect = text.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil)
|
let textRect = text.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil)
|
||||||
text.draw(at: CGPoint(x: UIScreenPixel + textRect.minX + floor((size.width - textRect.width) * 0.5), y: textRect.minY + floor((size.height - textRect.height) * 0.5)))
|
text.draw(at: CGPoint(x: textRect.minX + floorToScreenPixels((size.width - textRect.width) * 0.5), y: textRect.minY + floorToScreenPixels((size.height - textRect.height) * 0.5)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,76 +82,74 @@ extension UIImage.Orientation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let fetchPhotoWorkers = ThreadPool(threadCount: 3, threadPriority: 0.2)
|
||||||
|
|
||||||
public func fetchPhotoLibraryResource(localIdentifier: String) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
|
public func fetchPhotoLibraryResource(localIdentifier: String) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
|
let queue = ThreadPoolQueue(threadPool: fetchPhotoWorkers)
|
||||||
|
|
||||||
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
|
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
|
||||||
let requestId = Atomic<RequestId>(value: RequestId())
|
let requestId = Atomic<RequestId>(value: RequestId())
|
||||||
if fetchResult.count != 0 {
|
if fetchResult.count != 0 {
|
||||||
let asset = fetchResult.object(at: 0)
|
let asset = fetchResult.object(at: 0)
|
||||||
let option = PHImageRequestOptions()
|
let option = PHImageRequestOptions()
|
||||||
option.deliveryMode = .opportunistic
|
option.deliveryMode = .highQualityFormat
|
||||||
option.isNetworkAccessAllowed = true
|
option.isNetworkAccessAllowed = true
|
||||||
option.isSynchronous = false
|
option.isSynchronous = false
|
||||||
let madeProgress = Atomic<Bool>(value: false)
|
|
||||||
option.progressHandler = { progress, error, _, _ in
|
|
||||||
if !madeProgress.swap(true) {
|
|
||||||
//subscriber.putNext(.reset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let size = CGSize(width: 1280.0, height: 1280.0)
|
let size = CGSize(width: 1280.0, height: 1280.0)
|
||||||
|
|
||||||
let startTime = CACurrentMediaTime()
|
queue.addTask(ThreadPoolTask({ _ in
|
||||||
|
let startTime = CACurrentMediaTime()
|
||||||
let requestIdValue = PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFit, options: option, resultHandler: { (image, info) -> Void in
|
|
||||||
Queue.concurrentDefaultQueue().async {
|
let semaphore = DispatchSemaphore(value: 0)
|
||||||
requestId.with { current -> Void in
|
let requestIdValue = PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFit, options: option, resultHandler: { (image, info) -> Void in
|
||||||
if !current.invalidated {
|
Queue.concurrentDefaultQueue().async {
|
||||||
current.id = nil
|
requestId.with { current -> Void in
|
||||||
current.invalidated = true
|
if !current.invalidated {
|
||||||
|
current.id = nil
|
||||||
|
current.invalidated = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if let image = image {
|
||||||
if let image = image {
|
if let info = info, let degraded = info[PHImageResultIsDegradedKey], (degraded as AnyObject).boolValue!{
|
||||||
if let info = info, let degraded = info[PHImageResultIsDegradedKey], (degraded as AnyObject).boolValue!{
|
|
||||||
if !madeProgress.swap(true) {
|
} else {
|
||||||
//subscriber.putNext(.reset)
|
#if DEBUG
|
||||||
|
print("load completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
let scale = min(1.0, min(size.width / max(1.0, image.size.width), size.height / max(1.0, image.size.height)))
|
||||||
|
let scaledSize = CGSize(width: floor(image.size.width * scale), height: floor(image.size.height * scale))
|
||||||
|
let scaledImage = resizedImage(image, for: scaledSize)
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
print("scaled completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if let scaledImage = scaledImage, let data = compressImageToJPEG(scaledImage, quality: 0.6) {
|
||||||
|
#if DEBUG
|
||||||
|
print("compression completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
|
||||||
|
#endif
|
||||||
|
subscriber.putNext(.dataPart(resourceOffset: 0, data: data, range: 0 ..< Int64(data.count), complete: true))
|
||||||
|
subscriber.putCompletion()
|
||||||
|
} else {
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
|
semaphore.signal()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#if DEBUG
|
semaphore.signal()
|
||||||
print("load completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
|
|
||||||
#endif
|
|
||||||
|
|
||||||
_ = madeProgress.swap(true)
|
|
||||||
|
|
||||||
let scale = min(1.0, min(size.width / max(1.0, image.size.width), size.height / max(1.0, image.size.height)))
|
|
||||||
let scaledSize = CGSize(width: floor(image.size.width * scale), height: floor(image.size.height * scale))
|
|
||||||
let scaledImage = resizedImage(image, for: scaledSize)
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
print("scaled completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if let scaledImage = scaledImage, let data = compressImageToJPEG(scaledImage, quality: 0.6) {
|
|
||||||
#if DEBUG
|
|
||||||
print("compression completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
|
|
||||||
#endif
|
|
||||||
subscriber.putNext(.dataPart(resourceOffset: 0, data: data, range: 0 ..< Int64(data.count), complete: true))
|
|
||||||
subscriber.putCompletion()
|
|
||||||
} else {
|
|
||||||
subscriber.putCompletion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !madeProgress.swap(true) {
|
|
||||||
//subscriber.putNext(.reset)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
requestId.with { current -> Void in
|
||||||
|
if !current.invalidated {
|
||||||
|
current.id = requestIdValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
semaphore.wait()
|
||||||
requestId.with { current -> Void in
|
}))
|
||||||
if !current.invalidated {
|
|
||||||
current.id = requestIdValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
subscriber.putNext(.reset)
|
subscriber.putNext(.reset)
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ func assetImage(asset: PHAsset, targetSize: CGSize, exact: Bool, deliveryMode: P
|
|||||||
options.resizeMode = .exact
|
options.resizeMode = .exact
|
||||||
}
|
}
|
||||||
options.isSynchronous = synchronous
|
options.isSynchronous = synchronous
|
||||||
|
options.isNetworkAccessAllowed = true
|
||||||
let token = imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: options) { (image, info) in
|
let token = imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: options) { (image, info) in
|
||||||
var degraded = false
|
var degraded = false
|
||||||
|
|
||||||
|
@ -1452,13 +1452,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectionState.setItem(item, selected: value)
|
let success = selectionState.setItem(item, selected: value)
|
||||||
|
|
||||||
if showUndo {
|
if showUndo {
|
||||||
self.showSelectionUndo(item: item)
|
self.showSelectionUndo(item: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return success
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ public final class QrCodeScreen: ViewController {
|
|||||||
case let .invite(invite, _):
|
case let .invite(invite, _):
|
||||||
return invite.link ?? ""
|
return invite.link ?? ""
|
||||||
case let .chatFolder(slug):
|
case let .chatFolder(slug):
|
||||||
return "https://t.me/list/\(slug)"
|
return slug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||||||
for media in message.media {
|
for media in message.media {
|
||||||
if let action = media as? TelegramMediaAction {
|
if let action = media as? TelegramMediaAction {
|
||||||
let authorName = message.author?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? ""
|
let authorName = message.author?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? ""
|
||||||
|
let compactAuthorName = message.author?.compactDisplayTitle ?? ""
|
||||||
|
|
||||||
var isChannel = false
|
var isChannel = false
|
||||||
if message.id.peerId.namespace == Namespaces.Peer.CloudChannel, let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
if message.id.peerId.namespace == Namespaces.Peer.CloudChannel, let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
||||||
@ -695,7 +696,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||||||
attributedString = NSAttributedString(string: strings.Notification_YouDisabledTheme, font: titleFont, textColor: primaryTextColor)
|
attributedString = NSAttributedString(string: strings.Notification_YouDisabledTheme, font: titleFont, textColor: primaryTextColor)
|
||||||
} else {
|
} else {
|
||||||
let attributePeerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)]
|
let attributePeerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)]
|
||||||
let resultTitleString = strings.Notification_DisabledTheme(authorName)
|
let resultTitleString = strings.Notification_DisabledTheme(compactAuthorName)
|
||||||
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
|
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -704,7 +705,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||||||
} else if message.author?.id == accountPeerId {
|
} else if message.author?.id == accountPeerId {
|
||||||
attributedString = NSAttributedString(string: strings.Notification_YouChangedTheme(emoji).string, font: titleFont, textColor: primaryTextColor)
|
attributedString = NSAttributedString(string: strings.Notification_YouChangedTheme(emoji).string, font: titleFont, textColor: primaryTextColor)
|
||||||
} else {
|
} else {
|
||||||
let resultTitleString = strings.Notification_ChangedTheme(authorName, emoji)
|
let resultTitleString = strings.Notification_ChangedTheme(compactAuthorName, emoji)
|
||||||
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -717,7 +718,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||||||
} else {
|
} else {
|
||||||
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
|
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
|
||||||
attributes[1] = boldAttributes
|
attributes[1] = boldAttributes
|
||||||
attributedString = addAttributesToStringWithRanges(strings.Notification_PremiumGift_Sent(authorName, price)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
attributedString = addAttributesToStringWithRanges(strings.Notification_PremiumGift_Sent(compactAuthorName, price)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||||
}
|
}
|
||||||
case let .topicCreated(title, iconColor, iconFileId):
|
case let .topicCreated(title, iconColor, iconFileId):
|
||||||
if forForumOverview {
|
if forForumOverview {
|
||||||
@ -877,14 +878,14 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||||||
if message.author?.id == accountPeerId {
|
if message.author?.id == accountPeerId {
|
||||||
attributedString = NSAttributedString(string: strings.Notification_YouChangedWallpaper, font: titleFont, textColor: primaryTextColor)
|
attributedString = NSAttributedString(string: strings.Notification_YouChangedWallpaper, font: titleFont, textColor: primaryTextColor)
|
||||||
} else {
|
} else {
|
||||||
let resultTitleString = strings.Notification_ChangedWallpaper(authorName)
|
let resultTitleString = strings.Notification_ChangedWallpaper(compactAuthorName)
|
||||||
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||||
}
|
}
|
||||||
case .setSameChatWallpaper:
|
case .setSameChatWallpaper:
|
||||||
if message.author?.id == accountPeerId {
|
if message.author?.id == accountPeerId {
|
||||||
attributedString = NSAttributedString(string: strings.Notification_YouChangedToSameWallpaper, font: titleFont, textColor: primaryTextColor)
|
attributedString = NSAttributedString(string: strings.Notification_YouChangedToSameWallpaper, font: titleFont, textColor: primaryTextColor)
|
||||||
} else {
|
} else {
|
||||||
let resultTitleString = strings.Notification_ChangedToSameWallpaper(authorName)
|
let resultTitleString = strings.Notification_ChangedToSameWallpaper(compactAuthorName)
|
||||||
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||||
}
|
}
|
||||||
case .unknown:
|
case .unknown:
|
||||||
|
@ -705,10 +705,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
isLocation = true
|
isLocation = true
|
||||||
}
|
}
|
||||||
if let file = media as? TelegramMediaFile, file.isInstantVideo {
|
if let file = media as? TelegramMediaFile {
|
||||||
if strongSelf.chatDisplayNode.isInputViewFocused {
|
if file.isInstantVideo {
|
||||||
strongSelf.returnInputViewFocus = true
|
if strongSelf.chatDisplayNode.isInputViewFocused {
|
||||||
strongSelf.chatDisplayNode.dismissInput()
|
strongSelf.returnInputViewFocus = true
|
||||||
|
strongSelf.chatDisplayNode.dismissInput()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if file.isMusic || file.isVoice || file.isInstantVideo {
|
||||||
|
if !displayVoiceMessageDiscardAlert() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia {
|
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia {
|
||||||
@ -4294,7 +4301,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = (ApplicationSpecificNotice.getBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id)
|
var botPeer = EnginePeer(peer)
|
||||||
|
if case let .inline(bot) = source {
|
||||||
|
botPeer = bot
|
||||||
|
}
|
||||||
|
let _ = (ApplicationSpecificNotice.getBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: botPeer.id)
|
||||||
|> deliverOnMainQueue).start(next: { value in
|
|> deliverOnMainQueue).start(next: { value in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -4303,8 +4314,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if value {
|
if value {
|
||||||
openWebView()
|
openWebView()
|
||||||
} else {
|
} else {
|
||||||
let controller = webAppLaunchConfirmationController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: EnginePeer(peer), commit: {
|
let controller = webAppLaunchConfirmationController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: botPeer, commit: {
|
||||||
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id).start()
|
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: botPeer.id).start()
|
||||||
openWebView()
|
openWebView()
|
||||||
}, showMore: nil)
|
}, showMore: nil)
|
||||||
strongSelf.present(controller, in: .window(.root))
|
strongSelf.present(controller, in: .window(.root))
|
||||||
@ -12857,97 +12868,114 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self.presentAttachmentMenu(subject: .bot(id: botId, payload: payload, justInstalled: justInstalled))
|
self.presentAttachmentMenu(subject: .bot(id: botId, payload: payload, justInstalled: justInstalled))
|
||||||
}
|
}
|
||||||
|
|
||||||
public func presentBotApp(botApp: BotApp, botPeer: EnginePeer, payload: String?) {
|
public func presentBotApp(botApp: BotApp, botPeer: EnginePeer, payload: String?, concealed: Bool = false) {
|
||||||
guard let peerId = self.chatLocation.peerId else {
|
guard let peerId = self.chatLocation.peerId else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.attachmentController?.dismiss(animated: true, completion: nil)
|
self.attachmentController?.dismiss(animated: true, completion: nil)
|
||||||
|
|
||||||
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
let openBotApp = { [weak self] allowWrite in
|
||||||
return $0.updatedTitlePanelContext {
|
guard let strongSelf = self else {
|
||||||
if !$0.contains(where: {
|
return
|
||||||
switch $0 {
|
}
|
||||||
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||||
|
return $0.updatedTitlePanelContext {
|
||||||
|
if !$0.contains(where: {
|
||||||
|
switch $0 {
|
||||||
case .requestInProgress:
|
case .requestInProgress:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
var updatedContexts = $0
|
||||||
|
updatedContexts.append(.requestInProgress)
|
||||||
|
return updatedContexts.sorted()
|
||||||
}
|
}
|
||||||
}) {
|
return $0
|
||||||
var updatedContexts = $0
|
|
||||||
updatedContexts.append(.requestInProgress)
|
|
||||||
return updatedContexts.sorted()
|
|
||||||
}
|
}
|
||||||
return $0
|
})
|
||||||
}
|
|
||||||
})
|
let updateProgress = { [weak self] in
|
||||||
|
Queue.mainQueue().async {
|
||||||
let updateProgress = { [weak self] in
|
if let strongSelf = self {
|
||||||
Queue.mainQueue().async {
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||||
if let strongSelf = self {
|
return $0.updatedTitlePanelContext {
|
||||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
if let index = $0.firstIndex(where: {
|
||||||
return $0.updatedTitlePanelContext {
|
switch $0 {
|
||||||
if let index = $0.firstIndex(where: {
|
|
||||||
switch $0 {
|
|
||||||
case .requestInProgress:
|
case .requestInProgress:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
var updatedContexts = $0
|
||||||
|
updatedContexts.remove(at: index)
|
||||||
|
return updatedContexts
|
||||||
}
|
}
|
||||||
}) {
|
return $0
|
||||||
var updatedContexts = $0
|
|
||||||
updatedContexts.remove(at: index)
|
|
||||||
return updatedContexts
|
|
||||||
}
|
}
|
||||||
return $0
|
})
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let botAddress = botPeer.addressName ?? ""
|
|
||||||
|
|
||||||
self.messageActionCallbackDisposable.set(((self.context.engine.messages.requestAppWebView(peerId: peerId, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: payload, themeParams: generateWebAppThemeParams(self.presentationData.theme), allowWrite: false)
|
|
||||||
|> afterDisposed {
|
|
||||||
updateProgress()
|
|
||||||
})
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] url in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botApp.title, url: url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, fromMenu: false, fromAttachMenu: false, isInline: false, isSimple: false)
|
|
||||||
let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url in
|
|
||||||
self?.openUrl(url, concealed: true, forceExternal: true)
|
|
||||||
}, requestSwitchInline: { [weak self] query, chatTypes, completion in
|
|
||||||
if let strongSelf = self {
|
|
||||||
if let chatTypes {
|
|
||||||
let controller = strongSelf.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: strongSelf.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: chatTypes, hasContactSelector: false, hasCreation: false))
|
|
||||||
controller.peerSelected = { [weak self, weak controller] peer, _ in
|
|
||||||
if let strongSelf = self {
|
|
||||||
completion()
|
|
||||||
controller?.dismiss()
|
|
||||||
strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)", nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
strongSelf.push(controller)
|
|
||||||
} else {
|
|
||||||
strongSelf.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)", nil)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, completion: { [weak self] in
|
|
||||||
self?.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
|
||||||
}, getNavigationController: { [weak self] in
|
|
||||||
return self?.effectiveNavigationController
|
|
||||||
})
|
|
||||||
controller.navigationPresentation = .flatModal
|
|
||||||
strongSelf.currentWebAppController = controller
|
|
||||||
strongSelf.push(controller)
|
|
||||||
}, error: { [weak self] error in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
|
|
||||||
})]), in: .window(.root))
|
|
||||||
}
|
}
|
||||||
}))
|
|
||||||
|
let botAddress = botPeer.addressName ?? ""
|
||||||
|
strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestAppWebView(peerId: peerId, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: payload, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme), allowWrite: allowWrite)
|
||||||
|
|> afterDisposed {
|
||||||
|
updateProgress()
|
||||||
|
})
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] url in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botApp.title, url: url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, fromMenu: false, fromAttachMenu: false, isInline: false, isSimple: false)
|
||||||
|
let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url in
|
||||||
|
self?.openUrl(url, concealed: true, forceExternal: true)
|
||||||
|
}, requestSwitchInline: { [weak self] query, chatTypes, completion in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if let chatTypes {
|
||||||
|
let controller = strongSelf.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: strongSelf.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: chatTypes, hasContactSelector: false, hasCreation: false))
|
||||||
|
controller.peerSelected = { [weak self, weak controller] peer, _ in
|
||||||
|
if let strongSelf = self {
|
||||||
|
completion()
|
||||||
|
controller?.dismiss()
|
||||||
|
strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strongSelf.push(controller)
|
||||||
|
} else {
|
||||||
|
strongSelf.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, completion: { [weak self] in
|
||||||
|
self?.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||||
|
}, getNavigationController: { [weak self] in
|
||||||
|
return self?.effectiveNavigationController
|
||||||
|
})
|
||||||
|
controller.navigationPresentation = .flatModal
|
||||||
|
strongSelf.currentWebAppController = controller
|
||||||
|
strongSelf.push(controller)
|
||||||
|
}, error: { [weak self] error in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
|
||||||
|
})]), in: .window(.root))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if concealed || botApp.flags.contains(.notActivated) {
|
||||||
|
let controller = webAppLaunchConfirmationController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: botPeer, commit: {
|
||||||
|
openBotApp(false)
|
||||||
|
}, showMore: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.openResolved(result: .peer(botPeer._asPeer(), .info), sourceMessageId: nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.present(controller, in: .window(.root))
|
||||||
|
} else {
|
||||||
|
openBotApp(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentAttachmentPremiumGift() {
|
private func presentAttachmentPremiumGift() {
|
||||||
@ -17329,9 +17357,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}))
|
}))
|
||||||
case let .withBotStartPayload(startPayload):
|
case let .withBotStartPayload(startPayload):
|
||||||
if case .peer(peerId.id) = strongSelf.chatLocation {
|
if case .peer(peerId.id) = strongSelf.chatLocation {
|
||||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
strongSelf.startBot(startPayload.payload)
|
||||||
$0.updatedBotStartPayload(startPayload.payload)
|
|
||||||
})
|
|
||||||
} else if let navigationController = strongSelf.effectiveNavigationController {
|
} else if let navigationController = strongSelf.effectiveNavigationController {
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), botStart: startPayload, keepStack: .always))
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), botStart: startPayload, keepStack: .always))
|
||||||
}
|
}
|
||||||
@ -17343,23 +17369,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id))
|
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id))
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||||
if let strongSelf = self, let peer {
|
if let strongSelf = self, let peer {
|
||||||
let openBotApp = { [weak self] in
|
strongSelf.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload, concealed: concealed)
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.presentBotApp(botApp: botAppStart.botApp, botPeer: peerId, payload: botAppStart.payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if concealed {
|
|
||||||
let controller = webAppLaunchConfirmationController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, commit: {
|
|
||||||
openBotApp()
|
|
||||||
}, showMore: { [weak self] in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.openResolved(result: .peer(peer._asPeer(), .info), sourceMessageId: nil)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
strongSelf.present(controller, in: .window(.root))
|
|
||||||
} else {
|
|
||||||
openBotApp()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
import Display
|
import Display
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
@ -207,7 +208,11 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode {
|
|||||||
self.borderNode.view.mask = self.borderMaskNode.view
|
self.borderNode.view.mask = self.borderMaskNode.view
|
||||||
|
|
||||||
if self.context.sharedContext.energyUsageSettings.fullTranslucency {
|
if self.context.sharedContext.energyUsageSettings.fullTranslucency {
|
||||||
self.backgroundNode?.updateIsLooping(true)
|
Queue.mainQueue().after(0.3) {
|
||||||
|
if !self.didAnimateOut {
|
||||||
|
self.backgroundNode?.updateIsLooping(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,11 +284,12 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode {
|
|||||||
self.borderMaskNode.bounds = self.borderMaskNode.bounds.offsetBy(dx: 0.0, dy: inset)
|
self.borderMaskNode.bounds = self.borderMaskNode.bounds.offsetBy(dx: 0.0, dy: inset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var didAnimateOut = false
|
||||||
func animateOut(_ historyNode: ChatHistoryNode, completion: @escaping () -> Void = {}) {
|
func animateOut(_ historyNode: ChatHistoryNode, completion: @escaping () -> Void = {}) {
|
||||||
guard let listNode = historyNode as? ListView, let (size, _, _) = self.validLayout else {
|
guard let listNode = historyNode as? ListView, let (size, _, _) = self.validLayout else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
self.didAnimateOut = true
|
||||||
self.backgroundNode?.updateIsLooping(false)
|
self.backgroundNode?.updateIsLooping(false)
|
||||||
|
|
||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring)
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring)
|
||||||
|
@ -1473,7 +1473,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
transition.animatePosition(layer: self.startButton.layer, from: CGPoint(x: 0.0, y: 80.0), to: CGPoint(), additive: true)
|
transition.animatePosition(layer: self.startButton.layer, from: CGPoint(x: 0.0, y: 80.0), to: CGPoint(), additive: true)
|
||||||
}
|
}
|
||||||
if let context = self.context {
|
if let context = self.context {
|
||||||
let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil)
|
let parentFrame = self.view.convert(self.bounds, to: nil)
|
||||||
|
let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
|
||||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize())
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize())
|
||||||
|
|
||||||
if let tooltipController = self.tooltipController {
|
if let tooltipController = self.tooltipController {
|
||||||
@ -1494,7 +1495,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
delay = 0.35
|
delay = 0.35
|
||||||
}
|
}
|
||||||
Queue.mainQueue().after(delay, {
|
Queue.mainQueue().after(delay, {
|
||||||
let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil)
|
let parentFrame = self.view.convert(self.bounds, to: nil)
|
||||||
|
let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
|
||||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize())
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize())
|
||||||
controller.location = .point(location, .bottom)
|
controller.location = .point(location, .bottom)
|
||||||
self.interfaceInteraction?.presentControllerInCurrent(controller, nil)
|
self.interfaceInteraction?.presentControllerInCurrent(controller, nil)
|
||||||
|
@ -207,198 +207,65 @@ private final class FetchVideoLibraryMediaResourceContext {
|
|||||||
private let throttlingContext = FetchVideoLibraryMediaResourceContext()
|
private let throttlingContext = FetchVideoLibraryMediaResourceContext()
|
||||||
|
|
||||||
public func fetchVideoLibraryMediaResource(account: Account, resource: VideoLibraryMediaResource) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
|
public func fetchVideoLibraryMediaResource(account: Account, resource: VideoLibraryMediaResource) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
|
||||||
return account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
let signal = Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> { subscriber in
|
||||||
|> take(1)
|
subscriber.putNext(.reset)
|
||||||
|> map { view in
|
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [resource.localIdentifier], options: nil)
|
||||||
return view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue
|
var requestId: PHImageRequestID?
|
||||||
}
|
let disposable = MetaDisposable()
|
||||||
|> castError(MediaResourceDataFetchError.self)
|
if fetchResult.count != 0 {
|
||||||
|> mapToSignal { appConfiguration -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> in
|
let asset = fetchResult.object(at: 0)
|
||||||
let signal = Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> { subscriber in
|
let option = PHVideoRequestOptions()
|
||||||
subscriber.putNext(.reset)
|
option.isNetworkAccessAllowed = true
|
||||||
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [resource.localIdentifier], options: nil)
|
option.deliveryMode = .highQualityFormat
|
||||||
var requestId: PHImageRequestID?
|
|
||||||
let disposable = MetaDisposable()
|
let alreadyReceivedAsset = Atomic<Bool>(value: false)
|
||||||
if fetchResult.count != 0 {
|
requestId = PHImageManager.default().requestAVAsset(forVideo: asset, options: option, resultHandler: { avAsset, _, _ in
|
||||||
let asset = fetchResult.object(at: 0)
|
if avAsset == nil {
|
||||||
let option = PHVideoRequestOptions()
|
return
|
||||||
option.isNetworkAccessAllowed = true
|
}
|
||||||
option.deliveryMode = .highQualityFormat
|
|
||||||
|
|
||||||
let alreadyReceivedAsset = Atomic<Bool>(value: false)
|
if alreadyReceivedAsset.swap(true) {
|
||||||
requestId = PHImageManager.default().requestAVAsset(forVideo: asset, options: option, resultHandler: { avAsset, _, _ in
|
return
|
||||||
if avAsset == nil {
|
}
|
||||||
return
|
|
||||||
}
|
var adjustments: TGVideoEditAdjustments?
|
||||||
|
switch resource.conversion {
|
||||||
if alreadyReceivedAsset.swap(true) {
|
case .passthrough:
|
||||||
return
|
if let asset = avAsset as? AVURLAsset {
|
||||||
}
|
|
||||||
|
|
||||||
var adjustments: TGVideoEditAdjustments?
|
|
||||||
switch resource.conversion {
|
|
||||||
case .passthrough:
|
|
||||||
if let asset = avAsset as? AVURLAsset {
|
|
||||||
var value = stat()
|
|
||||||
if stat(asset.url.path, &value) == 0 {
|
|
||||||
subscriber.putNext(.copyLocalItem(AVURLAssetCopyItem(url: asset.url)))
|
|
||||||
subscriber.putCompletion()
|
|
||||||
} else {
|
|
||||||
subscriber.putError(.generic)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
adjustments = nil
|
|
||||||
}
|
|
||||||
case let .compress(adjustmentsValue):
|
|
||||||
if let adjustmentsValue = adjustmentsValue {
|
|
||||||
if let dict = NSKeyedUnarchiver.unarchiveObject(with: adjustmentsValue.data.makeData()) as? [AnyHashable : Any] {
|
|
||||||
adjustments = TGVideoEditAdjustments(dictionary: dict)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let updatedSize = Atomic<Int64>(value: 0)
|
|
||||||
let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in
|
|
||||||
if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
|
|
||||||
return LegacyPaintEntityRenderer(account: account, adjustments: adjustments)
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4")
|
|
||||||
let signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, path: tempFile.path, watcher: VideoConversionWatcher(update: { path, size in
|
|
||||||
var value = stat()
|
|
||||||
if stat(path, &value) == 0 {
|
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
|
|
||||||
var range: Range<Int64>?
|
|
||||||
let _ = updatedSize.modify { updatedSize in
|
|
||||||
range = updatedSize ..< value.st_size
|
|
||||||
return value.st_size
|
|
||||||
}
|
|
||||||
//print("size = \(Int(value.st_size)), range: \(range!)")
|
|
||||||
subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}), entityRenderer: entityRenderer)!
|
|
||||||
let signalDisposable = signal.start(next: { next in
|
|
||||||
if let result = next as? TGMediaVideoConversionResult {
|
|
||||||
var value = stat()
|
var value = stat()
|
||||||
if stat(result.fileURL.path, &value) == 0 {
|
if stat(asset.url.path, &value) == 0 {
|
||||||
if let data = try? Data(contentsOf: result.fileURL, options: [.mappedRead]) {
|
subscriber.putNext(.copyLocalItem(AVURLAssetCopyItem(url: asset.url)))
|
||||||
var range: Range<Int64>?
|
subscriber.putCompletion()
|
||||||
let _ = updatedSize.modify { updatedSize in
|
|
||||||
range = updatedSize ..< value.st_size
|
|
||||||
return value.st_size
|
|
||||||
}
|
|
||||||
//print("finish size = \(Int(value.st_size)), range: \(range!)")
|
|
||||||
subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false))
|
|
||||||
subscriber.putNext(.replaceHeader(data: data, range: 0 ..< 1024))
|
|
||||||
subscriber.putNext(.dataPart(resourceOffset: Int64(data.count), data: Data(), range: 0 ..< 0, complete: true))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
subscriber.putError(.generic)
|
subscriber.putError(.generic)
|
||||||
}
|
}
|
||||||
subscriber.putCompletion()
|
return
|
||||||
|
|
||||||
EngineTempBox.shared.dispose(tempFile)
|
|
||||||
}
|
|
||||||
}, error: { _ in
|
|
||||||
subscriber.putError(.generic)
|
|
||||||
}, completed: nil)
|
|
||||||
disposable.set(ActionDisposable {
|
|
||||||
signalDisposable?.dispose()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return ActionDisposable {
|
|
||||||
if let requestId = requestId {
|
|
||||||
PHImageManager.default().cancelImageRequest(requestId)
|
|
||||||
}
|
|
||||||
disposable.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return throttlingContext.wrap(priority: .default, signal: signal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchLocalFileVideoMediaResource(account: Account, resource: LocalFileVideoMediaResource) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
|
|
||||||
return account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
|
||||||
|> take(1)
|
|
||||||
|> map { view in
|
|
||||||
return view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue
|
|
||||||
}
|
|
||||||
|> castError(MediaResourceDataFetchError.self)
|
|
||||||
|> mapToSignal { appConfiguration -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> in
|
|
||||||
let signal = Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> { subscriber in
|
|
||||||
subscriber.putNext(.reset)
|
|
||||||
|
|
||||||
var filteredPath = resource.path
|
|
||||||
if filteredPath.hasPrefix("file://") {
|
|
||||||
filteredPath = String(filteredPath[filteredPath.index(filteredPath.startIndex, offsetBy: "file://".count)])
|
|
||||||
}
|
|
||||||
|
|
||||||
let avAsset = AVURLAsset(url: URL(fileURLWithPath: filteredPath))
|
|
||||||
var adjustments: TGVideoEditAdjustments?
|
|
||||||
if let videoAdjustments = resource.adjustments {
|
|
||||||
if let dict = NSKeyedUnarchiver.unarchiveObject(with: videoAdjustments.data.makeData()) as? [AnyHashable : Any] {
|
|
||||||
adjustments = TGVideoEditAdjustments(dictionary: dict)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4")
|
|
||||||
let updatedSize = Atomic<Int64>(value: 0)
|
|
||||||
let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in
|
|
||||||
if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
|
|
||||||
return LegacyPaintEntityRenderer(account: account, adjustments: adjustments)
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let signal: SSignal
|
|
||||||
if filteredPath.contains(".jpg"), let entityRenderer = entityRenderer {
|
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: filteredPath), options: [.mappedRead]), let image = UIImage(data: data) {
|
|
||||||
let durationSignal: SSignal = SSignal(generator: { subscriber in
|
|
||||||
let disposable = (entityRenderer.duration()).start(next: { duration in
|
|
||||||
subscriber.putNext(duration)
|
|
||||||
subscriber.putCompletion()
|
|
||||||
})
|
|
||||||
|
|
||||||
return SBlockDisposable(block: {
|
|
||||||
disposable.dispose()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
signal = durationSignal.map(toSignal: { duration -> SSignal in
|
|
||||||
if let duration = duration as? Double {
|
|
||||||
return TGMediaVideoConverter.renderUIImage(image, duration: duration, adjustments: adjustments, path: tempFile.path, watcher: VideoConversionWatcher(update: { path, size in
|
|
||||||
var value = stat()
|
|
||||||
if stat(path, &value) == 0 {
|
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
|
|
||||||
var range: Range<Int64>?
|
|
||||||
let _ = updatedSize.modify { updatedSize in
|
|
||||||
range = updatedSize ..< value.st_size
|
|
||||||
return value.st_size
|
|
||||||
}
|
|
||||||
//print("size = \(Int(value.st_size)), range: \(range!)")
|
|
||||||
subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}), entityRenderer: entityRenderer)!
|
|
||||||
} else {
|
} else {
|
||||||
return SSignal.single(nil)
|
adjustments = nil
|
||||||
|
}
|
||||||
|
case let .compress(adjustmentsValue):
|
||||||
|
if let adjustmentsValue = adjustmentsValue {
|
||||||
|
if let dict = NSKeyedUnarchiver.unarchiveObject(with: adjustmentsValue.data.makeData()) as? [AnyHashable : Any] {
|
||||||
|
adjustments = TGVideoEditAdjustments(dictionary: dict)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
} else {
|
|
||||||
signal = SSignal.single(nil)
|
|
||||||
}
|
}
|
||||||
} else {
|
let updatedSize = Atomic<Int64>(value: 0)
|
||||||
signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, path: tempFile.path, watcher: VideoConversionWatcher(update: { path, size in
|
let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in
|
||||||
|
if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
|
||||||
|
return LegacyPaintEntityRenderer(account: account, adjustments: adjustments)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4")
|
||||||
|
let signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, path: tempFile.path, watcher: VideoConversionWatcher(update: { path, size in
|
||||||
var value = stat()
|
var value = stat()
|
||||||
if stat(path, &value) == 0 {
|
if stat(path, &value) == 0 {
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
|
||||||
var range: Range<Int64>?
|
var range: Range<Int64>?
|
||||||
let _ = updatedSize.modify { updatedSize in
|
let _ = updatedSize.modify { updatedSize in
|
||||||
range = updatedSize ..< Int64(value.st_size)
|
range = updatedSize ..< value.st_size
|
||||||
return value.st_size
|
return value.st_size
|
||||||
}
|
}
|
||||||
//print("size = \(Int(value.st_size)), range: \(range!)")
|
//print("size = \(Int(value.st_size)), range: \(range!)")
|
||||||
@ -406,12 +273,130 @@ func fetchLocalFileVideoMediaResource(account: Account, resource: LocalFileVideo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}), entityRenderer: entityRenderer)!
|
}), entityRenderer: entityRenderer)!
|
||||||
|
let signalDisposable = signal.start(next: { next in
|
||||||
|
if let result = next as? TGMediaVideoConversionResult {
|
||||||
|
var value = stat()
|
||||||
|
if stat(result.fileURL.path, &value) == 0 {
|
||||||
|
if let data = try? Data(contentsOf: result.fileURL, options: [.mappedRead]) {
|
||||||
|
var range: Range<Int64>?
|
||||||
|
let _ = updatedSize.modify { updatedSize in
|
||||||
|
range = updatedSize ..< value.st_size
|
||||||
|
return value.st_size
|
||||||
|
}
|
||||||
|
//print("finish size = \(Int(value.st_size)), range: \(range!)")
|
||||||
|
subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false))
|
||||||
|
subscriber.putNext(.replaceHeader(data: data, range: 0 ..< 1024))
|
||||||
|
subscriber.putNext(.dataPart(resourceOffset: Int64(data.count), data: Data(), range: 0 ..< 0, complete: true))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
subscriber.putError(.generic)
|
||||||
|
}
|
||||||
|
subscriber.putCompletion()
|
||||||
|
|
||||||
|
EngineTempBox.shared.dispose(tempFile)
|
||||||
|
}
|
||||||
|
}, error: { _ in
|
||||||
|
subscriber.putError(.generic)
|
||||||
|
}, completed: nil)
|
||||||
|
disposable.set(ActionDisposable {
|
||||||
|
signalDisposable?.dispose()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionDisposable {
|
||||||
|
if let requestId = requestId {
|
||||||
|
PHImageManager.default().cancelImageRequest(requestId)
|
||||||
}
|
}
|
||||||
|
disposable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return throttlingContext.wrap(priority: .default, signal: signal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchLocalFileVideoMediaResource(account: Account, resource: LocalFileVideoMediaResource) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
|
||||||
|
let signal = Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> { subscriber in
|
||||||
|
subscriber.putNext(.reset)
|
||||||
|
|
||||||
|
var filteredPath = resource.path
|
||||||
|
if filteredPath.hasPrefix("file://") {
|
||||||
|
filteredPath = String(filteredPath[filteredPath.index(filteredPath.startIndex, offsetBy: "file://".count)])
|
||||||
|
}
|
||||||
|
|
||||||
|
let avAsset = AVURLAsset(url: URL(fileURLWithPath: filteredPath))
|
||||||
|
var adjustments: TGVideoEditAdjustments?
|
||||||
|
if let videoAdjustments = resource.adjustments {
|
||||||
|
if let dict = NSKeyedUnarchiver.unarchiveObject(with: videoAdjustments.data.makeData()) as? [AnyHashable : Any] {
|
||||||
|
adjustments = TGVideoEditAdjustments(dictionary: dict)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4")
|
||||||
|
let updatedSize = Atomic<Int64>(value: 0)
|
||||||
|
let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in
|
||||||
|
if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
|
||||||
|
return LegacyPaintEntityRenderer(account: account, adjustments: adjustments)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let signal: SSignal
|
||||||
|
if filteredPath.contains(".jpg"), let entityRenderer = entityRenderer {
|
||||||
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: filteredPath), options: [.mappedRead]), let image = UIImage(data: data) {
|
||||||
|
let durationSignal: SSignal = SSignal(generator: { subscriber in
|
||||||
|
let disposable = (entityRenderer.duration()).start(next: { duration in
|
||||||
|
subscriber.putNext(duration)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
})
|
||||||
|
|
||||||
|
return SBlockDisposable(block: {
|
||||||
|
disposable.dispose()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
let signalDisposable = signal.start(next: { next in
|
signal = durationSignal.map(toSignal: { duration -> SSignal in
|
||||||
if let result = next as? TGMediaVideoConversionResult {
|
if let duration = duration as? Double {
|
||||||
var value = stat()
|
return TGMediaVideoConverter.renderUIImage(image, duration: duration, adjustments: adjustments, path: tempFile.path, watcher: VideoConversionWatcher(update: { path, size in
|
||||||
if stat(result.fileURL.path, &value) == 0 {
|
var value = stat()
|
||||||
|
if stat(path, &value) == 0 {
|
||||||
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
|
||||||
|
var range: Range<Int64>?
|
||||||
|
let _ = updatedSize.modify { updatedSize in
|
||||||
|
range = updatedSize ..< value.st_size
|
||||||
|
return value.st_size
|
||||||
|
}
|
||||||
|
//print("size = \(Int(value.st_size)), range: \(range!)")
|
||||||
|
subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}), entityRenderer: entityRenderer)!
|
||||||
|
} else {
|
||||||
|
return SSignal.single(nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
signal = SSignal.single(nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, path: tempFile.path, watcher: VideoConversionWatcher(update: { path, size in
|
||||||
|
var value = stat()
|
||||||
|
if stat(path, &value) == 0 {
|
||||||
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
|
||||||
|
var range: Range<Int64>?
|
||||||
|
let _ = updatedSize.modify { updatedSize in
|
||||||
|
range = updatedSize ..< Int64(value.st_size)
|
||||||
|
return value.st_size
|
||||||
|
}
|
||||||
|
//print("size = \(Int(value.st_size)), range: \(range!)")
|
||||||
|
subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}), entityRenderer: entityRenderer)!
|
||||||
|
}
|
||||||
|
|
||||||
|
let signalDisposable = signal.start(next: { next in
|
||||||
|
if let result = next as? TGMediaVideoConversionResult {
|
||||||
|
var value = stat()
|
||||||
|
if stat(result.fileURL.path, &value) == 0 {
|
||||||
// if config.remuxToFMp4 {
|
// if config.remuxToFMp4 {
|
||||||
// let tempFile = TempBox.shared.tempFile(fileName: "video.mp4")
|
// let tempFile = TempBox.shared.tempFile(fileName: "video.mp4")
|
||||||
// if FFMpegRemuxer.remux(result.fileURL.path, to: tempFile.path) {
|
// if FFMpegRemuxer.remux(result.fileURL.path, to: tempFile.path) {
|
||||||
@ -424,35 +409,34 @@ func fetchLocalFileVideoMediaResource(account: Account, resource: LocalFileVideo
|
|||||||
// } else {
|
// } else {
|
||||||
// subscriber.putNext(.moveLocalFile(path: result.fileURL.path))
|
// subscriber.putNext(.moveLocalFile(path: result.fileURL.path))
|
||||||
// }
|
// }
|
||||||
if let data = try? Data(contentsOf: result.fileURL, options: [.mappedRead]) {
|
if let data = try? Data(contentsOf: result.fileURL, options: [.mappedRead]) {
|
||||||
var range: Range<Int64>?
|
var range: Range<Int64>?
|
||||||
let _ = updatedSize.modify { updatedSize in
|
let _ = updatedSize.modify { updatedSize in
|
||||||
range = updatedSize ..< value.st_size
|
range = updatedSize ..< value.st_size
|
||||||
return value.st_size
|
return value.st_size
|
||||||
}
|
|
||||||
//print("finish size = \(Int(value.st_size)), range: \(range!)")
|
|
||||||
subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false))
|
|
||||||
subscriber.putNext(.replaceHeader(data: data, range: 0 ..< 1024))
|
|
||||||
subscriber.putNext(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: true))
|
|
||||||
|
|
||||||
EngineTempBox.shared.dispose(tempFile)
|
|
||||||
}
|
}
|
||||||
|
//print("finish size = \(Int(value.st_size)), range: \(range!)")
|
||||||
|
subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false))
|
||||||
|
subscriber.putNext(.replaceHeader(data: data, range: 0 ..< 1024))
|
||||||
|
subscriber.putNext(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: true))
|
||||||
|
|
||||||
|
EngineTempBox.shared.dispose(tempFile)
|
||||||
}
|
}
|
||||||
subscriber.putCompletion()
|
|
||||||
}
|
}
|
||||||
}, error: { _ in
|
subscriber.putCompletion()
|
||||||
}, completed: nil)
|
|
||||||
|
|
||||||
let disposable = ActionDisposable {
|
|
||||||
signalDisposable?.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
return ActionDisposable {
|
|
||||||
disposable.dispose()
|
|
||||||
}
|
}
|
||||||
|
}, error: { _ in
|
||||||
|
}, completed: nil)
|
||||||
|
|
||||||
|
let disposable = ActionDisposable {
|
||||||
|
signalDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionDisposable {
|
||||||
|
disposable.dispose()
|
||||||
}
|
}
|
||||||
return throttlingContext.wrap(priority: .default, signal: signal)
|
|
||||||
}
|
}
|
||||||
|
return throttlingContext.wrap(priority: .default, signal: signal)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func fetchVideoLibraryMediaResourceHash(resource: VideoLibraryMediaResource) -> Signal<Data?, NoError> {
|
public func fetchVideoLibraryMediaResourceHash(resource: VideoLibraryMediaResource) -> Signal<Data?, NoError> {
|
||||||
|
@ -137,6 +137,10 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation.asChatLocation, chatLocationContextHolder: params.chatLocationContextHolder, subject: params.subject, botStart: params.botStart, attachBotStart: params.attachBotStart, botAppStart: params.botAppStart, peekData: params.peekData, peerNearbyData: params.peerNearbyData, chatListFilter: params.chatListFilter, chatNavigationStack: params.chatNavigationStack)
|
controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation.asChatLocation, chatLocationContextHolder: params.chatLocationContextHolder, subject: params.subject, botStart: params.botStart, attachBotStart: params.attachBotStart, botAppStart: params.botAppStart, peekData: params.peekData, peerNearbyData: params.peerNearbyData, chatListFilter: params.chatListFilter, chatNavigationStack: params.chatNavigationStack)
|
||||||
|
|
||||||
|
if let botAppStart = params.botAppStart, case let .peer(peer) = params.chatLocation {
|
||||||
|
controller.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
controller.purposefulAction = params.purposefulAction
|
controller.purposefulAction = params.purposefulAction
|
||||||
if let search = params.activateMessageSearch {
|
if let search = params.activateMessageSearch {
|
||||||
|
@ -219,6 +219,11 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
if let navigationController = navigationController {
|
if let navigationController = navigationController {
|
||||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), attachBotStart: attachBotStart))
|
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), attachBotStart: attachBotStart))
|
||||||
}
|
}
|
||||||
|
case let .withBotApp(botAppStart):
|
||||||
|
context.sharedContext.applicationBindings.dismissNativeController()
|
||||||
|
if let navigationController = navigationController {
|
||||||
|
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), botAppStart: botAppStart))
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -738,7 +743,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
} else if let domain = domain {
|
} else if let domain = domain {
|
||||||
var result = "https://t.me/\(domain)"
|
var result = "https://t.me/\(domain)"
|
||||||
if let appName {
|
if let appName {
|
||||||
result += "\(appName)"
|
result += "/\(appName)"
|
||||||
}
|
}
|
||||||
if let startApp {
|
if let startApp {
|
||||||
result += "?startapp=\(startApp)"
|
result += "?startapp=\(startApp)"
|
||||||
|
@ -206,10 +206,7 @@ public func chatTranslationState(context: AccountContext, peerId: EnginePeer.Id)
|
|||||||
var fromLangs: [String: Int] = [:]
|
var fromLangs: [String: Int] = [:]
|
||||||
var count = 0
|
var count = 0
|
||||||
for message in messages {
|
for message in messages {
|
||||||
if let _ = URL(string: message.text) {
|
if message.effectivelyIncoming(context.account.peerId), message.text.count >= 10 {
|
||||||
continue
|
|
||||||
}
|
|
||||||
if message.text.count >= 10 {
|
|
||||||
var text = String(message.text.prefix(256))
|
var text = String(message.text.prefix(256))
|
||||||
if var entities = message.textEntitiesAttribute?.entities.filter({ [.Pre, .Code, .Url, .Email, .Mention, .Hashtag, .BotCommand].contains($0.type) }) {
|
if var entities = message.textEntitiesAttribute?.entities.filter({ [.Pre, .Code, .Url, .Email, .Mention, .Hashtag, .BotCommand].contains($0.type) }) {
|
||||||
entities = entities.sorted(by: { $0.range.lowerBound > $1.range.lowerBound })
|
entities = entities.sorted(by: { $0.range.lowerBound > $1.range.lowerBound })
|
||||||
|
Loading…
x
Reference in New Issue
Block a user