mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Storage usage improvements
This commit is contained in:
parent
a526ba7843
commit
da7b04a592
@ -1341,12 +1341,20 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
|
||||
let storageInfo: Signal<Double?, NoError>
|
||||
if "".isEmpty, case .chatList(groupId: .root) = location, chatListFilter == nil {
|
||||
let storageBox = context.account.postbox.mediaBox.storageBox
|
||||
storageInfo = storageBox.totalSize()
|
||||
if case .chatList(groupId: .root) = location, chatListFilter == nil {
|
||||
let totalSizeSignal = combineLatest(context.account.postbox.mediaBox.storageBox.totalSize(), context.account.postbox.mediaBox.cacheStorageBox.totalSize())
|
||||
|> map { a, b -> Int64 in
|
||||
return a + b
|
||||
}
|
||||
|
||||
storageInfo = totalSizeSignal
|
||||
|> take(1)
|
||||
|> mapToSignal { initialSize -> Signal<Double?, NoError> in
|
||||
#if DEBUG
|
||||
let fractionLimit: Double = 0.0001
|
||||
#else
|
||||
let fractionLimit: Double = 0.3
|
||||
#endif
|
||||
|
||||
let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
|
||||
let deviceFreeSpace = (systemAttributes?[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0
|
||||
@ -1375,7 +1383,7 @@ public final class ChatListNode: ListView {
|
||||
|
||||
let state = Atomic(value: ReportState(lastSize: initialSize))
|
||||
let updatedReportSize: Signal<Double?, NoError> = Signal { subscriber in
|
||||
let disposable = storageBox.totalSize().start(next: { size in
|
||||
let disposable = totalSizeSignal.start(next: { size in
|
||||
let updatedSize = state.with { state -> Int64 in
|
||||
if abs(initialSize - size) > 50 * 1024 * 1024 {
|
||||
state.lastSize = size
|
||||
|
@ -92,6 +92,8 @@ class ChatListStorageInfoItemNode: ListViewItemNode {
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.arrowNode)
|
||||
|
||||
self.zPosition = 1.0
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
|
@ -71,7 +71,16 @@ private func generateBlurredThumbnail(image: UIImage, adjustSaturation: Bool = f
|
||||
return thumbnailContext.generateImage()
|
||||
}
|
||||
|
||||
private func storeImage(context: DrawingContext, to path: String) -> UIImage? {
|
||||
private func storeImage(context: DrawingContext, mediaBox: MediaBox, resourceId: MediaResourceId, imageType: DirectMediaImageCache.ImageType) -> UIImage? {
|
||||
let representationId: String
|
||||
switch imageType {
|
||||
case .blurredThumbnail:
|
||||
representationId = "blurred32"
|
||||
case let .square(width):
|
||||
representationId = "shm\(width)"
|
||||
}
|
||||
let path = mediaBox.cachedRepresentationPathForId(resourceId.stringRepresentation, representationId: representationId, keepDuration: .general)
|
||||
|
||||
if context.size.width <= 70.0 && context.size.height <= 70.0 {
|
||||
guard let file = ManagedFile(queue: nil, path: path, mode: .readwrite) else {
|
||||
return nil
|
||||
@ -103,6 +112,9 @@ private func storeImage(context: DrawingContext, to path: String) -> UIImage? {
|
||||
vImageConvert_BGRA8888toRGB565(&source, &target, vImage_Flags(kvImageDoNotTile))
|
||||
|
||||
let _ = file.write(targetData, count: targetLength)
|
||||
if let pathData = path.data(using: .utf8), let size = file.getSize() {
|
||||
mediaBox.cacheStorageBox.update(id: pathData, size: size)
|
||||
}
|
||||
|
||||
return context.generateImage()
|
||||
} else {
|
||||
@ -110,6 +122,9 @@ private func storeImage(context: DrawingContext, to path: String) -> UIImage? {
|
||||
return nil
|
||||
}
|
||||
let _ = try? resultData.write(to: URL(fileURLWithPath: path))
|
||||
if let pathData = path.data(using: .utf8) {
|
||||
mediaBox.cacheStorageBox.update(id: pathData, size: Int64(resultData.count))
|
||||
}
|
||||
return image
|
||||
}
|
||||
}
|
||||
@ -212,7 +227,7 @@ public final class DirectMediaImageCache {
|
||||
}
|
||||
}
|
||||
|
||||
private enum ImageType {
|
||||
fileprivate enum ImageType {
|
||||
case blurredThumbnail
|
||||
case square(width: Int)
|
||||
}
|
||||
@ -236,8 +251,6 @@ public final class DirectMediaImageCache {
|
||||
|
||||
private func getLoadSignal(width: Int, userLocation: MediaResourceUserLocation, userContentType: MediaResourceUserContentType, resource: MediaResourceReference, resourceSizeLimit: Int64) -> Signal<UIImage?, NoError>? {
|
||||
return Signal { subscriber in
|
||||
let cachePath = self.getCachePath(resourceId: resource.resource.id, imageType: .square(width: width))
|
||||
|
||||
let fetch = fetchedMediaResource(
|
||||
mediaBox: self.account.postbox.mediaBox,
|
||||
userLocation: userLocation,
|
||||
@ -281,7 +294,7 @@ public final class DirectMediaImageCache {
|
||||
context.draw(image.cgImage!, in: imageRect)
|
||||
}
|
||||
|
||||
if let scaledImage = storeImage(context: scaledContext, to: cachePath) {
|
||||
if let scaledImage = storeImage(context: scaledContext, mediaBox: self.account.postbox.mediaBox, resourceId: resource.resource.id, imageType: .square(width: width)) {
|
||||
subscriber.putNext(scaledImage)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
|
@ -145,6 +145,7 @@ public final class MediaBox {
|
||||
private let timeBasedCleanup: TimeBasedCleanup
|
||||
|
||||
public let storageBox: StorageBox
|
||||
public let cacheStorageBox: StorageBox
|
||||
|
||||
private let didRemoveResourcesPipe = ValuePipe<Void>()
|
||||
public var didRemoveResources: Signal<Void, NoError> {
|
||||
@ -192,6 +193,9 @@ public final class MediaBox {
|
||||
self.storageBox = StorageBox(logger: StorageBox.Logger(impl: { string in
|
||||
postboxLog(string)
|
||||
}), basePath: basePath + "/storage")
|
||||
self.cacheStorageBox = StorageBox(logger: StorageBox.Logger(impl: { string in
|
||||
postboxLog(string)
|
||||
}), basePath: basePath + "/cache-storage")
|
||||
|
||||
self.timeBasedCleanup = TimeBasedCleanup(storageBox: self.storageBox, generalPaths: [
|
||||
self.basePath + "/cache",
|
||||
@ -878,6 +882,9 @@ public final class MediaBox {
|
||||
public func storeCachedResourceRepresentation(_ resource: MediaResource, representation: CachedMediaResourceRepresentation, data: Data) {
|
||||
self.dataQueue.async {
|
||||
let path = self.cachedRepresentationPathsForId(resource.id.stringRepresentation, representationId: representation.uniqueId, keepDuration: representation.keepDuration).complete
|
||||
if let pathData = path.data(using: .utf8) {
|
||||
self.cacheStorageBox.update(id: pathData, size: Int64(data.count))
|
||||
}
|
||||
let _ = try? data.write(to: URL(fileURLWithPath: path))
|
||||
}
|
||||
}
|
||||
@ -885,6 +892,9 @@ public final class MediaBox {
|
||||
public func storeCachedResourceRepresentation(_ resource: MediaResource, representationId: String, keepDuration: CachedMediaRepresentationKeepDuration, data: Data, completion: @escaping (String) -> Void = { _ in }) {
|
||||
self.dataQueue.async {
|
||||
let path = self.cachedRepresentationPathsForId(resource.id.stringRepresentation, representationId: representationId, keepDuration: keepDuration).complete
|
||||
if let pathData = path.data(using: .utf8) {
|
||||
self.cacheStorageBox.update(id: pathData, size: Int64(data.count))
|
||||
}
|
||||
let _ = try? data.write(to: URL(fileURLWithPath: path))
|
||||
completion(path)
|
||||
}
|
||||
@ -894,6 +904,9 @@ public final class MediaBox {
|
||||
self.dataQueue.async {
|
||||
let path = self.cachedRepresentationPathsForId(resourceId, representationId: representationId, keepDuration: keepDuration).complete
|
||||
let _ = try? data.write(to: URL(fileURLWithPath: path))
|
||||
if let pathData = path.data(using: .utf8) {
|
||||
self.cacheStorageBox.update(id: pathData, size: Int64(data.count))
|
||||
}
|
||||
completion(path)
|
||||
}
|
||||
}
|
||||
@ -902,6 +915,9 @@ public final class MediaBox {
|
||||
self.dataQueue.async {
|
||||
let path = self.cachedRepresentationPathsForId(resourceId, representationId: representationId, keepDuration: keepDuration).complete
|
||||
let _ = try? FileManager.default.moveItem(atPath: tempFile.path, toPath: path)
|
||||
if let fileSize = fileSize(path), fileSize != 0, let pathData = path.data(using: .utf8) {
|
||||
self.cacheStorageBox.update(id: pathData, size: fileSize)
|
||||
}
|
||||
completion(path)
|
||||
}
|
||||
}
|
||||
@ -983,6 +999,7 @@ public final class MediaBox {
|
||||
|
||||
if !context.initialized {
|
||||
context.initialized = true
|
||||
let cacheStorageBox = self.cacheStorageBox
|
||||
let signal = self.wrappedFetchCachedResourceRepresentation.get()
|
||||
|> take(1)
|
||||
|> mapToSignal { fetch in
|
||||
@ -999,15 +1016,24 @@ public final class MediaBox {
|
||||
switch next {
|
||||
case let .temporaryPath(temporaryPath):
|
||||
rename(temporaryPath, paths.complete)
|
||||
if let size = fileSize(paths.complete), let pathData = paths.complete.data(using: .utf8) {
|
||||
cacheStorageBox.update(id: pathData, size: size)
|
||||
}
|
||||
isDone = true
|
||||
case let .tempFile(tempFile):
|
||||
rename(tempFile.path, paths.complete)
|
||||
TempBox.shared.dispose(tempFile)
|
||||
if let size = fileSize(paths.complete), let pathData = paths.complete.data(using: .utf8) {
|
||||
cacheStorageBox.update(id: pathData, size: size)
|
||||
}
|
||||
isDone = true
|
||||
case .reset:
|
||||
let file = ManagedFile(queue: strongSelf.dataQueue, path: paths.partial, mode: .readwrite)
|
||||
file?.truncate(count: 0)
|
||||
unlink(paths.complete)
|
||||
if let pathData = paths.complete.data(using: .utf8) {
|
||||
cacheStorageBox.update(id: pathData, size: 0)
|
||||
}
|
||||
case let .data(dataPart):
|
||||
let file = ManagedFile(queue: strongSelf.dataQueue, path: paths.partial, mode: .append)
|
||||
let dataCount = dataPart.count
|
||||
@ -1015,8 +1041,14 @@ public final class MediaBox {
|
||||
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
let _ = file?.write(bytes, count: dataCount)
|
||||
}
|
||||
if let file = file, let size = file.getSize(), let pathData = paths.complete.data(using: .utf8) {
|
||||
cacheStorageBox.update(id: pathData, size: size)
|
||||
}
|
||||
case .done:
|
||||
link(paths.partial, paths.complete)
|
||||
if let size = fileSize(paths.complete), let pathData = paths.complete.data(using: .utf8) {
|
||||
cacheStorageBox.update(id: pathData, size: size)
|
||||
}
|
||||
isDone = true
|
||||
}
|
||||
|
||||
@ -1155,6 +1187,7 @@ public final class MediaBox {
|
||||
|
||||
if !context.initialized {
|
||||
context.initialized = true
|
||||
let cacheStorageBox = self.cacheStorageBox
|
||||
let signal = fetch()
|
||||
|> deliverOn(self.dataQueue)
|
||||
context.disposable.set(signal.start(next: { [weak self, weak context] next in
|
||||
@ -1165,15 +1198,24 @@ public final class MediaBox {
|
||||
switch next {
|
||||
case let .temporaryPath(temporaryPath):
|
||||
rename(temporaryPath, paths.complete)
|
||||
if let size = fileSize(paths.complete), let pathData = paths.complete.data(using: .utf8) {
|
||||
cacheStorageBox.update(id: pathData, size: size)
|
||||
}
|
||||
isDone = true
|
||||
case let .tempFile(tempFile):
|
||||
rename(tempFile.path, paths.complete)
|
||||
TempBox.shared.dispose(tempFile)
|
||||
if let size = fileSize(paths.complete), let pathData = paths.complete.data(using: .utf8) {
|
||||
cacheStorageBox.update(id: pathData, size: size)
|
||||
}
|
||||
isDone = true
|
||||
case .reset:
|
||||
let file = ManagedFile(queue: strongSelf.dataQueue, path: paths.partial, mode: .readwrite)
|
||||
file?.truncate(count: 0)
|
||||
unlink(paths.complete)
|
||||
if let pathData = paths.complete.data(using: .utf8) {
|
||||
cacheStorageBox.update(id: pathData, size: 0)
|
||||
}
|
||||
case let .data(dataPart):
|
||||
let file = ManagedFile(queue: strongSelf.dataQueue, path: paths.partial, mode: .append)
|
||||
let dataCount = dataPart.count
|
||||
@ -1181,9 +1223,15 @@ public final class MediaBox {
|
||||
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
let _ = file?.write(bytes, count: dataCount)
|
||||
}
|
||||
if let file = file, let size = file.getSize(), let pathData = paths.complete.data(using: .utf8) {
|
||||
cacheStorageBox.update(id: pathData, size: size)
|
||||
}
|
||||
case .done:
|
||||
link(paths.partial, paths.complete)
|
||||
isDone = true
|
||||
if let size = fileSize(paths.complete), let pathData = paths.complete.data(using: .utf8) {
|
||||
cacheStorageBox.update(id: pathData, size: size)
|
||||
}
|
||||
}
|
||||
|
||||
if let strongSelf = self, let currentContext = strongSelf.cachedRepresentationContexts[key], currentContext === context {
|
||||
@ -1277,7 +1325,7 @@ public final class MediaBox {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateResourceIndex(lowImpact: Bool, completion: @escaping () -> Void) -> Disposable {
|
||||
private func updateGeneralResourceIndex(lowImpact: Bool, completion: @escaping () -> Void) -> Disposable {
|
||||
let basePath = self.basePath
|
||||
let storageBox = self.storageBox
|
||||
|
||||
@ -1370,6 +1418,104 @@ public final class MediaBox {
|
||||
}
|
||||
}
|
||||
|
||||
/*private func updateCacheResourceIndex(pathPrefix: String, lowImpact: Bool, completion: @escaping () -> Void) -> Disposable {
|
||||
let cacheStorageBox = self.cacheStorageBox
|
||||
|
||||
var isCancelled: Bool = false
|
||||
|
||||
let processQueue = Queue(name: "UpdateResourceIndex", qos: .background)
|
||||
processQueue.async {
|
||||
if isCancelled {
|
||||
return
|
||||
}
|
||||
|
||||
let scanContext = ScanFilesContext(path: pathPrefix)
|
||||
|
||||
func processStale(nextId: Data?) {
|
||||
let _ = (storageBox.enumerateItems(startingWith: nextId, limit: 1000)
|
||||
|> deliverOn(processQueue)).start(next: { ids, realNextId in
|
||||
var staleIds: [Data] = []
|
||||
|
||||
for id in ids {
|
||||
if let name = String(data: id, encoding: .utf8) {
|
||||
if self.resourceUsage(id: MediaResourceId(name)) == 0 {
|
||||
staleIds.append(id)
|
||||
}
|
||||
} else {
|
||||
staleIds.append(id)
|
||||
}
|
||||
}
|
||||
|
||||
if !staleIds.isEmpty {
|
||||
storageBox.remove(ids: staleIds)
|
||||
}
|
||||
|
||||
if realNextId == nil {
|
||||
completion()
|
||||
} else {
|
||||
if lowImpact {
|
||||
processQueue.after(0.4, {
|
||||
processStale(nextId: realNextId)
|
||||
})
|
||||
} else {
|
||||
processStale(nextId: realNextId)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func processNext() {
|
||||
processQueue.async {
|
||||
if isCancelled {
|
||||
return
|
||||
}
|
||||
|
||||
let results = scanContext.nextBatch(count: 32000)
|
||||
if results.isEmpty {
|
||||
processStale(nextId: nil)
|
||||
return
|
||||
}
|
||||
|
||||
storageBox.addEmptyReferencesIfNotReferenced(ids: results.map { name -> (id: Data, size: Int64) in
|
||||
let resourceId = MediaBox.idForFileName(name: name)
|
||||
let paths = self.storePathsForId(MediaResourceId(resourceId))
|
||||
var size: Int64 = 0
|
||||
if let value = fileSize(paths.complete) {
|
||||
size = value
|
||||
} else if let value = fileSize(paths.partial) {
|
||||
size = value
|
||||
}
|
||||
return (resourceId.data(using: .utf8)!, size)
|
||||
}, contentType: MediaResourceUserContentType.other.rawValue, completion: { addedCount in
|
||||
if addedCount != 0 {
|
||||
postboxLog("UpdateResourceIndex: added \(addedCount) unreferenced ids")
|
||||
}
|
||||
|
||||
if lowImpact {
|
||||
processQueue.after(0.4, {
|
||||
processNext()
|
||||
})
|
||||
} else {
|
||||
processNext()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
processNext()
|
||||
}
|
||||
|
||||
return ActionDisposable {
|
||||
isCancelled = true
|
||||
}
|
||||
}*/
|
||||
|
||||
public func updateResourceIndex(lowImpact: Bool, completion: @escaping () -> Void) -> Disposable {
|
||||
return self.updateGeneralResourceIndex(lowImpact: lowImpact, completion: {
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
public func collectAllResourceUsage() -> Signal<[(id: String?, path: String, size: Int64)], NoError> {
|
||||
return Signal { subscriber in
|
||||
self.dataQueue.async {
|
||||
@ -1396,66 +1542,6 @@ public final class MediaBox {
|
||||
}
|
||||
}
|
||||
|
||||
/*var cacheResult: Int64 = 0
|
||||
|
||||
var excludePrefixes = Set<String>()
|
||||
for id in excludeIds {
|
||||
let cachedRepresentationPrefix = self.fileNameForId(id)
|
||||
|
||||
excludePrefixes.insert(cachedRepresentationPrefix)
|
||||
}
|
||||
|
||||
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: self.basePath + "/cache"), includingPropertiesForKeys: [.fileSizeKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
|
||||
loop: for url in enumerator {
|
||||
if let url = url as? URL {
|
||||
if let prefix = url.lastPathComponent.components(separatedBy: ":").first, excludePrefixes.contains(prefix) {
|
||||
continue loop
|
||||
}
|
||||
|
||||
if let value = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize, value != 0 {
|
||||
paths.append("cache/" + url.lastPathComponent)
|
||||
cacheResult += Int64(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processRecursive(directoryPath: String, subdirectoryPath: String) {
|
||||
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: directoryPath), includingPropertiesForKeys: [.fileSizeKey, .isDirectoryKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
|
||||
loop: for url in enumerator {
|
||||
if let url = url as? URL {
|
||||
if let prefix = url.lastPathComponent.components(separatedBy: ":").first, excludePrefixes.contains(prefix) {
|
||||
continue loop
|
||||
}
|
||||
|
||||
if let isDirectory = (try? url.resourceValues(forKeys: Set([.isDirectoryKey])))?.isDirectory, isDirectory {
|
||||
processRecursive(directoryPath: url.path, subdirectoryPath: subdirectoryPath + "/\(url.lastPathComponent)")
|
||||
} else if let value = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize, value != 0 {
|
||||
paths.append("\(subdirectoryPath)/" + url.lastPathComponent)
|
||||
cacheResult += Int64(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processRecursive(directoryPath: self.basePath + "/animation-cache", subdirectoryPath: "animation-cache")
|
||||
|
||||
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: self.basePath + "/short-cache"), includingPropertiesForKeys: [.fileSizeKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
|
||||
loop: for url in enumerator {
|
||||
if let url = url as? URL {
|
||||
if let prefix = url.lastPathComponent.components(separatedBy: ":").first, excludePrefixes.contains(prefix) {
|
||||
continue loop
|
||||
}
|
||||
|
||||
if let value = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize, value != 0 {
|
||||
paths.append("short-cache/" + url.lastPathComponent)
|
||||
cacheResult += Int64(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
subscriber.putNext(result)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
|
@ -472,6 +472,8 @@ public final class StorageBox {
|
||||
for peerId in self.peerIdsReferencing(hashId: hashId) {
|
||||
self.internalAddSize(peerId: peerId, contentType: info.contentType, delta: sizeDelta)
|
||||
}
|
||||
} else {
|
||||
self.internalAdd(reference: StorageBox.Reference(peerId: 0, messageNamespace: 0, messageId: 0), to: id, contentType: 0, size: size)
|
||||
}
|
||||
|
||||
self.valueBox.commit()
|
||||
|
@ -449,10 +449,27 @@ private func cleanupAccount(networkArguments: NetworkInitializationArguments, ac
|
||||
break
|
||||
}
|
||||
|
||||
var cloudValue: [Data] = []
|
||||
if let list = NSUbiquitousKeyValueStore.default.object(forKey: "T_SLTokens") as? [String] {
|
||||
cloudValue = list.compactMap { string -> Data? in
|
||||
guard let stringData = string.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
return Data(base64Encoded: stringData)
|
||||
}
|
||||
}
|
||||
for data in cloudValue {
|
||||
if !tokens.contains(data) {
|
||||
tokens.insert(data, at: 0)
|
||||
}
|
||||
}
|
||||
if tokens.count > 20 {
|
||||
tokens.removeLast(tokens.count - 20)
|
||||
}
|
||||
|
||||
NSUbiquitousKeyValueStore.default.set(tokens.map { $0.base64EncodedString() }, forKey: "T_SLTokens")
|
||||
NSUbiquitousKeyValueStore.default.synchronize()
|
||||
|
||||
transaction.setStoredLoginTokens(tokens)
|
||||
}).start()
|
||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||
|
@ -72,11 +72,27 @@ private func ~=<T: RegularExpressionMatchable>(pattern: Regex, matchable: T) ->
|
||||
}
|
||||
|
||||
public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, phoneNumber: String, apiId: Int32, apiHash: String, syncContacts: Bool) -> Signal<UnauthorizedAccount, AuthorizationCodeRequestError> {
|
||||
var cloudValue: [Data] = []
|
||||
if let list = NSUbiquitousKeyValueStore.default.object(forKey: "T_SLTokens") as? [String] {
|
||||
cloudValue = list.compactMap { string -> Data? in
|
||||
guard let stringData = string.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
return Data(base64Encoded: stringData)
|
||||
}
|
||||
}
|
||||
return accountManager.transaction { transaction -> [Data] in
|
||||
return transaction.getStoredLoginTokens()
|
||||
}
|
||||
|> castError(AuthorizationCodeRequestError.self)
|
||||
|> mapToSignal { authTokens -> Signal<UnauthorizedAccount, AuthorizationCodeRequestError> in
|
||||
|> mapToSignal { localAuthTokens -> Signal<UnauthorizedAccount, AuthorizationCodeRequestError> in
|
||||
var authTokens = localAuthTokens
|
||||
for data in cloudValue {
|
||||
if !authTokens.contains(data) {
|
||||
authTokens.insert(data, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
var flags: Int32 = 0
|
||||
flags |= 1 << 5 //allowMissedCall
|
||||
flags |= 1 << 6 //tokens
|
||||
|
@ -123,10 +123,19 @@ private extension StorageUsageStats {
|
||||
mappedCategory = .avatars
|
||||
case MediaResourceUserContentType.sticker.rawValue:
|
||||
mappedCategory = .stickers
|
||||
case MediaResourceUserContentType.other.rawValue:
|
||||
mappedCategory = .misc
|
||||
case MediaResourceUserContentType.audioVideoMessage.rawValue:
|
||||
mappedCategory = .misc
|
||||
default:
|
||||
mappedCategory = .misc
|
||||
}
|
||||
mappedCategories[mappedCategory] = StorageUsageStats.CategoryData(size: value.size, messages: value.messages)
|
||||
if mappedCategories[mappedCategory] == nil {
|
||||
mappedCategories[mappedCategory] = StorageUsageStats.CategoryData(size: value.size, messages: value.messages)
|
||||
} else {
|
||||
mappedCategories[mappedCategory]?.size += value.size
|
||||
mappedCategories[mappedCategory]?.messages.merge(value.messages, uniquingKeysWith: { lhs, _ in lhs})
|
||||
}
|
||||
}
|
||||
|
||||
self.init(categories: mappedCategories)
|
||||
@ -134,7 +143,7 @@ private extension StorageUsageStats {
|
||||
}
|
||||
|
||||
func _internal_collectStorageUsageStats(account: Account) -> Signal<AllStorageUsageStats, NoError> {
|
||||
let additionalStats = Signal<Int64, NoError> { subscriber in
|
||||
/*let additionalStats = Signal<Int64, NoError> { subscriber in
|
||||
DispatchQueue.global().async {
|
||||
var totalSize: Int64 = 0
|
||||
|
||||
@ -207,7 +216,9 @@ func _internal_collectStorageUsageStats(account: Account) -> Signal<AllStorageUs
|
||||
}
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
}*/
|
||||
|
||||
let additionalStats = account.postbox.mediaBox.cacheStorageBox.totalSize() |> take(1)
|
||||
|
||||
return combineLatest(
|
||||
additionalStats,
|
||||
@ -264,6 +275,7 @@ func _internal_collectStorageUsageStats(account: Account) -> Signal<AllStorageUs
|
||||
func _internal_renderStorageUsageStatsMessages(account: Account, stats: StorageUsageStats, categories: [StorageUsageStats.CategoryKey], existingMessages: [EngineMessage.Id: Message]) -> Signal<[EngineMessage.Id: Message], NoError> {
|
||||
return account.postbox.transaction { transaction -> [EngineMessage.Id: Message] in
|
||||
var result: [EngineMessage.Id: Message] = [:]
|
||||
var peerInChatList: [EnginePeer.Id: Bool] = [:]
|
||||
for (category, value) in stats.categories {
|
||||
if !categories.contains(category) {
|
||||
continue
|
||||
@ -273,8 +285,23 @@ func _internal_renderStorageUsageStatsMessages(account: Account, stats: StorageU
|
||||
if result[id] == nil {
|
||||
if let message = existingMessages[id] {
|
||||
result[id] = message
|
||||
} else if let message = transaction.getMessage(id) {
|
||||
result[id] = message
|
||||
} else {
|
||||
var matches = false
|
||||
if let peerInChatListValue = peerInChatList[id.peerId] {
|
||||
if peerInChatListValue {
|
||||
matches = true
|
||||
}
|
||||
} else {
|
||||
let peerInChatListValue = transaction.getPeerChatListIndex(id.peerId) != nil
|
||||
peerInChatList[id.peerId] = peerInChatListValue
|
||||
if peerInChatListValue {
|
||||
matches = true
|
||||
}
|
||||
}
|
||||
|
||||
if matches, let message = transaction.getMessage(id) {
|
||||
result[id] = message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -327,6 +354,9 @@ func _internal_clearStorage(account: Account, peerId: EnginePeer.Id?, categories
|
||||
case .misc:
|
||||
mappedContentTypes.append(MediaResourceUserContentType.other.rawValue)
|
||||
mappedContentTypes.append(MediaResourceUserContentType.audioVideoMessage.rawValue)
|
||||
|
||||
// Legacy value for Gif
|
||||
mappedContentTypes.append(5)
|
||||
}
|
||||
}
|
||||
|
||||
@ -357,6 +387,8 @@ func _internal_clearStorage(account: Account, peerId: EnginePeer.Id?, categories
|
||||
}
|
||||
}
|
||||
|
||||
mediaBox.cacheStorageBox.reset()
|
||||
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
subscriber.putCompletion()
|
||||
|
@ -165,6 +165,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
|
||||
case forcedPasswordSetup = 31
|
||||
case emojiTooltip = 32
|
||||
case audioTranscriptionSuggestion = 33
|
||||
case clearStorageDismissedTipSize = 34
|
||||
|
||||
var key: ValueBoxKey {
|
||||
let v = ValueBoxKey(length: 4)
|
||||
@ -349,6 +350,10 @@ private struct ApplicationSpecificNoticeKeys {
|
||||
static func audioTranscriptionSuggestion() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.audioTranscriptionSuggestion.key)
|
||||
}
|
||||
|
||||
static func clearStorageDismissedTipSize() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.clearStorageDismissedTipSize.key)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificNotice {
|
||||
@ -1087,6 +1092,25 @@ public struct ApplicationSpecificNotice {
|
||||
}
|
||||
}
|
||||
|
||||
public static func getClearStorageDismissedTipSize(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
|
||||
return accountManager.transaction { transaction -> Int32 in
|
||||
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.clearStorageDismissedTipSize())?.get(ApplicationSpecificCounterNotice.self) {
|
||||
return value.value
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func setClearStorageDismissedTipSize(accountManager: AccountManager<TelegramAccountManagerTypes>, value: Int32) -> Signal<Never, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: value)) {
|
||||
transaction.setNotice(ApplicationSpecificNoticeKeys.clearStorageDismissedTipSize(), entry)
|
||||
}
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public static func getInteractiveEmojiSyncTip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<(Int32, Int32), NoError> {
|
||||
return accountManager.transaction { transaction -> (Int32, Int32) in
|
||||
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.interactiveEmojiSyncTip())?.get(ApplicationSpecificTimestampAndCounterNotice.self) {
|
||||
|
@ -14,6 +14,26 @@ private func alignUp(size: Int, align: Int) -> Int {
|
||||
return (size + alignmentMask) & ~alignmentMask
|
||||
}
|
||||
|
||||
private func fileSize(_ path: String, useTotalFileAllocatedSize: Bool = false) -> Int64? {
|
||||
if useTotalFileAllocatedSize {
|
||||
let url = URL(fileURLWithPath: path)
|
||||
if let values = (try? url.resourceValues(forKeys: Set([.isRegularFileKey, .totalFileAllocatedSizeKey]))) {
|
||||
if values.isRegularFile ?? false {
|
||||
if let fileSize = values.totalFileAllocatedSize {
|
||||
return Int64(fileSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var value = stat()
|
||||
if stat(path, &value) == 0 {
|
||||
return value.st_size
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public final class AnimationCacheItemFrame {
|
||||
public enum RequestedFormat {
|
||||
case rgba
|
||||
@ -1246,7 +1266,7 @@ private func loadItem(path: String) throws -> AnimationCacheItem {
|
||||
})
|
||||
}
|
||||
|
||||
private func adaptItemFromHigherResolution(currentQueue: Queue, itemPath: String, width: Int, height: Int, itemDirectoryPath: String, higherResolutionPath: String, allocateTempFile: @escaping () -> String) -> AnimationCacheItem? {
|
||||
private func adaptItemFromHigherResolution(currentQueue: Queue, itemPath: String, width: Int, height: Int, itemDirectoryPath: String, higherResolutionPath: String, allocateTempFile: @escaping () -> String, updateStorageStats: @escaping (String, Int64) -> Void) -> AnimationCacheItem? {
|
||||
guard let higherResolutionItem = try? loadItem(path: higherResolutionPath) else {
|
||||
return nil
|
||||
}
|
||||
@ -1286,6 +1306,10 @@ private func adaptItemFromHigherResolution(currentQueue: Queue, itemPath: String
|
||||
guard let _ = try? FileManager.default.moveItem(atPath: result.animationPath, toPath: itemPath) else {
|
||||
return nil
|
||||
}
|
||||
if let size = fileSize(itemPath) {
|
||||
updateStorageStats(itemPath, size)
|
||||
}
|
||||
|
||||
guard let item = try? loadItem(path: itemPath) else {
|
||||
return nil
|
||||
}
|
||||
@ -1295,7 +1319,7 @@ private func adaptItemFromHigherResolution(currentQueue: Queue, itemPath: String
|
||||
}
|
||||
}
|
||||
|
||||
private func generateFirstFrameFromItem(currentQueue: Queue, itemPath: String, animationItemPath: String, allocateTempFile: @escaping () -> String) -> Bool {
|
||||
private func generateFirstFrameFromItem(currentQueue: Queue, itemPath: String, animationItemPath: String, allocateTempFile: @escaping () -> String, updateStorageStats: @escaping (String, Int64) -> Void) -> Bool {
|
||||
guard let animationItem = try? loadItem(path: animationItemPath) else {
|
||||
return false
|
||||
}
|
||||
@ -1337,6 +1361,9 @@ private func generateFirstFrameFromItem(currentQueue: Queue, itemPath: String, a
|
||||
guard let _ = try? FileManager.default.moveItem(atPath: result.animationPath, toPath: itemPath) else {
|
||||
return false
|
||||
}
|
||||
if let size = fileSize(itemPath) {
|
||||
updateStorageStats(itemPath, size)
|
||||
}
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
@ -1408,13 +1435,14 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
private let queue: Queue
|
||||
private let basePath: String
|
||||
private let allocateTempFile: () -> String
|
||||
private let updateStorageStats: (String, Int64) -> Void
|
||||
|
||||
private let fetchQueues: [Queue]
|
||||
private var nextFetchQueueIndex: Int = 0
|
||||
|
||||
private var itemContexts: [ItemKey: ItemContext] = [:]
|
||||
|
||||
init(queue: Queue, basePath: String, allocateTempFile: @escaping () -> String) {
|
||||
init(queue: Queue, basePath: String, allocateTempFile: @escaping () -> String, updateStorageStats: @escaping (String, Int64) -> Void) {
|
||||
self.queue = queue
|
||||
|
||||
let fetchQueueCount: Int
|
||||
@ -1427,6 +1455,7 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
self.fetchQueues = (0 ..< fetchQueueCount).map { i in Queue(name: "AnimationCacheImpl-Fetch\(i)", qos: .default) }
|
||||
self.basePath = basePath
|
||||
self.allocateTempFile = allocateTempFile
|
||||
self.updateStorageStats = updateStorageStats
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -1464,6 +1493,7 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
let fetchQueueIndex = self.nextFetchQueueIndex
|
||||
self.nextFetchQueueIndex += 1
|
||||
let allocateTempFile = self.allocateTempFile
|
||||
let updateStorageStats = self.updateStorageStats
|
||||
guard let writer = AnimationCacheItemWriterImpl(queue: self.fetchQueues[fetchQueueIndex % self.fetchQueues.count], allocateTempFile: self.allocateTempFile, completion: { [weak self, weak itemContext] result in
|
||||
queue.async {
|
||||
guard let strongSelf = self, let itemContext = itemContext, itemContext === strongSelf.itemContexts[key] else {
|
||||
@ -1482,8 +1512,11 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
guard let _ = try? FileManager.default.moveItem(atPath: result.animationPath, toPath: itemPath) else {
|
||||
return
|
||||
}
|
||||
if let size = fileSize(itemPath) {
|
||||
updateStorageStats(itemPath, size)
|
||||
}
|
||||
|
||||
let _ = generateFirstFrameFromItem(currentQueue: queue, itemPath: itemFirstFramePath, animationItemPath: itemPath, allocateTempFile: allocateTempFile)
|
||||
let _ = generateFirstFrameFromItem(currentQueue: queue, itemPath: itemFirstFramePath, animationItemPath: itemPath, allocateTempFile: allocateTempFile, updateStorageStats: updateStorageStats)
|
||||
|
||||
for f in itemContext.subscribers.copyItems() {
|
||||
guard let item = try? loadItem(path: itemPath) else {
|
||||
@ -1522,7 +1555,7 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
}
|
||||
}
|
||||
|
||||
static func getFirstFrameSynchronously(basePath: String, sourceId: String, size: CGSize, allocateTempFile: @escaping () -> String) -> AnimationCacheItem? {
|
||||
static func getFirstFrameSynchronously(basePath: String, sourceId: String, size: CGSize, allocateTempFile: @escaping () -> String, updateStorageStats: @escaping (String, Int64) -> Void) -> AnimationCacheItem? {
|
||||
let hashString = md5Hash(sourceId)
|
||||
let sourceIdPath = itemSubpath(hashString: hashString, width: Int(size.width), height: Int(size.height))
|
||||
let itemDirectoryPath = "\(basePath)/\(sourceIdPath.directory)"
|
||||
@ -1535,7 +1568,7 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
}
|
||||
|
||||
if let adaptationItemPath = findHigherResolutionFileForAdaptation(itemDirectoryPath: itemDirectoryPath, baseName: "\(hashString)_", baseSuffix: "-f", width: Int(size.width), height: Int(size.height)) {
|
||||
if let adaptedItem = adaptItemFromHigherResolution(currentQueue: .mainQueue(), itemPath: itemFirstFramePath, width: Int(size.width), height: Int(size.height), itemDirectoryPath: itemDirectoryPath, higherResolutionPath: adaptationItemPath, allocateTempFile: allocateTempFile) {
|
||||
if let adaptedItem = adaptItemFromHigherResolution(currentQueue: .mainQueue(), itemPath: itemFirstFramePath, width: Int(size.width), height: Int(size.height), itemDirectoryPath: itemDirectoryPath, higherResolutionPath: adaptationItemPath, allocateTempFile: allocateTempFile, updateStorageStats: updateStorageStats) {
|
||||
return adaptedItem
|
||||
}
|
||||
}
|
||||
@ -1543,7 +1576,7 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
return nil
|
||||
}
|
||||
|
||||
static func getFirstFrame(queue: Queue, basePath: String, sourceId: String, size: CGSize, allocateTempFile: @escaping () -> String, fetch: ((AnimationCacheFetchOptions) -> Disposable)?, completion: @escaping (AnimationCacheItemResult) -> Void) -> Disposable {
|
||||
static func getFirstFrame(queue: Queue, basePath: String, sourceId: String, size: CGSize, allocateTempFile: @escaping () -> String, updateStorageStats: @escaping (String, Int64) -> Void, fetch: ((AnimationCacheFetchOptions) -> Disposable)?, completion: @escaping (AnimationCacheItemResult) -> Void) -> Disposable {
|
||||
let hashString = md5Hash(sourceId)
|
||||
let sourceIdPath = itemSubpath(hashString: hashString, width: Int(size.width), height: Int(size.height))
|
||||
let itemDirectoryPath = "\(basePath)/\(sourceIdPath.directory)"
|
||||
@ -1555,7 +1588,7 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
}
|
||||
|
||||
if let adaptationItemPath = findHigherResolutionFileForAdaptation(itemDirectoryPath: itemDirectoryPath, baseName: "\(hashString)_", baseSuffix: "-f", width: Int(size.width), height: Int(size.height)) {
|
||||
if let adaptedItem = adaptItemFromHigherResolution(currentQueue: .mainQueue(), itemPath: itemFirstFramePath, width: Int(size.width), height: Int(size.height), itemDirectoryPath: itemDirectoryPath, higherResolutionPath: adaptationItemPath, allocateTempFile: allocateTempFile) {
|
||||
if let adaptedItem = adaptItemFromHigherResolution(currentQueue: .mainQueue(), itemPath: itemFirstFramePath, width: Int(size.width), height: Int(size.height), itemDirectoryPath: itemDirectoryPath, higherResolutionPath: adaptationItemPath, allocateTempFile: allocateTempFile, updateStorageStats: updateStorageStats) {
|
||||
completion(AnimationCacheItemResult(item: adaptedItem, isFinal: true))
|
||||
return EmptyDisposable
|
||||
}
|
||||
@ -1579,6 +1612,9 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
completion(AnimationCacheItemResult(item: nil, isFinal: true))
|
||||
return
|
||||
}
|
||||
if let size = fileSize(itemFirstFramePath) {
|
||||
updateStorageStats(itemFirstFramePath, size)
|
||||
}
|
||||
guard let item = try? loadItem(path: itemFirstFramePath) else {
|
||||
completion(AnimationCacheItemResult(item: nil, isFinal: true))
|
||||
return
|
||||
@ -1604,14 +1640,16 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
private let basePath: String
|
||||
private let impl: QueueLocalObject<Impl>
|
||||
private let allocateTempFile: () -> String
|
||||
private let updateStorageStats: (String, Int64) -> Void
|
||||
|
||||
public init(basePath: String, allocateTempFile: @escaping () -> String) {
|
||||
public init(basePath: String, allocateTempFile: @escaping () -> String, updateStorageStats: @escaping (String, Int64) -> Void) {
|
||||
let queue = Queue()
|
||||
self.queue = queue
|
||||
self.basePath = basePath
|
||||
self.allocateTempFile = allocateTempFile
|
||||
self.updateStorageStats = updateStorageStats
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return Impl(queue: queue, basePath: basePath, allocateTempFile: allocateTempFile)
|
||||
return Impl(queue: queue, basePath: basePath, allocateTempFile: allocateTempFile, updateStorageStats: updateStorageStats)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1634,7 +1672,7 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
}
|
||||
|
||||
public func getFirstFrameSynchronously(sourceId: String, size: CGSize) -> AnimationCacheItem? {
|
||||
return Impl.getFirstFrameSynchronously(basePath: self.basePath, sourceId: sourceId, size: size, allocateTempFile: self.allocateTempFile)
|
||||
return Impl.getFirstFrameSynchronously(basePath: self.basePath, sourceId: sourceId, size: size, allocateTempFile: self.allocateTempFile, updateStorageStats: self.updateStorageStats)
|
||||
}
|
||||
|
||||
public func getFirstFrame(queue: Queue, sourceId: String, size: CGSize, fetch: ((AnimationCacheFetchOptions) -> Disposable)?, completion: @escaping (AnimationCacheItemResult) -> Void) -> Disposable {
|
||||
@ -1642,8 +1680,9 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
|
||||
let basePath = self.basePath
|
||||
let allocateTempFile = self.allocateTempFile
|
||||
let updateStorageStats = self.updateStorageStats
|
||||
queue.async {
|
||||
disposable.set(Impl.getFirstFrame(queue: queue, basePath: basePath, sourceId: sourceId, size: size, allocateTempFile: allocateTempFile, fetch: fetch, completion: completion))
|
||||
disposable.set(Impl.getFirstFrame(queue: queue, basePath: basePath, sourceId: sourceId, size: size, allocateTempFile: allocateTempFile, updateStorageStats: updateStorageStats, fetch: fetch, completion: completion))
|
||||
}
|
||||
|
||||
return disposable
|
||||
|
@ -332,15 +332,18 @@ final class PieChartComponent: Component {
|
||||
private struct CalculatedLayout {
|
||||
var size: CGSize
|
||||
var sections: [CalculatedSection]
|
||||
var isEmpty: Bool
|
||||
|
||||
init(size: CGSize, sections: [CalculatedSection]) {
|
||||
self.size = size
|
||||
self.sections = sections
|
||||
self.isEmpty = sections.isEmpty
|
||||
}
|
||||
|
||||
init(interpolating start: CalculatedLayout, to end: CalculatedLayout, progress: CGFloat, size: CGSize) {
|
||||
self.size = size
|
||||
self.sections = []
|
||||
self.isEmpty = end.isEmpty
|
||||
|
||||
for i in 0 ..< end.sections.count {
|
||||
let right = end.sections[i]
|
||||
@ -370,16 +373,17 @@ final class PieChartComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
init(size: CGSize, items: [ChartData.Item], selectedKey: AnyHashable?) {
|
||||
init(size: CGSize, items: [ChartData.Item], selectedKey: AnyHashable?, isEmpty: Bool) {
|
||||
self.size = size
|
||||
self.sections = []
|
||||
self.isEmpty = isEmpty
|
||||
|
||||
if items.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
let innerDiameter: CGFloat = 100.0
|
||||
let spacing: CGFloat = 2.0
|
||||
let innerDiameter: CGFloat = isEmpty ? 90.0 : 100.0
|
||||
let spacing: CGFloat = isEmpty ? -0.5 : 2.0
|
||||
let innerAngleSpacing: CGFloat = spacing / (innerDiameter * 0.5)
|
||||
|
||||
var angles: [Double] = []
|
||||
@ -389,8 +393,8 @@ final class PieChartComponent: Component {
|
||||
angles.append(angle)
|
||||
}
|
||||
|
||||
let diameter: CGFloat = 200.0
|
||||
let reducedDiameter: CGFloat = 170.0
|
||||
let diameter: CGFloat = isEmpty ? (innerDiameter + 6.0 * 2.0) : 200.0
|
||||
let reducedDiameter: CGFloat = floor(0.85 * diameter)
|
||||
|
||||
var anglesData: [ItemAngleData] = []
|
||||
|
||||
@ -413,30 +417,8 @@ final class PieChartComponent: Component {
|
||||
|
||||
let angleValue: CGFloat = angles[i]
|
||||
|
||||
var beforeSpacingFraction: CGFloat = 1.0
|
||||
var afterSpacingFraction: CGFloat = 1.0
|
||||
if item.mergeable {
|
||||
let previousItem: ChartData.Item
|
||||
if i == 0 {
|
||||
previousItem = items[items.count - 1]
|
||||
} else {
|
||||
previousItem = items[i - 1]
|
||||
}
|
||||
|
||||
let nextItem: ChartData.Item
|
||||
if i == items.count - 1 {
|
||||
nextItem = items[0]
|
||||
} else {
|
||||
nextItem = items[i + 1]
|
||||
}
|
||||
|
||||
if previousItem.mergeable {
|
||||
beforeSpacingFraction = item.mergeFactor * 1.0 + (1.0 - item.mergeFactor) * (-0.2)
|
||||
}
|
||||
if nextItem.mergeable {
|
||||
afterSpacingFraction = item.mergeFactor * 1.0 + (1.0 - item.mergeFactor) * (-0.2)
|
||||
}
|
||||
}
|
||||
let beforeSpacingFraction: CGFloat = 1.0
|
||||
let afterSpacingFraction: CGFloat = 1.0
|
||||
|
||||
let innerStartAngle = startAngle + innerAngleSpacing * 0.5
|
||||
let arcInnerStartAngle = startAngle + innerAngleSpacing * 0.5 * beforeSpacingFraction
|
||||
@ -453,9 +435,11 @@ final class PieChartComponent: Component {
|
||||
var arcOuterEndAngle = startAngle + angleValue - angleSpacing * 0.5 * afterSpacingFraction
|
||||
arcOuterEndAngle = max(arcOuterEndAngle, arcOuterStartAngle)
|
||||
|
||||
let itemColor: UIColor = isEmpty ? UIColor(rgb: 0x34C759) : item.color
|
||||
|
||||
self.sections.append(CalculatedSection(
|
||||
id: item.id,
|
||||
color: item.color,
|
||||
color: itemColor,
|
||||
innerAngle: arcInnerStartAngle ..< arcInnerEndAngle,
|
||||
outerAngle: arcOuterStartAngle ..< arcOuterEndAngle,
|
||||
innerRadius: innerDiameter * 0.5,
|
||||
@ -705,10 +689,15 @@ final class PieChartComponent: Component {
|
||||
}
|
||||
|
||||
private final class ParticleSet {
|
||||
private let innerRadius: CGFloat
|
||||
private let maxRadius: CGFloat
|
||||
private(set) var particles: [Particle] = []
|
||||
|
||||
init() {
|
||||
self.generateParticles(preAdvance: true)
|
||||
init(innerRadius: CGFloat, maxRadius: CGFloat, preAdvance: Bool) {
|
||||
self.innerRadius = innerRadius
|
||||
self.maxRadius = maxRadius
|
||||
|
||||
self.generateParticles(preAdvance: preAdvance)
|
||||
}
|
||||
|
||||
private func generateParticles(preAdvance: Bool) {
|
||||
@ -768,12 +757,13 @@ final class PieChartComponent: Component {
|
||||
|
||||
func update(deltaTime: CGFloat) {
|
||||
let size = CGSize(width: 200.0, height: 200.0)
|
||||
let radius2 = pow(size.width * 0.5 + 10.0, 2.0)
|
||||
let radius = size.width * 0.5 + 10.0
|
||||
for i in (0 ..< self.particles.count).reversed() {
|
||||
self.particles[i].update(deltaTime: deltaTime)
|
||||
let position = self.particles[i].position
|
||||
|
||||
if pow(position.x - size.width * 0.5, 2.0) + pow(position.y - size.height * 0.5, 2.0) > radius2 {
|
||||
let distance = sqrt(pow(position.x - size.width * 0.5, 2.0) + pow(position.y - size.height * 0.5, 2.0))
|
||||
if distance > radius {
|
||||
self.particles.remove(at: i)
|
||||
}
|
||||
}
|
||||
@ -901,7 +891,7 @@ final class PieChartComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
func updateParticles(particleSet: ParticleSet) {
|
||||
func updateParticles(particleSet: ParticleSet, alpha: CGFloat) {
|
||||
guard let particleImage = self.particleImage else {
|
||||
return
|
||||
}
|
||||
@ -922,7 +912,93 @@ final class PieChartComponent: Component {
|
||||
|
||||
particleLayer.position = particle.position
|
||||
particleLayer.transform = CATransform3DMakeScale(particle.scale, particle.scale, 1.0)
|
||||
particleLayer.opacity = Float(particle.alpha)
|
||||
particleLayer.opacity = Float(particle.alpha * alpha)
|
||||
}
|
||||
if particleSet.particles.count < self.particleLayers.count {
|
||||
for i in particleSet.particles.count ..< self.particleLayers.count {
|
||||
self.particleLayers[i].isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class DoneLayer: SimpleLayer {
|
||||
private let maskShapeLayer: CAShapeLayer
|
||||
private var particleImage: UIImage?
|
||||
private var particleSet: ParticleSet?
|
||||
private var particleLayers: [SimpleLayer] = []
|
||||
|
||||
override init() {
|
||||
self.maskShapeLayer = CAShapeLayer()
|
||||
self.maskShapeLayer.fillColor = UIColor.black.cgColor
|
||||
self.maskShapeLayer.fillRule = .evenOdd
|
||||
|
||||
super.init()
|
||||
|
||||
self.particleImage = UIImage(bundleImageName: "Settings/Storage/ParticleStar")?.precomposed()
|
||||
|
||||
let path = CGMutablePath()
|
||||
|
||||
path.addRect(CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 200.0, height: 200.0)))
|
||||
path.addEllipse(in: CGRect(origin: CGPoint(x: floor((200.0 - 102.0) * 0.5), y: floor((200.0 - 102.0) * 0.5)), size: CGSize(width: 102.0, height: 102.0)))
|
||||
|
||||
self.maskShapeLayer.path = path
|
||||
self.mask = self.maskShapeLayer
|
||||
|
||||
self.particleSet = ParticleSet(innerRadius: 45.0, maxRadius: 100.0, preAdvance: true)
|
||||
}
|
||||
|
||||
override init(layer: Any) {
|
||||
self.maskShapeLayer = CAShapeLayer()
|
||||
|
||||
super.init(layer: layer)
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func updateParticles(deltaTime: CGFloat) {
|
||||
guard let particleSet = self.particleSet else {
|
||||
return
|
||||
}
|
||||
particleSet.update(deltaTime: deltaTime)
|
||||
|
||||
let size = CGSize(width: 200.0, height: 200.0)
|
||||
|
||||
guard let particleImage = self.particleImage else {
|
||||
return
|
||||
}
|
||||
for i in 0 ..< particleSet.particles.count {
|
||||
let particle = particleSet.particles[i]
|
||||
|
||||
let particleLayer: SimpleLayer
|
||||
if i < self.particleLayers.count {
|
||||
particleLayer = self.particleLayers[i]
|
||||
particleLayer.isHidden = false
|
||||
} else {
|
||||
particleLayer = SimpleLayer()
|
||||
particleLayer.contents = particleImage.cgImage
|
||||
particleLayer.bounds = CGRect(origin: CGPoint(), size: particleImage.size)
|
||||
self.particleLayers.append(particleLayer)
|
||||
self.addSublayer(particleLayer)
|
||||
|
||||
particleLayer.layerTintColor = UIColor(rgb: 0x34C759).cgColor
|
||||
}
|
||||
|
||||
particleLayer.position = particle.position
|
||||
particleLayer.transform = CATransform3DMakeScale(particle.scale * 1.2, particle.scale * 1.2, 1.0)
|
||||
|
||||
let distance = sqrt(pow(particle.position.x - size.width * 0.5, 2.0) + pow(particle.position.y - size.height * 0.5, 2.0))
|
||||
var mulAlpha: CGFloat = 1.0
|
||||
let outerDistanceNorm: CGFloat = 20.0
|
||||
if distance > 100.0 - outerDistanceNorm {
|
||||
let outerDistanceFactor: CGFloat = (100.0 - distance) / outerDistanceNorm
|
||||
let alphaFactor: CGFloat = max(0.0, min(1.0, outerDistanceFactor))
|
||||
mulAlpha = alphaFactor
|
||||
}
|
||||
|
||||
particleLayer.opacity = Float(particle.alpha * mulAlpha)
|
||||
}
|
||||
if particleSet.particles.count < self.particleLayers.count {
|
||||
for i in particleSet.particles.count ..< self.particleLayers.count {
|
||||
@ -945,10 +1021,10 @@ final class PieChartComponent: Component {
|
||||
|
||||
private var sectionLayers: [AnyHashable: SectionLayer] = [:]
|
||||
private let particleSet: ParticleSet
|
||||
private var labels: [AnyHashable: ChartLabel] = [:]
|
||||
private var doneLayer: DoneLayer?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.particleSet = ParticleSet()
|
||||
self.particleSet = ParticleSet(innerRadius: 50.0, maxRadius: 100.0, preAdvance: true)
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
@ -1008,6 +1084,7 @@ final class PieChartComponent: Component {
|
||||
if self.theme !== theme || self.data != data || self.selectedKey != selectedKey {
|
||||
self.theme = theme
|
||||
self.selectedKey = selectedKey
|
||||
let previousData = self.data
|
||||
|
||||
if animated, let previous = self.currentLayout {
|
||||
var initialState = previous
|
||||
@ -1016,19 +1093,42 @@ final class PieChartComponent: Component {
|
||||
let mappedProgress = listViewAnimationCurveSystem(CGFloat(currentProgress))
|
||||
initialState = CalculatedLayout(interpolating: currentAnimation.start, to: previous, progress: mappedProgress, size: previous.size)
|
||||
}
|
||||
let targetLayout = CalculatedLayout(
|
||||
size: CGSize(width: 200.0, height: 200.0),
|
||||
items: data.items,
|
||||
selectedKey: self.selectedKey
|
||||
)
|
||||
|
||||
let targetLayout: CalculatedLayout
|
||||
if let previousData = previousData, data.items.isEmpty {
|
||||
targetLayout = CalculatedLayout(
|
||||
size: CGSize(width: 200.0, height: 200.0),
|
||||
items: previousData.items,
|
||||
selectedKey: self.selectedKey,
|
||||
isEmpty: true
|
||||
)
|
||||
} else {
|
||||
targetLayout = CalculatedLayout(
|
||||
size: CGSize(width: 200.0, height: 200.0),
|
||||
items: data.items,
|
||||
selectedKey: self.selectedKey,
|
||||
isEmpty: false
|
||||
)
|
||||
}
|
||||
|
||||
self.currentLayout = targetLayout
|
||||
self.currentAnimation = (initialState, CACurrentMediaTime(), 0.4)
|
||||
} else {
|
||||
self.currentLayout = CalculatedLayout(
|
||||
size: CGSize(width: 200.0, height: 200.0),
|
||||
items: data.items,
|
||||
selectedKey: self.selectedKey
|
||||
)
|
||||
if data.items.isEmpty {
|
||||
self.currentLayout = CalculatedLayout(
|
||||
size: CGSize(width: 200.0, height: 200.0),
|
||||
items: [.init(id: .other, displayValue: 0.0, displaySize: 0, value: 1.0, color: .green, mergeable: false, mergeFactor: 1.0)],
|
||||
selectedKey: self.selectedKey,
|
||||
isEmpty: true
|
||||
)
|
||||
} else {
|
||||
self.currentLayout = CalculatedLayout(
|
||||
size: CGSize(width: 200.0, height: 200.0),
|
||||
items: data.items,
|
||||
selectedKey: self.selectedKey,
|
||||
isEmpty: data.items.isEmpty
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
self.data = data
|
||||
@ -1043,15 +1143,71 @@ final class PieChartComponent: Component {
|
||||
var validIds: [AnyHashable] = []
|
||||
if let currentLayout = self.currentLayout {
|
||||
var effectiveLayout = currentLayout
|
||||
var verticalOffset: CGFloat = 0.0
|
||||
var particleAlpha: CGFloat = 1.0
|
||||
var rotationAngle: CGFloat = 0.0
|
||||
let emptyRotationAngle: CGFloat = CGFloat.pi
|
||||
let emptyVerticalOffset: CGFloat = (92.0 - 200.0) * 0.5
|
||||
if let currentAnimation = self.currentAnimation {
|
||||
let currentProgress: Double = max(0.0, min(1.0, (CACurrentMediaTime() - currentAnimation.startTime) / currentAnimation.duration))
|
||||
let mappedProgress = listViewAnimationCurveSystem(CGFloat(currentProgress))
|
||||
|
||||
effectiveLayout = CalculatedLayout(interpolating: currentAnimation.start, to: currentLayout, progress: mappedProgress, size: currentLayout.size)
|
||||
|
||||
let fromVerticalOffset: CGFloat
|
||||
let fromRotationAngle: CGFloat
|
||||
if currentAnimation.start.isEmpty {
|
||||
fromVerticalOffset = emptyVerticalOffset
|
||||
fromRotationAngle = emptyRotationAngle
|
||||
} else {
|
||||
fromVerticalOffset = 0.0
|
||||
fromRotationAngle = 0.0
|
||||
}
|
||||
let toVerticalOffset: CGFloat
|
||||
let toRotationAngle: CGFloat
|
||||
if currentLayout.isEmpty {
|
||||
toVerticalOffset = emptyVerticalOffset
|
||||
toRotationAngle = emptyRotationAngle
|
||||
} else {
|
||||
toVerticalOffset = 0.0
|
||||
toRotationAngle = 0.0
|
||||
}
|
||||
|
||||
verticalOffset = (1.0 - mappedProgress) * fromVerticalOffset + mappedProgress * toVerticalOffset
|
||||
rotationAngle = (1.0 - mappedProgress) * fromRotationAngle + mappedProgress * toRotationAngle
|
||||
|
||||
if currentLayout.isEmpty {
|
||||
particleAlpha = 1.0 - mappedProgress
|
||||
}
|
||||
|
||||
if currentProgress >= 1.0 - CGFloat.ulpOfOne {
|
||||
self.currentAnimation = nil
|
||||
}
|
||||
} else {
|
||||
if currentLayout.isEmpty {
|
||||
verticalOffset = emptyVerticalOffset
|
||||
particleAlpha = 0.0
|
||||
rotationAngle = emptyRotationAngle
|
||||
}
|
||||
}
|
||||
|
||||
if currentLayout.isEmpty {
|
||||
let doneLayer: DoneLayer
|
||||
if let current = self.doneLayer {
|
||||
doneLayer = current
|
||||
} else {
|
||||
doneLayer = DoneLayer()
|
||||
self.doneLayer = doneLayer
|
||||
self.layer.insertSublayer(doneLayer, at: 0)
|
||||
}
|
||||
doneLayer.updateParticles(deltaTime: deltaTime)
|
||||
doneLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: CGSize(width: 200.0, height: 200.0))
|
||||
doneLayer.opacity = Float(1.0 - particleAlpha)
|
||||
} else {
|
||||
if let doneLayer = self.doneLayer {
|
||||
self.doneLayer = nil
|
||||
doneLayer.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
|
||||
for section in effectiveLayout.sections {
|
||||
@ -1066,9 +1222,12 @@ final class PieChartComponent: Component {
|
||||
self.layer.addSublayer(sectionLayer)
|
||||
}
|
||||
|
||||
sectionLayer.frame = CGRect(origin: CGPoint(), size: CGSize(width: 200.0, height: 200.0))
|
||||
let sectionLayerFrame = CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: CGSize(width: 200.0, height: 200.0))
|
||||
sectionLayer.position = sectionLayerFrame.center
|
||||
sectionLayer.bounds = CGRect(origin: CGPoint(), size: sectionLayerFrame.size)
|
||||
sectionLayer.transform = CATransform3DMakeRotation(rotationAngle, 0.0, 0.0, 1.0)
|
||||
sectionLayer.update(size: sectionLayer.bounds.size, section: section)
|
||||
sectionLayer.updateParticles(particleSet: self.particleSet)
|
||||
sectionLayer.updateParticles(particleSet: self.particleSet, alpha: particleAlpha)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,7 +289,11 @@ private final class PeerListItemComponent: Component {
|
||||
} else {
|
||||
clipStyle = .round
|
||||
}
|
||||
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, clipStyle: clipStyle, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
|
||||
if peer.id == component.context.account.peerId {
|
||||
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, overrideImage: .savedMessagesIcon, clipStyle: clipStyle, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
|
||||
} else {
|
||||
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, clipStyle: clipStyle, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
|
||||
}
|
||||
}
|
||||
|
||||
let labelSize = self.label.update(
|
||||
@ -582,13 +586,20 @@ final class StoragePeerListPanelComponent: Component {
|
||||
itemSelectionState = .none
|
||||
}
|
||||
|
||||
let itemTitle: String
|
||||
if item.peer.id == component.context.account.peerId {
|
||||
itemTitle = environment.strings.DialogList_SavedMessages
|
||||
} else {
|
||||
itemTitle = item.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
|
||||
}
|
||||
|
||||
let _ = itemView.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(PeerListItemComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
sideInset: environment.containerInsets.left,
|
||||
title: item.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
|
||||
title: itemTitle,
|
||||
peer: item.peer,
|
||||
label: dataSizeString(item.size, formatting: dataSizeFormatting),
|
||||
selectionState: itemSelectionState,
|
||||
|
@ -727,6 +727,8 @@ final class StorageUsageScreenComponent: Component {
|
||||
private var doneStatusCircle: SimpleShapeLayer?
|
||||
private var doneStatusNode: RadialStatusNode?
|
||||
|
||||
private let scrollContainerView: UIView
|
||||
|
||||
private let pieChartView = ComponentView<Empty>()
|
||||
private let chartTotalLabel = ComponentView<Empty>()
|
||||
private let categoriesView = ComponentView<Empty>()
|
||||
@ -777,6 +779,8 @@ final class StorageUsageScreenComponent: Component {
|
||||
self.navigationSeparatorLayerContainer = SimpleLayer()
|
||||
self.navigationSeparatorLayerContainer.opacity = 0.0
|
||||
|
||||
self.scrollContainerView = UIView()
|
||||
|
||||
self.scrollView = ScrollViewImpl()
|
||||
|
||||
self.keepDurationSectionContainerView = UIView()
|
||||
@ -805,7 +809,9 @@ final class StorageUsageScreenComponent: Component {
|
||||
self.scrollView.clipsToBounds = true
|
||||
self.addSubview(self.scrollView)
|
||||
|
||||
self.scrollView.addSubview(self.keepDurationSectionContainerView)
|
||||
self.scrollView.addSubview(self.scrollContainerView)
|
||||
|
||||
self.scrollContainerView.addSubview(self.keepDurationSectionContainerView)
|
||||
|
||||
self.scrollView.layer.addSublayer(self.headerProgressBackgroundLayer)
|
||||
self.scrollView.layer.addSublayer(self.headerProgressForegroundLayer)
|
||||
@ -1070,9 +1076,10 @@ final class StorageUsageScreenComponent: Component {
|
||||
alphaTransition.setAlpha(view: self.scrollView, alpha: self.aggregatedData != nil ? 1.0 : 0.0)
|
||||
alphaTransition.setAlpha(view: self.headerOffsetContainer, alpha: self.aggregatedData != nil ? 1.0 : 0.0)
|
||||
} else if case .clearedItems = animationHint.value {
|
||||
if let snapshotView = self.snapshotView(afterScreenUpdates: false) {
|
||||
snapshotView.frame = self.bounds
|
||||
self.addSubview(snapshotView)
|
||||
if let snapshotView = self.scrollContainerView.snapshotView(afterScreenUpdates: false) {
|
||||
snapshotView.frame = self.scrollContainerView.frame
|
||||
self.scrollView.insertSubview(snapshotView, aboveSubview: self.scrollContainerView)
|
||||
self.scrollContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
@ -1347,12 +1354,20 @@ final class StorageUsageScreenComponent: Component {
|
||||
chartItems.append(chartItem)
|
||||
}
|
||||
|
||||
chartItems.append(PieChartComponent.ChartData.Item(id: .other, displayValue: otherRealSum, displaySize: totalOtherSize, value: self.isOtherCategoryExpanded ? 0.0 : otherSum, color: Category.misc.color, mergeable: false, mergeFactor: 1.0))
|
||||
if !listCategories.isEmpty {
|
||||
chartItems.append(PieChartComponent.ChartData.Item(id: .other, displayValue: otherRealSum, displaySize: totalOtherSize, value: self.isOtherCategoryExpanded ? 0.0 : otherSum, color: Category.misc.color, mergeable: false, mergeFactor: 1.0))
|
||||
}
|
||||
|
||||
let chartData = PieChartComponent.ChartData(items: chartItems)
|
||||
self.pieChartView.parentState = state
|
||||
|
||||
var pieChartTransition = transition
|
||||
if transition.animation.isImmediate, let animationHint, case .clearedItems = animationHint.value {
|
||||
pieChartTransition = Transition(animation: .curve(duration: 0.4, curve: .spring))
|
||||
}
|
||||
|
||||
let pieChartSize = self.pieChartView.update(
|
||||
transition: transition,
|
||||
transition: pieChartTransition,
|
||||
component: AnyComponent(PieChartComponent(
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
@ -1367,8 +1382,8 @@ final class StorageUsageScreenComponent: Component {
|
||||
self.scrollView.addSubview(pieChartComponentView)
|
||||
}
|
||||
|
||||
transition.setFrame(view: pieChartComponentView, frame: pieChartFrame)
|
||||
transition.setAlpha(view: pieChartComponentView, alpha: listCategories.isEmpty ? 0.0 : 1.0)
|
||||
pieChartTransition.setFrame(view: pieChartComponentView, frame: pieChartFrame)
|
||||
//transition.setAlpha(view: pieChartComponentView, alpha: listCategories.isEmpty ? 0.0 : 1.0)
|
||||
}
|
||||
if let _ = self.aggregatedData, listCategories.isEmpty {
|
||||
let checkColor = UIColor(rgb: 0x34C759)
|
||||
@ -1392,7 +1407,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
} else {
|
||||
doneStatusCircle = SimpleShapeLayer()
|
||||
self.doneStatusCircle = doneStatusCircle
|
||||
self.scrollView.layer.addSublayer(doneStatusCircle)
|
||||
//self.scrollView.layer.addSublayer(doneStatusCircle)
|
||||
doneStatusCircle.opacity = 0.0
|
||||
}
|
||||
|
||||
@ -1431,7 +1446,11 @@ final class StorageUsageScreenComponent: Component {
|
||||
if listCategories.isEmpty {
|
||||
headerText = environment.strings.StorageManagement_TitleCleared
|
||||
} else if let peer = component.peer {
|
||||
headerText = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
|
||||
if peer.id == component.context.account.peerId {
|
||||
headerText = environment.strings.DialogList_SavedMessages
|
||||
} else {
|
||||
headerText = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
|
||||
}
|
||||
} else {
|
||||
headerText = environment.strings.StorageManagement_Title
|
||||
}
|
||||
@ -1527,7 +1546,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
let headerDescriptionFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - headerDescriptionSize.width) / 2.0), y: contentHeight), size: headerDescriptionSize)
|
||||
if let headerDescriptionComponentView = self.headerDescriptionView.view {
|
||||
if headerDescriptionComponentView.superview == nil {
|
||||
self.scrollView.addSubview(headerDescriptionComponentView)
|
||||
self.scrollContainerView.addSubview(headerDescriptionComponentView)
|
||||
}
|
||||
transition.setFrame(view: headerDescriptionComponentView, frame: headerDescriptionFrame)
|
||||
}
|
||||
@ -1562,14 +1581,14 @@ final class StorageUsageScreenComponent: Component {
|
||||
} else {
|
||||
chartAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 17.0))
|
||||
self.chartAvatarNode = chartAvatarNode
|
||||
if let pieChartComponentView = self.pieChartView.view {
|
||||
self.scrollView.insertSubview(chartAvatarNode.view, belowSubview: pieChartComponentView)
|
||||
} else {
|
||||
self.scrollView.addSubview(chartAvatarNode.view)
|
||||
}
|
||||
self.scrollContainerView.addSubview(chartAvatarNode.view)
|
||||
chartAvatarNode.frame = avatarFrame
|
||||
|
||||
chartAvatarNode.setPeer(context: component.context, theme: environment.theme, peer: peer, displayDimensions: avatarSize)
|
||||
if peer.id == component.context.account.peerId {
|
||||
chartAvatarNode.setPeer(context: component.context, theme: environment.theme, peer: peer, overrideImage: .savedMessagesIcon, displayDimensions: avatarSize)
|
||||
} else {
|
||||
chartAvatarNode.setPeer(context: component.context, theme: environment.theme, peer: peer, displayDimensions: avatarSize)
|
||||
}
|
||||
}
|
||||
transition.setAlpha(view: chartAvatarNode.view, alpha: listCategories.isEmpty ? 0.0 : 1.0)
|
||||
} else {
|
||||
@ -1606,11 +1625,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
)
|
||||
if let chartTotalLabelView = self.chartTotalLabel.view {
|
||||
if chartTotalLabelView.superview == nil {
|
||||
if let pieChartComponentView = self.pieChartView.view {
|
||||
self.scrollView.insertSubview(chartTotalLabelView, belowSubview: pieChartComponentView)
|
||||
} else {
|
||||
self.scrollView.addSubview(chartTotalLabelView)
|
||||
}
|
||||
self.scrollContainerView.addSubview(chartTotalLabelView)
|
||||
}
|
||||
let totalLabelFrame = CGRect(origin: CGPoint(x: pieChartFrame.minX + floor((pieChartFrame.width - chartTotalLabelSize.width) / 2.0), y: pieChartFrame.minY + floor((pieChartFrame.height - chartTotalLabelSize.height) / 2.0)), size: chartTotalLabelSize)
|
||||
transition.setFrame(view: chartTotalLabelView, frame: totalLabelFrame)
|
||||
@ -1674,7 +1689,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
)
|
||||
if let categoriesComponentView = self.categoriesView.view {
|
||||
if categoriesComponentView.superview == nil {
|
||||
self.scrollView.addSubview(categoriesComponentView)
|
||||
self.scrollContainerView.addSubview(categoriesComponentView)
|
||||
}
|
||||
|
||||
transition.setFrame(view: categoriesComponentView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: categoriesSize))
|
||||
@ -1697,7 +1712,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
let categoriesDescriptionFrame = CGRect(origin: CGPoint(x: sideInset + 15.0, y: contentHeight), size: categoriesDescriptionSize)
|
||||
if let categoriesDescriptionComponentView = self.categoriesDescriptionView.view {
|
||||
if categoriesDescriptionComponentView.superview == nil {
|
||||
self.scrollView.addSubview(categoriesDescriptionComponentView)
|
||||
self.scrollContainerView.addSubview(categoriesDescriptionComponentView)
|
||||
}
|
||||
transition.setFrame(view: categoriesDescriptionComponentView, frame: categoriesDescriptionFrame)
|
||||
}
|
||||
@ -1728,7 +1743,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
let keepDurationTitleFrame = CGRect(origin: CGPoint(x: sideInset + 15.0, y: contentHeight), size: keepDurationTitleSize)
|
||||
if let keepDurationTitleComponentView = self.keepDurationTitleView.view {
|
||||
if keepDurationTitleComponentView.superview == nil {
|
||||
self.scrollView.addSubview(keepDurationTitleComponentView)
|
||||
self.scrollContainerView.addSubview(keepDurationTitleComponentView)
|
||||
}
|
||||
transition.setFrame(view: keepDurationTitleComponentView, frame: keepDurationTitleFrame)
|
||||
}
|
||||
@ -1830,7 +1845,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
let keepDurationDescriptionFrame = CGRect(origin: CGPoint(x: sideInset + 15.0, y: contentHeight), size: keepDurationDescriptionSize)
|
||||
if let keepDurationDescriptionComponentView = self.keepDurationDescriptionView.view {
|
||||
if keepDurationDescriptionComponentView.superview == nil {
|
||||
self.scrollView.addSubview(keepDurationDescriptionComponentView)
|
||||
self.scrollContainerView.addSubview(keepDurationDescriptionComponentView)
|
||||
}
|
||||
transition.setFrame(view: keepDurationDescriptionComponentView, frame: keepDurationDescriptionFrame)
|
||||
}
|
||||
@ -1856,7 +1871,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
let keepSizeTitleFrame = CGRect(origin: CGPoint(x: sideInset + 15.0, y: contentHeight), size: keepSizeTitleSize)
|
||||
if let keepSizeTitleComponentView = self.keepSizeTitleView.view {
|
||||
if keepSizeTitleComponentView.superview == nil {
|
||||
self.scrollView.addSubview(keepSizeTitleComponentView)
|
||||
self.scrollContainerView.addSubview(keepSizeTitleComponentView)
|
||||
}
|
||||
transition.setFrame(view: keepSizeTitleComponentView, frame: keepSizeTitleFrame)
|
||||
}
|
||||
@ -1887,7 +1902,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
let keepSizeFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: keepSizeSize)
|
||||
if let keepSizeComponentView = self.keepSizeView.view {
|
||||
if keepSizeComponentView.superview == nil {
|
||||
self.scrollView.addSubview(keepSizeComponentView)
|
||||
self.scrollContainerView.addSubview(keepSizeComponentView)
|
||||
}
|
||||
transition.setFrame(view: keepSizeComponentView, frame: keepSizeFrame)
|
||||
}
|
||||
@ -1913,7 +1928,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
let keepSizeDescriptionFrame = CGRect(origin: CGPoint(x: sideInset + 15.0, y: contentHeight), size: keepSizeDescriptionSize)
|
||||
if let keepSizeDescriptionComponentView = self.keepSizeDescriptionView.view {
|
||||
if keepSizeDescriptionComponentView.superview == nil {
|
||||
self.scrollView.addSubview(keepSizeDescriptionComponentView)
|
||||
self.scrollContainerView.addSubview(keepSizeDescriptionComponentView)
|
||||
}
|
||||
transition.setFrame(view: keepSizeDescriptionComponentView, frame: keepSizeDescriptionFrame)
|
||||
}
|
||||
@ -2129,7 +2144,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
)
|
||||
if let panelContainerView = self.panelContainer.view {
|
||||
if panelContainerView.superview == nil {
|
||||
self.scrollView.addSubview(panelContainerView)
|
||||
self.scrollContainerView.addSubview(panelContainerView)
|
||||
}
|
||||
transition.setFrame(view: panelContainerView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: panelContainerSize))
|
||||
}
|
||||
@ -2146,6 +2161,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
if self.scrollView.contentSize != contentSize {
|
||||
self.scrollView.contentSize = contentSize
|
||||
}
|
||||
transition.setFrame(view: self.scrollContainerView, frame: CGRect(origin: CGPoint(), size: contentSize))
|
||||
|
||||
var scrollViewBounds = self.scrollView.bounds
|
||||
scrollViewBounds.size = availableSize
|
||||
@ -2181,7 +2197,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
clearingNode.updateLayout(size: clearingSize, transition: .immediate)
|
||||
|
||||
if animateIn {
|
||||
clearingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.15)
|
||||
clearingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.4)
|
||||
}
|
||||
} else {
|
||||
if let clearingNode = self.clearingNode {
|
||||
@ -2190,10 +2206,10 @@ final class StorageUsageScreenComponent: Component {
|
||||
var delay: Double = 0.0
|
||||
if let clearingDisplayTimestamp = self.clearingDisplayTimestamp {
|
||||
let timeDelta = CFAbsoluteTimeGetCurrent() - clearingDisplayTimestamp
|
||||
if timeDelta < 0.12 {
|
||||
if timeDelta < 0.4 {
|
||||
delay = 0.0
|
||||
} else if timeDelta < 0.4 {
|
||||
delay = 0.4
|
||||
} else if timeDelta < 1.0 {
|
||||
delay = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
@ -2414,7 +2430,9 @@ final class StorageUsageScreenComponent: Component {
|
||||
|
||||
self.isClearing = false
|
||||
|
||||
self.state?.updated(transition: Transition(animation: .none).withUserData(AnimationHint(value: .clearedItems)))
|
||||
if !firstTime {
|
||||
self.state?.updated(transition: Transition(animation: .none).withUserData(AnimationHint(value: .clearedItems)))
|
||||
}
|
||||
|
||||
completion()
|
||||
})
|
||||
@ -2468,8 +2486,21 @@ final class StorageUsageScreenComponent: Component {
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
var openTitle: String = presentationData.strings.StorageManagement_OpenPhoto
|
||||
for media in message.media {
|
||||
if let _ = media as? TelegramMediaImage {
|
||||
openTitle = presentationData.strings.StorageManagement_OpenPhoto
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
if file.isVideo {
|
||||
openTitle = presentationData.strings.StorageManagement_OpenVideo
|
||||
} else {
|
||||
openTitle = presentationData.strings.StorageManagement_OpenFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(
|
||||
text: presentationData.strings.StorageManagement_OpenPhoto,
|
||||
text: openTitle,
|
||||
icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Expand"), color: theme.contextMenu.primaryColor) },
|
||||
action: { [weak self] c, _ in
|
||||
c.dismiss(completion: { [weak self] in
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Settings/Storage/ParticleStar.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/Storage/ParticleStar.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Star.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
7
submodules/TelegramUI/Images.xcassets/Settings/Storage/ParticleStar.imageset/Star.svg
vendored
Normal file
7
submodules/TelegramUI/Images.xcassets/Settings/Storage/ParticleStar.imageset/Star.svg
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Icon / Video Copy 13</title>
|
||||
<g id="Icon-/-Video-Copy-13" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M28.9214873,41.7868781 L14.2382898,37.778549 C13.2560244,37.5104028 12.6771163,36.4967439 12.9452625,35.5144784 C13.1170949,34.8850266 13.6088381,34.3932834 14.2382898,34.221451 L28.9214873,30.2131219 C29.5510238,30.0412663 30.0428079,29.5494165 30.2145795,28.9198571 L34.2213862,14.2345211 C34.4894013,13.2522198 35.5029829,12.6731764 36.4852842,12.9411915 C37.1149283,13.1129862 37.6068191,13.604877 37.7786138,14.2345211 L41.7854205,28.9198571 C41.9571921,29.5494165 42.4489762,30.0412663 43.0785127,30.2131219 L57.7617102,34.221451 C58.7439756,34.4895972 59.3228837,35.5032561 59.0547375,36.4855216 C58.8829051,37.1149734 58.3911619,37.6067166 57.7617102,37.778549 L43.0785127,41.7868781 C42.4489762,41.9587337 41.9571921,42.4505835 41.7854205,43.0801429 L37.7786138,57.7654789 C37.5105987,58.7477802 36.4970171,59.3268236 35.5147158,59.0588085 C34.8850717,58.8870138 34.3931809,58.395123 34.2213862,57.7654789 L30.2145795,43.0801429 C30.0428079,42.4505835 29.5510238,41.9587337 28.9214873,41.7868781 Z" id="Star-Copy-3" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
@ -217,8 +217,13 @@ public final class AccountContextImpl: AccountContext {
|
||||
self.cachedGroupCallContexts = AccountGroupCallContextCacheImpl()
|
||||
self.meshAnimationCache = MeshAnimationCache(mediaBox: account.postbox.mediaBox)
|
||||
|
||||
let cacheStorageBox = self.account.postbox.mediaBox.cacheStorageBox
|
||||
self.animationCache = AnimationCacheImpl(basePath: self.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
}, updateStorageStats: { path, size in
|
||||
if let pathData = path.data(using: .utf8) {
|
||||
cacheStorageBox.update(id: pathData, size: size)
|
||||
}
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
|
||||
|
@ -1306,26 +1306,10 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
BGTaskScheduler.shared.register(forTaskWithIdentifier: taskId, using: DispatchQueue.main) { task in
|
||||
Logger.shared.log("App \(self.episodeId)", "Executing cleanup task")
|
||||
|
||||
let disposable = MetaDisposable()
|
||||
let disposable = self.runCacheReindexTasks(lowImpact: true, completion: {
|
||||
Logger.shared.log("App \(self.episodeId)", "Completed cleanup task")
|
||||
|
||||
let _ = (self.sharedContextPromise.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
|
||||
let _ = (sharedApplicationContext.sharedContext.activeAccountContexts
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { activeAccounts in
|
||||
var signals: Signal<Never, NoError> = .complete()
|
||||
|
||||
for (_, context, _) in activeAccounts.accounts {
|
||||
signals = signals |> then(context.account.cleanupTasks(lowImpact: false))
|
||||
}
|
||||
|
||||
disposable.set(signals.start(completed: {
|
||||
Logger.shared.log("App \(self.episodeId)", "Completed cleanup task")
|
||||
|
||||
task.setTaskCompleted(success: true)
|
||||
}))
|
||||
})
|
||||
task.setTaskCompleted(success: true)
|
||||
})
|
||||
|
||||
task.expirationHandler = {
|
||||
@ -1351,9 +1335,44 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
})
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
private func runCacheReindexTasks(lowImpact: Bool, completion: @escaping () -> Void) -> Disposable {
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
let _ = (self.sharedContextPromise.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
|
||||
let _ = (sharedApplicationContext.sharedContext.activeAccountContexts
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { activeAccounts in
|
||||
var signals: Signal<Never, NoError> = .complete()
|
||||
|
||||
for (_, context, _) in activeAccounts.accounts {
|
||||
signals = signals |> then(context.account.cleanupTasks(lowImpact: lowImpact))
|
||||
}
|
||||
|
||||
disposable.set(signals.start(completed: {
|
||||
completion()
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
return disposable
|
||||
}
|
||||
|
||||
private func resetBadge() {
|
||||
var resetOnce = true
|
||||
self.badgeDisposable.set((self.context.get()
|
||||
|
Loading…
x
Reference in New Issue
Block a user