mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-04 20:00:53 +00:00
Merge branch 'beta'
# Conflicts: # submodules/AvatarNode/Sources/AvatarNode.swift # submodules/ChatListUI/Sources/ChatListFilterPresetController.swift # submodules/TelegramUI/Sources/EditableTokenListNode.swift
This commit is contained in:
commit
bbe1fc94ef
@ -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 {
|
||||
|
@ -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
|
||||
),
|
||||
]
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,6 +227,8 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
private let pagingEnabledPromise = ValuePromise<Bool>(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)
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
|
@ -60,103 +60,4 @@ public class ExternalMusicAlbumArtResource: Equatable {
|
||||
|
||||
public func fetchExternalMusicAlbumArtResource(engine: TelegramEngine, file: FileMediaReference?, resource: ExternalMusicAlbumArtResource) -> Signal<EngineMediaResource.Fetch.Result, EngineMediaResource.Fetch.Error> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ swift_library(
|
||||
"//submodules/ManagedFile:ManagedFile",
|
||||
"//submodules/Utils/RangeSet:RangeSet",
|
||||
"//submodules/CryptoUtils",
|
||||
"//submodules/Utils/DarwinDirStat",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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<darwin_dirstat>.size)
|
||||
if result != -1 {
|
||||
return Int64(s.total_size)
|
||||
} else {
|
||||
result = dirstat_np(path, 0, &s, MemoryLayout<darwin_dirstat>.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<Data>()
|
||||
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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ public enum ShareControllerSubject {
|
||||
case image([ImageRepresentationWithReference])
|
||||
case media(AnyMediaReference)
|
||||
case mapMedia(TelegramMediaMap)
|
||||
case fromExternal(([PeerId], String, Account, Bool) -> Signal<ShareControllerExternalStatus, ShareControllerError>)
|
||||
case fromExternal(([PeerId], [PeerId: Int64], String, Account, Bool) -> Signal<ShareControllerExternalStatus, ShareControllerError>)
|
||||
}
|
||||
|
||||
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):
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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<Float, Void> {
|
||||
public func sentShareItems(account: Account, to peerIds: [PeerId], threadIds: [PeerId: Int64], items: [PreparedShareItemContent], silently: Bool) -> Signal<Float, Void> {
|
||||
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<Float, Void> in
|
||||
return TelegramEngine(account: account).data.subscribe(EngineDataMap(
|
||||
|
@ -19,6 +19,10 @@ public class SpeechSynthesizerHolder: NSObject, AVSpeechSynthesizerDelegate {
|
||||
self.speechSynthesizer.delegate = self
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.stop()
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
self.speechSynthesizer.stopSpeaking(at: .immediate)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -3,7 +3,7 @@ import Postbox
|
||||
import TelegramApi
|
||||
import SwiftSignalKit
|
||||
|
||||
func _internal_forwardGameWithScore(account: Account, messageId: MessageId, to peerId: PeerId, as sendAsPeerId: PeerId?) -> Signal<Void, NoError> {
|
||||
func _internal_forwardGameWithScore(account: Account, messageId: MessageId, to peerId: PeerId, threadId: Int64?, as sendAsPeerId: PeerId?) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> 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<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
|
@ -98,8 +98,8 @@ public extension TelegramEngine {
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func forwardGameWithScore(messageId: MessageId, to peerId: PeerId, as senderPeerId: PeerId?) -> Signal<Void, NoError> {
|
||||
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<Void, NoError> {
|
||||
return _internal_forwardGameWithScore(account: self.account, messageId: messageId, to: peerId, threadId: threadId, as: senderPeerId)
|
||||
}
|
||||
|
||||
public func requestUpdatePinnedMessage(peerId: PeerId, update: PinnedMessageUpdate) -> Signal<Void, UpdatePinnedMessageError> {
|
||||
|
@ -904,7 +904,9 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
let wakeupManager = SharedWakeupManager(beginBackgroundTask: { name, expiration in application.beginBackgroundTask(withName: name, expirationHandler: expiration) }, endBackgroundTask: { id in application.endBackgroundTask(id) }, backgroundTimeRemaining: { application.backgroundTimeRemaining }, 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
|
||||
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<TelegramAcco
|
||||
})
|
||||
}
|
||||
|
||||
let timestamp = Int(CFAbsoluteTimeGetCurrent())
|
||||
/*let timestamp = Int(CFAbsoluteTimeGetCurrent())
|
||||
let minReindexTimestamp = timestamp - 2 * 24 * 60 * 60
|
||||
if let indexTimestamp = UserDefaults.standard.object(forKey: "TelegramCacheIndexTimestamp") as? NSNumber, indexTimestamp.intValue >= 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
|
||||
}
|
||||
|
@ -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<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(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 = {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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([])
|
||||
|
@ -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<UIImage?, NoError> 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)
|
||||
}
|
||||
|
@ -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<ShareControllerExternalStatus, NoError> = { 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<ShareControllerExternalStatus, NoError> = { 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<ShareControllerExternalStatus, ShareControllerError> 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)
|
||||
}
|
||||
}
|
||||
|
@ -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<AccountRecordId?, NoError>, watchTasks: Signal<AccountRecordId?, NoError>, inForeground: Signal<Bool, NoError>, hasActiveAudioSession: Signal<Bool, NoError>, notificationManager: SharedNotificationManager?, mediaManager: MediaManager, callManager: PresentationCallManager?, accountUserInterfaceInUse: @escaping (AccountRecordId) -> Signal<Bool, NoError>) {
|
||||
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<AccountRecordId?, NoError>, watchTasks: Signal<AccountRecordId?, NoError>, inForeground: Signal<Bool, NoError>, hasActiveAudioSession: Signal<Bool, NoError>, notificationManager: SharedNotificationManager?, mediaManager: MediaManager, callManager: PresentationCallManager?, accountUserInterfaceInUse: @escaping (AccountRecordId) -> Signal<Bool, NoError>) {
|
||||
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) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "9.3.1",
|
||||
"app": "9.3.3",
|
||||
"bazel": "5.3.1",
|
||||
"xcode": "14.1"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user