diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift index 22e8301f4a..d80d2ad40b 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift @@ -1506,14 +1506,21 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS return UIMenu(children: actions) } + private var currentSpeechHolder: SpeechSynthesizerHolder? @objc func _accessibilitySpeak(_ sender: Any) { var text = "" self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in text = current.inputText.attributedSubstring(from: NSMakeRange(current.selectionRange.lowerBound, current.selectionRange.count)).string return (current, inputMode) } - let _ = speakText(context: self.context, text: text) - + if let speechHolder = speakText(context: self.context, text: text) { + speechHolder.completion = { [weak self, weak speechHolder] in + if let strongSelf = self, strongSelf.currentSpeechHolder == speechHolder { + strongSelf.currentSpeechHolder = nil + } + } + self.currentSpeechHolder = speechHolder + } if #available(iOS 13.0, *) { UIMenuController.shared.hideMenu() } else { diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index 2602b0add5..171bc47881 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -710,19 +710,19 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex ChatListNodeAdditionalCategory( id: AdditionalExcludeCategoryId.muted.rawValue, icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Muted"), color: .white), cornerRadius: 12.0, color: .red), - smallIcon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Muted"), color: .white), iconScale: 0.6, cornerRadius: 11.0, circleCorners: true, color: .red), + smallIcon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Muted"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .red), title: presentationData.strings.ChatListFolder_CategoryMuted ), ChatListNodeAdditionalCategory( id: AdditionalExcludeCategoryId.read.rawValue, icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Read"), color: .white), cornerRadius: 12.0, color: .blue), - smallIcon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Read"), color: .white), iconScale: 0.6, cornerRadius: 11.0, circleCorners: true, color: .blue), + smallIcon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Read"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .blue), title: presentationData.strings.ChatListFolder_CategoryRead ), ChatListNodeAdditionalCategory( id: AdditionalExcludeCategoryId.archived.rawValue, icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Archive"), color: .white), cornerRadius: 12.0, color: .yellow), - smallIcon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Archive"), color: .white), iconScale: 0.6, cornerRadius: 11.0, circleCorners: true, color: .yellow), + smallIcon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Archive"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .yellow), title: presentationData.strings.ChatListFolder_CategoryArchived ), ] diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index b4e95c1dca..4919679517 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -411,7 +411,7 @@ private func forumGeneralRevealOptions(strings: PresentationStrings, theme: Pres if canOpenClose && !hiddenByDefault { if !isEditing { if !isClosed { -// options.append(ItemListRevealOption(key: RevealOptionKey.close.rawValue, title: strings.ChatList_CloseAction, icon: closeIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.inactive.foregroundColor)) + } else { options.append(ItemListRevealOption(key: RevealOptionKey.open.rawValue, title: strings.ChatList_StartAction, icon: startIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) } @@ -2358,6 +2358,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if item.interaction.isInlineMode { titleLeftCutout = 22.0 } + + if let titleAttributedStringValue = titleAttributedString, titleAttributedStringValue.length == 0 { + titleAttributedString = NSAttributedString(string: " ", font: titleFont, textColor: theme.titleColor) + } let titleRectWidth = rawContentWidth - dateLayout.size.width - 10.0 - statusWidth - titleIconsWidth var titleCutout: TextNodeCutout? @@ -2533,8 +2537,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } strongSelf.currentOnline = online - if let currentHiddenOffset = currentItem?.hiddenOffset, item.hiddenOffset, currentHiddenOffset != item.hiddenOffset { - strongSelf.supernode?.insertSubnode(strongSelf, at: 0) + if item.hiddenOffset { + strongSelf.layer.zPosition = -1.0 } if case .groupReference = item.content { diff --git a/submodules/Display/Source/DisplayLinkAnimator.swift b/submodules/Display/Source/DisplayLinkAnimator.swift index 2089800711..d1e7e99f81 100644 --- a/submodules/Display/Source/DisplayLinkAnimator.swift +++ b/submodules/Display/Source/DisplayLinkAnimator.swift @@ -1,10 +1,20 @@ import Foundation import UIKit +public protocol SharedDisplayLinkDriverLink: AnyObject { + var isPaused: Bool { get set } + + func invalidate() +} + public final class SharedDisplayLinkDriver { + public typealias Link = SharedDisplayLinkDriverLink + public static let shared = SharedDisplayLinkDriver() - public final class Link { + private let useNative: Bool + + public final class LinkImpl: Link { private let driver: SharedDisplayLinkDriver public let needsHighestFramerate: Bool let update: () -> Void @@ -28,10 +38,52 @@ public final class SharedDisplayLinkDriver { } } - private final class RequestContext { - weak var link: Link? + public final class NativeLinkImpl: Link { + private var displayLink: CADisplayLink? - init(link: Link) { + public var isPaused: Bool = false { + didSet { + self.displayLink?.isPaused = self.isPaused + } + } + + init(needsHighestFramerate: Bool, update: @escaping () -> Void) { + let displayLink = CADisplayLink(target: DisplayLinkTarget { + update() + }, selector: #selector(DisplayLinkTarget.event)) + + if #available(iOS 15.0, *) { + let maxFps = Float(UIScreen.main.maximumFramesPerSecond) + if maxFps > 61.0 { + let frameRateRange: CAFrameRateRange + if needsHighestFramerate { + frameRateRange = CAFrameRateRange(minimum: 30.0, maximum: 120.0, preferred: 120.0) + } else { + frameRateRange = .default + } + if displayLink.preferredFrameRateRange != frameRateRange { + displayLink.preferredFrameRateRange = frameRateRange + } + } + } + + self.displayLink = displayLink + displayLink.add(to: .main, forMode: .common) + } + + deinit { + self.displayLink?.invalidate() + } + + public func invalidate() { + self.displayLink?.invalidate() + } + } + + private final class RequestContext { + weak var link: LinkImpl? + + init(link: LinkImpl) { self.link = link } } @@ -43,6 +95,8 @@ public final class SharedDisplayLinkDriver { private var isInForeground: Bool = false private init() { + self.useNative = false + let _ = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil, using: { [weak self] _ in guard let self else { return @@ -58,11 +112,15 @@ public final class SharedDisplayLinkDriver { self.update() }) - switch UIApplication.shared.applicationState { - case .active: + if Bundle.main.bundlePath.hasSuffix(".appex") { self.isInForeground = true - default: - self.isInForeground = false + } else { + switch UIApplication.shared.applicationState { + case .active: + self.isInForeground = true + default: + self.isInForeground = false + } } self.update() @@ -95,14 +153,17 @@ public final class SharedDisplayLinkDriver { displayLink.add(to: .main, forMode: .common) } if #available(iOS 15.0, *) { - let frameRateRange: CAFrameRateRange - if needHighestFramerate { - frameRateRange = CAFrameRateRange(minimum: 30.0, maximum: 120.0, preferred: 120.0) - } else { - frameRateRange = .default - } - if displayLink.preferredFrameRateRange != frameRateRange { - displayLink.preferredFrameRateRange = frameRateRange + let maxFps = Float(UIScreen.main.maximumFramesPerSecond) + if maxFps > 61.0 { + let frameRateRange: CAFrameRateRange + if needHighestFramerate { + frameRateRange = CAFrameRateRange(minimum: 30.0, maximum: 120.0, preferred: 120.0) + } else { + frameRateRange = .default + } + if displayLink.preferredFrameRateRange != frameRateRange { + displayLink.preferredFrameRateRange = frameRateRange + } } } displayLink.isPaused = false @@ -139,12 +200,16 @@ public final class SharedDisplayLinkDriver { } public func add(needsHighestFramerate: Bool = true, _ update: @escaping () -> Void) -> Link { - let link = Link(driver: self, needsHighestFramerate: needsHighestFramerate, update: update) - self.requests.append(RequestContext(link: link)) - - self.update() - - return link + if self.useNative { + return NativeLinkImpl(needsHighestFramerate: needsHighestFramerate, update: update) + } else { + let link = LinkImpl(driver: self, needsHighestFramerate: needsHighestFramerate, update: update) + self.requests.append(RequestContext(link: link)) + + self.update() + + return link + } } } diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index 80c40276b4..b0454a7af2 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -227,6 +227,8 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { private let pagingEnabledPromise = ValuePromise(true) + private var currentSpeechHolder: SpeechSynthesizerHolder? + init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) { self.context = context self.presentationData = presentationData @@ -378,7 +380,14 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { window.rootViewController?.present(controller, animated: true) } case .speak: - let _ = speakText(context: strongSelf.context, text: string) + if let speechHolder = speakText(context: strongSelf.context, text: string) { + speechHolder.completion = { [weak self, weak speechHolder] in + if let strongSelf = self, strongSelf.currentSpeechHolder == speechHolder { + strongSelf.currentSpeechHolder = nil + } + } + strongSelf.currentSpeechHolder = speechHolder + } case .translate: if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController { let controller = TranslateScreen(context: strongSelf.context, text: string, canCopy: true, fromLanguage: nil) diff --git a/submodules/GameUI/Sources/GameControllerNode.swift b/submodules/GameUI/Sources/GameControllerNode.swift index d54b1fec04..5ee44cd3fb 100644 --- a/submodules/GameUI/Sources/GameControllerNode.swift +++ b/submodules/GameUI/Sources/GameControllerNode.swift @@ -144,9 +144,9 @@ final class GameControllerNode: ViewControllerTracingNode { if eventName == "share_game" || eventName == "share_score" { if let (botPeer, gameName) = self.shareData(), let addressName = botPeer.addressName, !addressName.isEmpty, !gameName.isEmpty { if eventName == "share_score" { - self.present(ShareController(context: self.context, subject: .fromExternal({ [weak self] peerIds, text, account, _ in + self.present(ShareController(context: self.context, subject: .fromExternal({ [weak self] peerIds, threadIds, text, account, _ in if let strongSelf = self, let message = strongSelf.message { - let signals = peerIds.map { TelegramEngine(account: account).messages.forwardGameWithScore(messageId: message.id, to: $0, as: nil) } + let signals = peerIds.map { TelegramEngine(account: account).messages.forwardGameWithScore(messageId: message.id, to: $0, threadId: threadIds[$0], as: nil) } return .single(.preparing(false)) |> castError(ShareControllerError.self) |> then( diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index 6663053c28..dccb694f8a 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -1193,6 +1193,10 @@ public final class ListMessageFileItemNode: ListMessageNode { let _ = extensionTextApply() strongSelf.currentIconImage = iconImage + + if let updateIconImageSignal, let iconImage, case .albumArt = iconImage { + strongSelf.iconStatusNode.setBackgroundImage(updateIconImageSignal) + } if let iconImageApply = iconImageApply { if let updateImageSignal = updateIconImageSignal { diff --git a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift index e95bdd9d0b..a45e5ae06d 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift @@ -722,7 +722,10 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { self.updateProgressAnimations() } + private var isCollapsed = false public func setCollapsed(_ collapsed: Bool, animated: Bool) { + self.isCollapsed = collapsed + let alpha: CGFloat = collapsed ? 0.0 : 1.0 let backgroundScale: CGFloat = collapsed ? 0.4 : 1.0 let handleScale: CGFloat = collapsed ? 0.2 : 1.0 @@ -956,10 +959,12 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { if let handleNodeContainer = node.handleNodeContainer { handleNodeContainer.bounds = bounds.offsetBy(dx: -floorToScreenPixels(bounds.size.width * progress), dy: 0.0) - if handleNodeContainer.alpha.isZero { - handleNodeContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + if !self.isCollapsed { + if handleNodeContainer.alpha.isZero { + handleNodeContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + handleNodeContainer.alpha = 1.0 } - handleNodeContainer.alpha = 1.0 } } else if let statusValue = self.statusValue { var actualTimestamp: Double @@ -987,10 +992,12 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { if let handleNodeContainer = node.handleNodeContainer { handleNodeContainer.bounds = bounds.offsetBy(dx: -floorToScreenPixels(bounds.size.width * progress), dy: 0.0) - if handleNodeContainer.alpha.isZero { - handleNodeContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + if !self.isCollapsed { + if handleNodeContainer.alpha.isZero { + handleNodeContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + handleNodeContainer.alpha = 1.0 } - handleNodeContainer.alpha = 1.0 } } else { node.foregroundNode.frame = CGRect(origin: backgroundFrame.origin, size: CGSize(width: 0.0, height: backgroundFrame.size.height)) diff --git a/submodules/MusicAlbumArtResources/Sources/ExternalMusicAlbumArtResources.swift b/submodules/MusicAlbumArtResources/Sources/ExternalMusicAlbumArtResources.swift index c7906f79d1..9949f1a771 100644 --- a/submodules/MusicAlbumArtResources/Sources/ExternalMusicAlbumArtResources.swift +++ b/submodules/MusicAlbumArtResources/Sources/ExternalMusicAlbumArtResources.swift @@ -60,103 +60,4 @@ public class ExternalMusicAlbumArtResource: Equatable { public func fetchExternalMusicAlbumArtResource(engine: TelegramEngine, file: FileMediaReference?, resource: ExternalMusicAlbumArtResource) -> Signal { return engine.resources.fetchAlbumCover(file: file, title: resource.title, performer: resource.performer, isThumbnail: resource.isThumbnail) - - /*return Signal { subscriber in - if resource.performer.isEmpty || resource.performer.lowercased().trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) == "unknown artist" || resource.title.isEmpty { - subscriber.putError(.generic) - return EmptyDisposable - } else { - let excludeWords: [String] = [ - " vs. ", - " vs ", - " versus ", - " ft. ", - " ft ", - " featuring ", - " feat. ", - " feat ", - " presents ", - " pres. ", - " pres ", - " and ", - " & ", - " . " - ] - - var performer = resource.performer - - for word in excludeWords { - performer = performer.replacingOccurrences(of: word, with: " ") - } - - let metaUrl = "https://itunes.apple.com/search?term=\(urlEncodedStringFromString("\(performer) \(resource.title)"))&entity=song&limit=4" - - let title = resource.title.lowercased() - let isMix = title.contains("remix") || title.contains("mixed") - - let fetchDisposable = MetaDisposable() - - let disposable = fetchHttpResource(url: metaUrl).start(next: { result in - if case let .dataPart(_, data, _, complete) = result, complete { - guard let dict = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] else { - subscriber.putError(.generic) - return - } - - guard let results = dict["results"] as? [Any] else { - subscriber.putError(.generic) - return - } - - var matchingResult: Any? - for result in results { - if let result = result as? [String: Any] { - let title = ((result["trackCensoredName"] as? String) ?? (result["trackName"] as? String))?.lowercased() ?? "" - let resultIsMix = title.contains("remix") || title.contains("mixed") - if isMix == resultIsMix { - matchingResult = result - break - } - } - } - - if matchingResult == nil { - matchingResult = results.first - } - - guard let result = matchingResult as? [String: Any] else { - subscriber.putError(.generic) - return - } - - guard var artworkUrl = result["artworkUrl100"] as? String else { - subscriber.putError(.generic) - return - } - - if !resource.isThumbnail { - artworkUrl = artworkUrl.replacingOccurrences(of: "100x100", with: "600x600") - } - - if artworkUrl.isEmpty { - subscriber.putError(.generic) - return - } else { - fetchDisposable.set(engine.resources.httpData(url: artworkUrl).start(next: { next in - let file = EngineTempBox.shared.tempFile(fileName: "image.jpg") - let _ = try? next.write(to: URL(fileURLWithPath: file.path)) - subscriber.putNext(.moveTempFile(file: file)) - }, completed: { - subscriber.putCompletion() - })) - } - } - }) - - return ActionDisposable { - disposable.dispose() - fetchDisposable.dispose() - } - } - }*/ } diff --git a/submodules/Postbox/BUILD b/submodules/Postbox/BUILD index d9ce7c24b3..a86c579b54 100644 --- a/submodules/Postbox/BUILD +++ b/submodules/Postbox/BUILD @@ -18,6 +18,7 @@ swift_library( "//submodules/ManagedFile:ManagedFile", "//submodules/Utils/RangeSet:RangeSet", "//submodules/CryptoUtils", + "//submodules/Utils/DarwinDirStat", ], visibility = [ "//visibility:public", diff --git a/submodules/Postbox/Sources/TimeBasedCleanup.swift b/submodules/Postbox/Sources/TimeBasedCleanup.swift index e96254c4a6..2d2e86fd18 100644 --- a/submodules/Postbox/Sources/TimeBasedCleanup.swift +++ b/submodules/Postbox/Sources/TimeBasedCleanup.swift @@ -1,5 +1,7 @@ import Foundation import SwiftSignalKit +import DarwinDirStat + private typealias SignalKitTimer = SwiftSignalKit.Timer struct InodeInfo { @@ -52,7 +54,7 @@ private final class TempScanDatabase { init?(queue: Queue, basePath: String) { self.queue = queue - guard let valueBox = SqliteValueBox(basePath: basePath, queue: queue, isTemporary: true, isReadOnly: false, useCaches: true, removeDatabaseOnError: true, encryptionParameters: nil, upgradeProgress: { _ in }) else { + guard let valueBox = SqliteValueBox(basePath: basePath, queue: queue, isTemporary: true, isReadOnly: false, useCaches: true, removeDatabaseOnError: true, encryptionParameters: nil, upgradeProgress: { _ in }, inMemory: true) else { return nil } self.valueBox = valueBox @@ -127,9 +129,11 @@ private final class TempScanDatabase { } } -private func scanFiles(at path: String, olderThan minTimestamp: Int32, includeSubdirectories: Bool, tempDatabase: TempScanDatabase) -> ScanFilesResult { +private func scanFiles(at path: String, olderThan minTimestamp: Int32, includeSubdirectories: Bool, performSizeMapping: Bool, tempDatabase: TempScanDatabase) -> ScanFilesResult { var result = ScanFilesResult() + var subdirectories: [String] = [] + if let dp = opendir(path) { let pathBuffer = malloc(2048).assumingMemoryBound(to: Int8.self) defer { @@ -156,9 +160,7 @@ private func scanFiles(at path: String, olderThan minTimestamp: Int32, includeSu if (((value.st_mode) & S_IFMT) == S_IFDIR) { if includeSubdirectories { if let subPath = String(data: Data(bytes: pathBuffer, count: strnlen(pathBuffer, 1024)), encoding: .utf8) { - let subResult = scanFiles(at: subPath, olderThan: minTimestamp, includeSubdirectories: true, tempDatabase: tempDatabase) - result.totalSize += subResult.totalSize - result.unlinkedCount += subResult.unlinkedCount + subdirectories.append(subPath) } } } else { @@ -167,7 +169,9 @@ private func scanFiles(at path: String, olderThan minTimestamp: Int32, includeSu result.unlinkedCount += 1 } else { result.totalSize += UInt64(value.st_size) - tempDatabase.add(pathBuffer: pathBuffer, pathSize: strnlen(pathBuffer, 1024), size: Int64(value.st_size), timestamp: Int32(value.st_mtimespec.tv_sec)) + if performSizeMapping { + tempDatabase.add(pathBuffer: pathBuffer, pathSize: strnlen(pathBuffer, 1024), size: Int64(value.st_size), timestamp: Int32(value.st_mtimespec.tv_sec)) + } } } } @@ -175,6 +179,14 @@ private func scanFiles(at path: String, olderThan minTimestamp: Int32, includeSu closedir(dp) } + if includeSubdirectories { + for subPath in subdirectories { + let subResult = scanFiles(at: subPath, olderThan: minTimestamp, includeSubdirectories: true, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase) + result.totalSize += subResult.totalSize + result.unlinkedCount += subResult.unlinkedCount + } + } + return result } @@ -259,6 +271,21 @@ private func scanFiles(at path: String, olderThan minTimestamp: Int32, includeSu } }*/ +private func statForDirectory(path: String) -> Int64 { + var s = darwin_dirstat() + var result = dirstat_np(path, 1, &s, MemoryLayout.size) + if result != -1 { + return Int64(s.total_size) + } else { + result = dirstat_np(path, 0, &s, MemoryLayout.size) + if result != -1 { + return Int64(s.total_size) + } else { + return 0 + } + } +} + private final class TimeBasedCleanupImpl { private let queue: Queue private let storageBox: StorageBox @@ -298,6 +325,11 @@ private final class TimeBasedCleanupImpl { } private func resetScan(general: Int32, shortLived: Int32, gigabytesLimit: Int32) { + if "".isEmpty { + //TODO:remove debugging + return + } + let generalPaths = self.generalPaths let totalSizeBasedPath = self.totalSizeBasedPath let shortLivedPaths = self.shortLivedPaths @@ -329,10 +361,42 @@ private final class TimeBasedCleanupImpl { let bytesLimit = UInt64(gigabytesLimit) * 1024 * 1024 * 1024 //#endif + var totalApproximateSize: Int64 = 0 + for path in shortLivedPaths { + totalApproximateSize += statForDirectory(path: path) + } + for path in generalPaths { + totalApproximateSize += statForDirectory(path: path) + } + + if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: totalSizeBasedPath), includingPropertiesForKeys: [.fileSizeKey, .fileResourceIdentifierKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) { + var fileIds = Set() + loop: for url in enumerator { + guard let url = url as? URL else { + continue + } + if let fileId = (try? url.resourceValues(forKeys: Set([.fileResourceIdentifierKey])))?.fileResourceIdentifier as? Data { + if fileIds.contains(fileId) { + continue loop + } + + if let value = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize, value != 0 { + fileIds.insert(fileId) + totalApproximateSize += Int64(value) + } + } + } + } + + var performSizeMapping = true + if totalApproximateSize <= bytesLimit { + performSizeMapping = false + } + let oldestShortLivedTimestamp = timestamp - shortLived let oldestGeneralTimestamp = timestamp - general for path in shortLivedPaths { - let scanResult = scanFiles(at: path, olderThan: oldestShortLivedTimestamp, includeSubdirectories: true, tempDatabase: tempDatabase) + let scanResult = scanFiles(at: path, olderThan: oldestShortLivedTimestamp, includeSubdirectories: true, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase) if !paths.contains(path) { paths.append(path) } @@ -342,7 +406,7 @@ private final class TimeBasedCleanupImpl { var totalLimitSize: UInt64 = 0 for path in generalPaths { - let scanResult = scanFiles(at: path, olderThan: oldestGeneralTimestamp, includeSubdirectories: true, tempDatabase: tempDatabase) + let scanResult = scanFiles(at: path, olderThan: oldestGeneralTimestamp, includeSubdirectories: true, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase) if !paths.contains(path) { paths.append(path) } @@ -350,7 +414,7 @@ private final class TimeBasedCleanupImpl { totalLimitSize += scanResult.totalSize } do { - let scanResult = scanFiles(at: totalSizeBasedPath, olderThan: 0, includeSubdirectories: false, tempDatabase: tempDatabase) + let scanResult = scanFiles(at: totalSizeBasedPath, olderThan: 0, includeSubdirectories: false, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase) if !paths.contains(totalSizeBasedPath) { paths.append(totalSizeBasedPath) } diff --git a/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift b/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift index 9852a8d08c..ca2d5d5a5d 100644 --- a/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift +++ b/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift @@ -857,6 +857,25 @@ public final class SemanticStatusNode: ASControlNode { self.setNeedsDisplay() } } + + public func setBackgroundImage(_ image: Signal<(TransformImageArguments) -> DrawingContext?, NoError>) { + let start = CACurrentMediaTime() + self.disposable = combineLatest(queue: Queue.mainQueue(), image, self.hasLayoutPromise.get()).start(next: { [weak self] transform, ready in + guard let strongSelf = self, ready else { + return + } + let context = transform(TransformImageArguments(corners: ImageCorners(radius: strongSelf.bounds.width / 2.0), imageSize: strongSelf.bounds.size, boundingSize: strongSelf.bounds.size, intrinsicInsets: UIEdgeInsets())) + + let previousAppearanceContext = strongSelf.appearanceContext + strongSelf.appearanceContext = strongSelf.appearanceContext.withUpdatedBackgroundImage(context?.generateImage()) + + if CACurrentMediaTime() - start > 0.3 { + strongSelf.transitionContext = SemanticStatusNodeTransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousStateContext: nil, previousAppearanceContext: previousAppearanceContext, completion: {}) + strongSelf.updateAnimations() + } + strongSelf.setNeedsDisplay() + }) + } private var animator: ConstantDisplayLinkAnimator? @@ -889,23 +908,8 @@ public final class SemanticStatusNode: ASControlNode { self.isOpaque = false self.displaysAsynchronously = true - if let image = image { - let start = CACurrentMediaTime() - self.disposable = combineLatest(queue: Queue.mainQueue(), image, self.hasLayoutPromise.get()).start(next: { [weak self] transform, ready in - guard let strongSelf = self, ready else { - return - } - let context = transform(TransformImageArguments(corners: ImageCorners(radius: strongSelf.bounds.width / 2.0), imageSize: strongSelf.bounds.size, boundingSize: strongSelf.bounds.size, intrinsicInsets: UIEdgeInsets())) - - let previousAppearanceContext = strongSelf.appearanceContext - strongSelf.appearanceContext = strongSelf.appearanceContext.withUpdatedBackgroundImage(context?.generateImage()) - - if CACurrentMediaTime() - start > 0.3 { - strongSelf.transitionContext = SemanticStatusNodeTransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousStateContext: nil, previousAppearanceContext: previousAppearanceContext, completion: {}) - strongSelf.updateAnimations() - } - strongSelf.setNeedsDisplay() - }) + if let image { + self.setBackgroundImage(image) } } diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index ccb9c4f094..08a96013cd 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -65,7 +65,7 @@ public enum ShareControllerSubject { case image([ImageRepresentationWithReference]) case media(AnyMediaReference) case mapMedia(TelegramMediaMap) - case fromExternal(([PeerId], String, Account, Bool) -> Signal) + case fromExternal(([PeerId], [PeerId: Int64], String, Account, Bool) -> Signal) } private enum ExternalShareItem { @@ -695,7 +695,7 @@ public final class ShareController: ViewController { shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messagesToEnqueue)) } case let .fromExternal(f): - return f(peerIds, text, strongSelf.currentAccount, silently) + return f(peerIds, topicIds, text, strongSelf.currentAccount, silently) |> map { state -> ShareState in switch state { case let .preparing(long): diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 360ce83835..a1a3f1679b 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -236,7 +236,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate ]) return ContextController.Items(content: .list(items), animationCache: nil) } - let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(ShareContextReferenceContentSource(sourceNode: node, customPosition: CGPoint(x: 0.0, y: -116.0))), items: items, gesture: gesture) + let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(ShareContextReferenceContentSource(sourceNode: node, customPosition: CGPoint(x: 0.0, y: fromForeignApp ? -116.0 : 0.0))), items: items, gesture: gesture) contextController.immediateItemsTransitionAnimation = true strongSelf.present?(contextController) } diff --git a/submodules/ShareItems/Sources/ShareItems.swift b/submodules/ShareItems/Sources/ShareItems.swift index 9ceeaf925a..6d66c748a8 100644 --- a/submodules/ShareItems/Sources/ShareItems.swift +++ b/submodules/ShareItems/Sources/ShareItems.swift @@ -414,7 +414,7 @@ public func preparedShareItems(account: Account, to peerId: PeerId, dataItems: [ }) } -public func sentShareItems(account: Account, to peerIds: [PeerId], items: [PreparedShareItemContent], silently: Bool) -> Signal { +public func sentShareItems(account: Account, to peerIds: [PeerId], threadIds: [PeerId: Int64], items: [PreparedShareItemContent], silently: Bool) -> Signal { var messages: [EnqueueMessage] = [] var groupingKey: Int64? var mediaTypes: (photo: Int, video: Int, music: Int, other: Int) = (0, 0, 0, 0) @@ -474,7 +474,7 @@ public func sentShareItems(account: Account, to peerIds: [PeerId], items: [Prepa } } - return enqueueMessagesToMultiplePeers(account: account, peerIds: peerIds, messages: messages) + return enqueueMessagesToMultiplePeers(account: account, peerIds: peerIds, threadIds: threadIds, messages: messages) |> castError(Void.self) |> mapToSignal { messageIds -> Signal in return TelegramEngine(account: account).data.subscribe(EngineDataMap( diff --git a/submodules/Speak/Sources/Speak.swift b/submodules/Speak/Sources/Speak.swift index 845550e67b..7afa3306bf 100644 --- a/submodules/Speak/Sources/Speak.swift +++ b/submodules/Speak/Sources/Speak.swift @@ -19,6 +19,10 @@ public class SpeechSynthesizerHolder: NSObject, AVSpeechSynthesizerDelegate { self.speechSynthesizer.delegate = self } + deinit { + self.stop() + } + public func stop() { self.speechSynthesizer.stopSpeaking(at: .immediate) } diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index 7b9daf339a..48312014ef 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -244,7 +244,7 @@ public func enqueueMessages(account: Account, peerId: PeerId, messages: [Enqueue } } -public func enqueueMessagesToMultiplePeers(account: Account, peerIds: [PeerId], messages: [EnqueueMessage]) -> Signal<[MessageId], NoError> { +public func enqueueMessagesToMultiplePeers(account: Account, peerIds: [PeerId], threadIds: [PeerId: Int64], messages: [EnqueueMessage]) -> Signal<[MessageId], NoError> { let signal: Signal<[(Bool, EnqueueMessage)], NoError> if let transformOutgoingMessageMedia = account.transformOutgoingMessageMedia { signal = opportunisticallyTransformOutgoingMedia(network: account.network, postbox: account.postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messages: messages, userInteractive: true) @@ -256,6 +256,14 @@ public func enqueueMessagesToMultiplePeers(account: Account, peerIds: [PeerId], return account.postbox.transaction { transaction -> [MessageId] in var messageIds: [MessageId] = [] for peerId in peerIds { + var replyToMessageId: MessageId? + if let threadIds = threadIds[peerId] { + replyToMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadIds)) + } + var messages = messages + if let replyToMessageId { + messages = messages.map { ($0.0, $0.1.withUpdatedReplyToMessageId(replyToMessageId)) } + } for id in enqueueMessages(transaction: transaction, account: account, peerId: peerId, messages: messages, disableAutoremove: false, transformGroupingKeysWithPeerId: true) { if let id = id { messageIds.append(id) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift index 4067170ea3..b61557dec8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift @@ -3,7 +3,7 @@ import Postbox import TelegramApi import SwiftSignalKit -func _internal_forwardGameWithScore(account: Account, messageId: MessageId, to peerId: PeerId, as sendAsPeerId: PeerId?) -> Signal { +func _internal_forwardGameWithScore(account: Account, messageId: MessageId, to peerId: PeerId, threadId: Int64?, as sendAsPeerId: PeerId?) -> Signal { return account.postbox.transaction { transaction -> Signal in if let _ = transaction.getMessage(messageId), let fromPeer = transaction.getPeer(messageId.peerId), let fromInputPeer = apiInputPeer(fromPeer), let toPeer = transaction.getPeer(peerId), let toInputPeer = apiInputPeer(toPeer) { var flags: Int32 = 1 << 8 @@ -14,7 +14,7 @@ func _internal_forwardGameWithScore(account: Account, messageId: MessageId, to p flags |= (1 << 13) } - return account.network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: fromInputPeer, id: [messageId.id], randomId: [Int64.random(in: Int64.min ... Int64.max)], toPeer: toInputPeer, topMsgId: nil, scheduleDate: nil, sendAs: sendAsInputPeer)) + return account.network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: fromInputPeer, id: [messageId.id], randomId: [Int64.random(in: Int64.min ... Int64.max)], toPeer: toInputPeer, topMsgId: threadId.flatMap { Int32(clamping: $0) }, scheduleDate: nil, sendAs: sendAsInputPeer)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index f1c1afa03f..217b37fd35 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -98,8 +98,8 @@ public extension TelegramEngine { |> ignoreValues } - public func forwardGameWithScore(messageId: MessageId, to peerId: PeerId, as senderPeerId: PeerId?) -> Signal { - return _internal_forwardGameWithScore(account: self.account, messageId: messageId, to: peerId, as: senderPeerId) + public func forwardGameWithScore(messageId: MessageId, to peerId: PeerId, threadId: Int64?, as senderPeerId: PeerId?) -> Signal { + return _internal_forwardGameWithScore(account: self.account, messageId: messageId, to: peerId, threadId: threadId, as: senderPeerId) } public func requestUpdatePinnedMessage(peerId: PeerId, update: PinnedMessageUpdate) -> Signal { diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 8509275531..72f3b55784 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -904,7 +904,9 @@ private func extractAccountManagerState(records: AccountRecordsView map { ($0.0?.account, $0.1.map { ($0.0, $0.1.account) }) }, liveLocationPolling: liveLocationPolling, watchTasks: watchTasks, inForeground: applicationBindings.applicationInForeground, hasActiveAudioSession: self.hasActiveAudioSession.get(), notificationManager: notificationManager, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in + let wakeupManager = SharedWakeupManager(beginBackgroundTask: { name, expiration in application.beginBackgroundTask(withName: name, expirationHandler: expiration) }, endBackgroundTask: { id in application.endBackgroundTask(id) }, backgroundTimeRemaining: { application.backgroundTimeRemaining }, acquireIdleExtension: { + return applicationBindings.pushIdleTimerExtension() + }, activeAccounts: sharedContext.activeAccountContexts |> map { ($0.0?.account, $0.1.map { ($0.0, $0.1.account) }) }, liveLocationPolling: liveLocationPolling, watchTasks: watchTasks, inForeground: applicationBindings.applicationInForeground, hasActiveAudioSession: self.hasActiveAudioSession.get(), notificationManager: notificationManager, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in return sharedContext.accountUserInterfaceInUse(id) }) let sharedApplicationContext = SharedApplicationContext(sharedContext: sharedContext, notificationManager: notificationManager, wakeupManager: wakeupManager) @@ -1335,16 +1337,17 @@ private func extractAccountManagerState(records: AccountRecordsView= minReindexTimestamp { } else { + UserDefaults.standard.set(timestamp as NSNumber, forKey: "TelegramCacheIndexTimestamp") + Logger.shared.log("App \(self.episodeId)", "Executing low-impact cache reindex in foreground") let _ = self.runCacheReindexTasks(lowImpact: true, completion: { Logger.shared.log("App \(self.episodeId)", "Executing low-impact cache reindex in foreground — done") - UserDefaults.standard.set(timestamp as NSNumber, forKey: "TelegramCacheIndexTimestamp") }) - } + }*/ return true } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index ea011b83aa..ee74217187 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -539,6 +539,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return title } + private var currentSpeechHolder: SpeechSynthesizerHolder? + public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = []) { let _ = ChatControllerCount.modify { value in return value + 1 @@ -3534,7 +3536,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G window.rootViewController?.present(controller, animated: true) } case .speak: - let _ = speakText(context: strongSelf.context, text: text.string) + if let speechHolder = speakText(context: strongSelf.context, text: text.string) { + speechHolder.completion = { [weak self, weak speechHolder] in + if let strongSelf = self, strongSelf.currentSpeechHolder == speechHolder { + strongSelf.currentSpeechHolder = nil + } + } + strongSelf.currentSpeechHolder = speechHolder + } case .translate: strongSelf.chatDisplayNode.dismissInput() let f = { diff --git a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift index 2f34774565..d1fab167b3 100644 --- a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift @@ -439,17 +439,17 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode { } } + let additionalRightInset: CGFloat = 36.0 if !self.buttons.isEmpty { let maxInset = max(contentRightInset, leftInset) if self.buttons.count == 1 { - let buttonWidth = floor((width - maxInset * 2.0) / CGFloat(self.buttons.count)) + let buttonWidth = floor((width - maxInset * 2.0 - additionalRightInset) / CGFloat(self.buttons.count)) var nextButtonOrigin: CGFloat = maxInset for (_, view) in self.buttons { view.frame = CGRect(origin: CGPoint(x: nextButtonOrigin, y: 0.0), size: CGSize(width: buttonWidth, height: panelHeight)) nextButtonOrigin += buttonWidth } } else { - let additionalRightInset: CGFloat = 36.0 var areaWidth = width - maxInset * 2.0 - additionalRightInset let maxButtonWidth = floor(areaWidth / CGFloat(self.buttons.count)) let buttonSizes = self.buttons.map { button -> CGFloat in diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 82aaefbf80..17c279aa47 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -3059,6 +3059,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { return UIMenu(children: actions) } + private var currentSpeechHolder: SpeechSynthesizerHolder? @objc func _accessibilitySpeak(_ sender: Any) { var text = "" self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in @@ -3066,9 +3067,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { return (current, inputMode) } if let context = self.context { - let _ = speakText(context: context, text: text) + if let speechHolder = speakText(context: context, text: text) { + speechHolder.completion = { [weak self, weak speechHolder] in + if let strongSelf = self, strongSelf.currentSpeechHolder == speechHolder { + strongSelf.currentSpeechHolder = nil + } + } + self.currentSpeechHolder = speechHolder + } } - if #available(iOS 13.0, *) { UIMenuController.shared.hideMenu() } else { diff --git a/submodules/TelegramUI/Sources/LegacyCamera.swift b/submodules/TelegramUI/Sources/LegacyCamera.swift index 23c6bf543b..1efc0e4076 100644 --- a/submodules/TelegramUI/Sources/LegacyCamera.swift +++ b/submodules/TelegramUI/Sources/LegacyCamera.swift @@ -234,7 +234,7 @@ func presentedLegacyShortcutCamera(context: AccountContext, saveCapturedMedia: B nativeGenerator(_1, _2, _3, nil) }) if let parentController = parentController { - parentController.present(ShareController(context: context, subject: .fromExternal({ peerIds, text, account, silently in + parentController.present(ShareController(context: context, subject: .fromExternal({ peerIds, _, text, account, silently in return legacyAssetPickerEnqueueMessages(account: account, signals: signals!) |> `catch` { _ -> Signal<[LegacyAssetPickerEnqueueMessage], ShareControllerError> in return .single([]) diff --git a/submodules/TelegramUI/Sources/MediaManager.swift b/submodules/TelegramUI/Sources/MediaManager.swift index 725e33f801..d9068cfc74 100644 --- a/submodules/TelegramUI/Sources/MediaManager.swift +++ b/submodules/TelegramUI/Sources/MediaManager.swift @@ -2,6 +2,7 @@ import Foundation import SwiftSignalKit import AVFoundation import MobileCoreServices +import Display import Postbox import TelegramCore import MediaPlayer @@ -325,23 +326,11 @@ public final class MediaManagerImpl: NSObject, MediaManager { |> distinctUntilChanged(isEqual: { $0?.0 === $1?.0 && $0?.1 == $1?.1 }) |> mapToSignal { value -> Signal in if let (account, value) = value { - return albumArtThumbnailData(engine: TelegramEngine(account: account), thumbnail: value.fullSizeResource) - |> map { data -> UIImage? in - return data.flatMap(UIImage.init(data:)) + return playerAlbumArt(postbox: account.postbox, engine: TelegramEngine(account: account), fileReference: value.fullSizeResource.file, albumArt: value, thumbnail: false) + |> map { generator -> UIImage? in + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(width: 640, height: 640), boundingSize: CGSize(width: 640, height: 640), intrinsicInsets: .zero) + return generator(arguments)?.generateImage() } - /*return Signal { subscriber in - let fetched = account.postbox.mediaBox.fetchedResource(value.fullSizeResource, parameters: nil).start() - let data = account.postbox.mediaBox.resourceData(value.fullSizeResource, pathExtension: nil, option: .complete(waitUntilFetchStatus: false)).start(next: { data in - if data.complete, let value = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { - subscriber.putNext(UIImage(data: value)) - subscriber.putCompletion() - } - }) - return ActionDisposable { - fetched.dispose() - data.dispose() - } - }*/ } else { return .single(nil) } diff --git a/submodules/TelegramUI/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Sources/ShareExtensionContext.swift index 12fb7471d7..104c329fe4 100644 --- a/submodules/TelegramUI/Sources/ShareExtensionContext.swift +++ b/submodules/TelegramUI/Sources/ShareExtensionContext.swift @@ -33,7 +33,9 @@ private final class InternalContext { init(sharedContext: SharedAccountContextImpl) { self.sharedContext = sharedContext - self.wakeupManager = SharedWakeupManager(beginBackgroundTask: { _, _ in nil }, endBackgroundTask: { _ in }, backgroundTimeRemaining: { 0.0 }, activeAccounts: sharedContext.activeAccountContexts |> map { ($0.0?.account, $0.1.map { ($0.0, $0.1.account) }) }, liveLocationPolling: .single(nil), watchTasks: .single(nil), inForeground: inForeground.get(), hasActiveAudioSession: .single(false), notificationManager: nil, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in + self.wakeupManager = SharedWakeupManager(beginBackgroundTask: { _, _ in nil }, endBackgroundTask: { _ in }, backgroundTimeRemaining: { 0.0 }, acquireIdleExtension: { + return nil + }, activeAccounts: sharedContext.activeAccountContexts |> map { ($0.0?.account, $0.1.map { ($0.0, $0.1.account) }) }, liveLocationPolling: .single(nil), watchTasks: .single(nil), inForeground: inForeground.get(), hasActiveAudioSession: .single(false), notificationManager: nil, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in return sharedContext.accountUserInterfaceInUse(id) }) } @@ -353,8 +355,8 @@ public class ShareRootControllerImpl { } |> runOn(Queue.mainQueue()) } - let sentItems: ([PeerId], [PreparedShareItemContent], Account, Bool) -> Signal = { peerIds, contents, account, silently in - let sentItems = sentShareItems(account: account, to: peerIds, items: contents, silently: silently) + let sentItems: ([PeerId], [PeerId: Int64], [PreparedShareItemContent], Account, Bool) -> Signal = { peerIds, threadIds, contents, account, silently in + let sentItems = sentShareItems(account: account, to: peerIds, threadIds: threadIds, items: contents, silently: silently) |> `catch` { _ -> Signal< Float, NoError> in return .complete() @@ -366,7 +368,7 @@ public class ShareRootControllerImpl { |> then(.single(.done)) } - let shareController = ShareController(context: context, subject: .fromExternal({ peerIds, additionalText, account, silently in + let shareController = ShareController(context: context, subject: .fromExternal({ peerIds, threadIds, additionalText, account, silently in if let strongSelf = self, let inputItems = strongSelf.getExtensionContext()?.inputItems, !inputItems.isEmpty, !peerIds.isEmpty { let rawSignals = TGItemProviderSignals.itemSignals(forInputItems: inputItems)! return preparedShareItems(account: account, to: peerIds[0], dataItems: rawSignals, additionalText: additionalText) @@ -392,11 +394,11 @@ public class ShareRootControllerImpl { return requestUserInteraction(value) |> castError(ShareControllerError.self) |> mapToSignal { contents -> Signal in - return sentItems(peerIds, contents, account, silently) + return sentItems(peerIds, threadIds, contents, account, silently) |> castError(ShareControllerError.self) } case let .done(contents): - return sentItems(peerIds, contents, account, silently) + return sentItems(peerIds, threadIds, contents, account, silently) |> castError(ShareControllerError.self) } } diff --git a/submodules/TelegramUI/Sources/SharedWakeupManager.swift b/submodules/TelegramUI/Sources/SharedWakeupManager.swift index 0aef0e616f..a6a089bcd2 100644 --- a/submodules/TelegramUI/Sources/SharedWakeupManager.swift +++ b/submodules/TelegramUI/Sources/SharedWakeupManager.swift @@ -49,6 +49,7 @@ public final class SharedWakeupManager { private let beginBackgroundTask: (String, @escaping () -> Void) -> UIBackgroundTaskIdentifier? private let endBackgroundTask: (UIBackgroundTaskIdentifier) -> Void private let backgroundTimeRemaining: () -> Double + private let acquireIdleExtension: () -> Disposable? private var inForeground: Bool = false private var hasActiveAudioSession: Bool = false @@ -65,15 +66,17 @@ public final class SharedWakeupManager { private var currentExternalCompletionValidationTimer: SwiftSignalKit.Timer? private var managedPausedInBackgroundPlayer: Disposable? + private var keepIdleDisposable: Disposable? private var accountsAndTasks: [(Account, Bool, AccountTasks)] = [] - public init(beginBackgroundTask: @escaping (String, @escaping () -> Void) -> UIBackgroundTaskIdentifier?, endBackgroundTask: @escaping (UIBackgroundTaskIdentifier) -> Void, backgroundTimeRemaining: @escaping () -> Double, activeAccounts: Signal<(primary: Account?, accounts: [(AccountRecordId, Account)]), NoError>, liveLocationPolling: Signal, watchTasks: Signal, inForeground: Signal, hasActiveAudioSession: Signal, notificationManager: SharedNotificationManager?, mediaManager: MediaManager, callManager: PresentationCallManager?, accountUserInterfaceInUse: @escaping (AccountRecordId) -> Signal) { + public init(beginBackgroundTask: @escaping (String, @escaping () -> Void) -> UIBackgroundTaskIdentifier?, endBackgroundTask: @escaping (UIBackgroundTaskIdentifier) -> Void, backgroundTimeRemaining: @escaping () -> Double, acquireIdleExtension: @escaping () -> Disposable?, activeAccounts: Signal<(primary: Account?, accounts: [(AccountRecordId, Account)]), NoError>, liveLocationPolling: Signal, watchTasks: Signal, inForeground: Signal, hasActiveAudioSession: Signal, notificationManager: SharedNotificationManager?, mediaManager: MediaManager, callManager: PresentationCallManager?, accountUserInterfaceInUse: @escaping (AccountRecordId) -> Signal) { assert(Queue.mainQueue().isCurrent()) self.beginBackgroundTask = beginBackgroundTask self.endBackgroundTask = endBackgroundTask self.backgroundTimeRemaining = backgroundTimeRemaining + self.acquireIdleExtension = acquireIdleExtension self.inForegroundDisposable = (inForeground |> deliverOnMainQueue).start(next: { [weak self] value in @@ -186,6 +189,7 @@ public final class SharedWakeupManager { self.hasActiveAudioSessionDisposable?.dispose() self.tasksDisposable?.dispose() self.managedPausedInBackgroundPlayer?.dispose() + self.keepIdleDisposable?.dispose() if let (taskId, _, timer) = self.currentTask { timer.invalidate() self.endBackgroundTask(taskId) @@ -265,11 +269,15 @@ public final class SharedWakeupManager { var hasTasksForBackgroundExtension = false var hasActiveCalls = false + var hasPendingMessages = false for (_, _, tasks) in self.accountsAndTasks { if tasks.activeCalls { hasActiveCalls = true break } + if tasks.importantTasks.contains(.pendingMessages) { + hasPendingMessages = true + } } if self.inForeground || self.hasActiveAudioSession || hasActiveCalls { @@ -350,6 +358,17 @@ public final class SharedWakeupManager { } } self.updateAccounts(hasTasks: hasTasksForBackgroundExtension) + + if hasPendingMessages { + if self.keepIdleDisposable == nil { + self.keepIdleDisposable = self.acquireIdleExtension() + } + } else { + if let keepIdleDisposable = self.keepIdleDisposable { + self.keepIdleDisposable = nil + keepIdleDisposable.dispose() + } + } } private func updateAccounts(hasTasks: Bool) { diff --git a/versions.json b/versions.json index 40064af59e..0d05857c5a 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "9.3.1", + "app": "9.3.3", "bazel": "5.3.1", "xcode": "14.1" }