mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Storage calculation
This commit is contained in:
parent
34909d0de9
commit
45ff6ba714
@ -65,11 +65,11 @@ public func peerAvatarImageData(account: Account, peerReference: PeerReference?,
|
||||
})
|
||||
var fetchedDataDisposable: Disposable?
|
||||
if let peerReference = peerReference {
|
||||
fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .image, reference: .avatar(peer: peerReference, resource: smallProfileImage.resource), statsCategory: .generic).start()
|
||||
fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .avatar, reference: .avatar(peer: peerReference, resource: smallProfileImage.resource), statsCategory: .generic).start()
|
||||
} else if let authorOfMessage = authorOfMessage {
|
||||
fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .image, reference: .messageAuthorAvatar(message: authorOfMessage, resource: smallProfileImage.resource), statsCategory: .generic).start()
|
||||
fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .avatar, reference: .messageAuthorAvatar(message: authorOfMessage, resource: smallProfileImage.resource), statsCategory: .generic).start()
|
||||
} else {
|
||||
fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .image, reference: .standalone(resource: smallProfileImage.resource), statsCategory: .generic).start()
|
||||
fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .avatar, reference: .standalone(resource: smallProfileImage.resource), statsCategory: .generic).start()
|
||||
}
|
||||
return ActionDisposable {
|
||||
resourceDataDisposable.dispose()
|
||||
|
@ -605,7 +605,7 @@ public struct Transition {
|
||||
public func animateBounds(layer: CALayer, from fromValue: CGRect, to toValue: CGRect, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self.animation {
|
||||
case .none:
|
||||
break
|
||||
completion?(true)
|
||||
case let .curve(duration, curve):
|
||||
layer.animate(
|
||||
from: NSValue(cgRect: fromValue),
|
||||
@ -624,7 +624,7 @@ public struct Transition {
|
||||
public func animateBoundsOrigin(layer: CALayer, from fromValue: CGPoint, to toValue: CGPoint, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self.animation {
|
||||
case .none:
|
||||
break
|
||||
completion?(true)
|
||||
case let .curve(duration, curve):
|
||||
layer.animate(
|
||||
from: NSValue(cgPoint: fromValue),
|
||||
@ -643,7 +643,7 @@ public struct Transition {
|
||||
public func animateBoundsSize(layer: CALayer, from fromValue: CGSize, to toValue: CGSize, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self.animation {
|
||||
case .none:
|
||||
break
|
||||
completion?(true)
|
||||
case let .curve(duration, curve):
|
||||
layer.animate(
|
||||
from: NSValue(cgSize: fromValue),
|
||||
@ -661,6 +661,7 @@ public struct Transition {
|
||||
|
||||
public func setCornerRadius(layer: CALayer, cornerRadius: CGFloat, completion: ((Bool) -> Void)? = nil) {
|
||||
if layer.cornerRadius == cornerRadius {
|
||||
completion?(true)
|
||||
return
|
||||
}
|
||||
switch self.animation {
|
||||
@ -693,8 +694,9 @@ public struct Transition {
|
||||
switch self.animation {
|
||||
case .none:
|
||||
layer.path = path
|
||||
completion?(true)
|
||||
case let .curve(duration, curve):
|
||||
if let previousPath = layer.path {
|
||||
if let previousPath = layer.path, previousPath != path {
|
||||
layer.animate(
|
||||
from: previousPath,
|
||||
to: path,
|
||||
@ -709,6 +711,7 @@ public struct Transition {
|
||||
layer.path = path
|
||||
} else {
|
||||
layer.path = path
|
||||
completion?(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -717,6 +720,7 @@ public struct Transition {
|
||||
switch self.animation {
|
||||
case .none:
|
||||
layer.lineWidth = lineWidth
|
||||
completion?(true)
|
||||
case let .curve(duration, curve):
|
||||
let previousLineWidth = layer.lineWidth
|
||||
layer.lineWidth = lineWidth
|
||||
@ -739,6 +743,7 @@ public struct Transition {
|
||||
switch self.animation {
|
||||
case .none:
|
||||
layer.lineDashPattern = pattern
|
||||
completion?(true)
|
||||
case let .curve(duration, curve):
|
||||
if let previousLineDashPattern = layer.lineDashPattern {
|
||||
layer.lineDashPattern = pattern
|
||||
@ -756,6 +761,7 @@ public struct Transition {
|
||||
)
|
||||
} else {
|
||||
layer.lineDashPattern = pattern
|
||||
completion?(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -141,6 +141,7 @@ public final class ComponentView<EnvironmentType> {
|
||||
private var currentSize: CGSize?
|
||||
public private(set) var view: UIView?
|
||||
private(set) var isUpdating: Bool = false
|
||||
public weak var parentState: ComponentState?
|
||||
|
||||
public init() {
|
||||
}
|
||||
@ -181,10 +182,15 @@ public final class ComponentView<EnvironmentType> {
|
||||
context.erasedEnvironment = environmentResult
|
||||
}
|
||||
|
||||
let isEnvironmentUpdated = context.erasedEnvironment.calculateIsUpdated()
|
||||
|
||||
var isStateUpdated = false
|
||||
if componentState.isUpdated {
|
||||
isStateUpdated = true
|
||||
componentState.isUpdated = false
|
||||
}
|
||||
|
||||
if !forceUpdate, !isEnvironmentUpdated, let currentComponent = self.currentComponent, let currentContainerSize = self.currentContainerSize, let currentSize = self.currentSize {
|
||||
let isEnvironmentUpdated = context.erasedEnvironment.calculateIsUpdated()
|
||||
|
||||
if !forceUpdate, !isEnvironmentUpdated, !isStateUpdated, let currentComponent = self.currentComponent, let currentContainerSize = self.currentContainerSize, let currentSize = self.currentSize {
|
||||
if currentContainerSize == containerSize && currentComponent == component {
|
||||
self.isUpdating = false
|
||||
return currentSize
|
||||
@ -197,9 +203,13 @@ public final class ComponentView<EnvironmentType> {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = strongSelf._update(transition: transition, component: component, maybeEnvironment: {
|
||||
preconditionFailure()
|
||||
} as () -> Environment<EnvironmentType>, updateEnvironment: false, forceUpdate: true, containerSize: containerSize)
|
||||
if let parentState = strongSelf.parentState {
|
||||
parentState.updated(transition: transition)
|
||||
} else {
|
||||
let _ = strongSelf._update(transition: transition, component: component, maybeEnvironment: {
|
||||
preconditionFailure()
|
||||
} as () -> Environment<EnvironmentType>, updateEnvironment: false, forceUpdate: true, containerSize: containerSize)
|
||||
}
|
||||
}
|
||||
|
||||
let updatedSize = component._update(view: componentView, availableSize: containerSize, environment: context.erasedEnvironment, transition: transition)
|
||||
@ -207,6 +217,9 @@ public final class ComponentView<EnvironmentType> {
|
||||
if isEnvironmentUpdated {
|
||||
context.erasedEnvironment._isUpdated = false
|
||||
}
|
||||
if isStateUpdated {
|
||||
context.erasedState.isUpdated = false
|
||||
}
|
||||
|
||||
self.isUpdating = false
|
||||
|
||||
|
@ -39,7 +39,7 @@ public func reactionStaticImage(context: AccountContext, animation: TelegramMedi
|
||||
}
|
||||
}
|
||||
|
||||
let fetchFrame = animationCacheFetchFile(context: context, userLocation: .other, userContentType: .emoji, resource: MediaResourceReference.standalone(resource: animation.resource), type: type, keyframeOnly: true, customColor: customColor)
|
||||
let fetchFrame = animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: MediaResourceReference.standalone(resource: animation.resource), type: type, keyframeOnly: true, customColor: customColor)
|
||||
|
||||
class AnimationCacheItemWriterImpl: AnimationCacheItemWriter {
|
||||
let queue: Queue
|
||||
|
@ -9,6 +9,7 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
public typealias Theme = SolidRoundedButtonTheme
|
||||
|
||||
public let title: String?
|
||||
public let label: String?
|
||||
public let icon: UIImage?
|
||||
public let theme: SolidRoundedButtonTheme
|
||||
public let font: SolidRoundedButtonFont
|
||||
@ -16,6 +17,7 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
public let height: CGFloat
|
||||
public let cornerRadius: CGFloat
|
||||
public let gloss: Bool
|
||||
public let isEnabled: Bool
|
||||
public let iconName: String?
|
||||
public let animationName: String?
|
||||
public let iconPosition: SolidRoundedButtonIconPosition
|
||||
@ -25,6 +27,7 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
|
||||
public init(
|
||||
title: String? = nil,
|
||||
label: String? = nil,
|
||||
icon: UIImage? = nil,
|
||||
theme: SolidRoundedButtonTheme,
|
||||
font: SolidRoundedButtonFont = .bold,
|
||||
@ -32,6 +35,7 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
height: CGFloat = 48.0,
|
||||
cornerRadius: CGFloat = 24.0,
|
||||
gloss: Bool = false,
|
||||
isEnabled: Bool = true,
|
||||
iconName: String? = nil,
|
||||
animationName: String? = nil,
|
||||
iconPosition: SolidRoundedButtonIconPosition = .left,
|
||||
@ -40,6 +44,7 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.title = title
|
||||
self.label = label
|
||||
self.icon = icon
|
||||
self.theme = theme
|
||||
self.font = font
|
||||
@ -47,6 +52,7 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
self.height = height
|
||||
self.cornerRadius = cornerRadius
|
||||
self.gloss = gloss
|
||||
self.isEnabled = isEnabled
|
||||
self.iconName = iconName
|
||||
self.animationName = animationName
|
||||
self.iconPosition = iconPosition
|
||||
@ -59,6 +65,9 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.label != rhs.label {
|
||||
return false
|
||||
}
|
||||
if lhs.icon !== rhs.icon {
|
||||
return false
|
||||
}
|
||||
@ -80,6 +89,9 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
if lhs.gloss != rhs.gloss {
|
||||
return false
|
||||
}
|
||||
if lhs.isEnabled != rhs.isEnabled {
|
||||
return false
|
||||
}
|
||||
if lhs.iconName != rhs.iconName {
|
||||
return false
|
||||
}
|
||||
@ -108,6 +120,7 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
if self.button == nil {
|
||||
let button = SolidRoundedButtonView(
|
||||
title: component.title,
|
||||
label: component.label,
|
||||
icon: component.icon,
|
||||
theme: component.theme,
|
||||
font: component.font,
|
||||
@ -127,12 +140,15 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
|
||||
if let button = self.button {
|
||||
button.title = component.title
|
||||
button.label = component.label
|
||||
button.iconPosition = component.iconPosition
|
||||
button.iconSpacing = component.iconSpacing
|
||||
button.icon = component.iconName.flatMap { UIImage(bundleImageName: $0) }
|
||||
button.animation = component.animationName
|
||||
button.gloss = component.gloss
|
||||
|
||||
button.isEnabled = component.isEnabled
|
||||
|
||||
button.updateTheme(component.theme)
|
||||
let height = button.updateLayout(width: availableSize.width, transition: .immediate)
|
||||
transition.setFrame(view: button, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height)), completion: nil)
|
||||
|
@ -250,6 +250,10 @@ private final class FetchManagerCategoryContext {
|
||||
userContentType = .image
|
||||
case .video:
|
||||
userContentType = .video
|
||||
case .audio:
|
||||
userContentType = .audio
|
||||
case .file:
|
||||
userContentType = .file
|
||||
default:
|
||||
userContentType = .other
|
||||
}
|
||||
@ -376,6 +380,10 @@ private final class FetchManagerCategoryContext {
|
||||
userContentType = .image
|
||||
case .video:
|
||||
userContentType = .video
|
||||
case .audio:
|
||||
userContentType = .audio
|
||||
case .file:
|
||||
userContentType = .file
|
||||
default:
|
||||
userContentType = .other
|
||||
}
|
||||
@ -407,6 +415,10 @@ private final class FetchManagerCategoryContext {
|
||||
userContentType = .image
|
||||
case .video:
|
||||
userContentType = .video
|
||||
case .audio:
|
||||
userContentType = .audio
|
||||
case .file:
|
||||
userContentType = .file
|
||||
default:
|
||||
userContentType = .other
|
||||
}
|
||||
|
@ -77,3 +77,11 @@
|
||||
+ (NSData *)_manuallyEncryptedMessage:(NSData *)preparedData messageId:(int64_t)messageId authKey:(MTDatacenterAuthKey *)authKey;
|
||||
|
||||
@end
|
||||
|
||||
//#define DIRSTAT_FAST_ONLY 0x1
|
||||
struct darwin_dirstat {
|
||||
off_t total_size;
|
||||
uint64_t descendants;
|
||||
};
|
||||
|
||||
int dirstat_np(const char *path, int flags, struct darwin_dirstat *ds, size_t dirstat_size);
|
||||
|
@ -561,7 +561,7 @@ public final class MediaBox {
|
||||
paths.partial,
|
||||
paths.partial + ".meta"
|
||||
])
|
||||
if let fileContext = MediaBoxFileContext(queue: self.dataQueue, manager: self.dataFileManager, path: paths.complete, partialPath: paths.partial, metaPath: paths.partial + ".meta") {
|
||||
if let fileContext = MediaBoxFileContext(queue: self.dataQueue, manager: self.dataFileManager, storageBox: self.storageBox, resourceId: id.stringRepresentation.data(using: .utf8)!, path: paths.complete, partialPath: paths.partial, metaPath: paths.partial + ".meta") {
|
||||
context = fileContext
|
||||
self.fileContexts[resourceId] = fileContext
|
||||
} else {
|
||||
@ -598,7 +598,7 @@ public final class MediaBox {
|
||||
return
|
||||
}
|
||||
|
||||
if let location = parameters?.location {
|
||||
if let parameters = parameters, let location = parameters.location {
|
||||
var messageNamespace: Int32 = 0
|
||||
var messageIdValue: Int32 = 0
|
||||
if let messageId = location.messageId {
|
||||
@ -606,7 +606,9 @@ public final class MediaBox {
|
||||
messageIdValue = messageId.id
|
||||
}
|
||||
|
||||
self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId.toInt64(), messageNamespace: UInt8(clamping: messageNamespace), messageId: messageIdValue), to: resource.id.stringRepresentation.data(using: .utf8)!)
|
||||
self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId.toInt64(), messageNamespace: UInt8(clamping: messageNamespace), messageId: messageIdValue), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters.contentType.rawValue)
|
||||
} else {
|
||||
self.storageBox.add(reference: StorageBox.Reference(peerId: 0, messageNamespace: 0, messageId: 0), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters?.contentType.rawValue ?? 0)
|
||||
}
|
||||
|
||||
guard let (fileContext, releaseContext) = self.fileContext(for: resource.id) else {
|
||||
@ -771,7 +773,7 @@ public final class MediaBox {
|
||||
self.dataQueue.async {
|
||||
let paths = self.storePathsForId(resource.id)
|
||||
|
||||
if let location = parameters?.location {
|
||||
if let parameters = parameters, let location = parameters.location {
|
||||
var messageNamespace: Int32 = 0
|
||||
var messageIdValue: Int32 = 0
|
||||
if let messageId = location.messageId {
|
||||
@ -779,7 +781,9 @@ public final class MediaBox {
|
||||
messageIdValue = messageId.id
|
||||
}
|
||||
|
||||
self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId.toInt64(), messageNamespace: UInt8(clamping: messageNamespace), messageId: messageIdValue), to: resource.id.stringRepresentation.data(using: .utf8)!)
|
||||
self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId.toInt64(), messageNamespace: UInt8(clamping: messageNamespace), messageId: messageIdValue), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters.contentType.rawValue)
|
||||
} else {
|
||||
self.storageBox.add(reference: StorageBox.Reference(peerId: 0, messageNamespace: 0, messageId: 0), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters?.contentType.rawValue ?? 0)
|
||||
}
|
||||
|
||||
if let _ = fileSize(paths.complete) {
|
||||
@ -1289,9 +1293,17 @@ public final class MediaBox {
|
||||
return
|
||||
}
|
||||
|
||||
storageBox.addEmptyReferencesIfNotReferenced(ids: results.map { name -> Data in
|
||||
return MediaBox.idForFileName(name: name).data(using: .utf8)!
|
||||
}, completion: { addedCount in
|
||||
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")
|
||||
}
|
||||
|
@ -460,6 +460,8 @@ private class MediaBoxPartialFileDataRequest {
|
||||
final class MediaBoxPartialFile {
|
||||
private let queue: Queue
|
||||
private let manager: MediaBoxFileManager
|
||||
private let storageBox: StorageBox
|
||||
private let resourceId: Data
|
||||
private let path: String
|
||||
private let metaPath: String
|
||||
private let completePath: String
|
||||
@ -476,9 +478,12 @@ final class MediaBoxPartialFile {
|
||||
private var currentFetch: (Promise<[(Range<Int64>, MediaBoxFetchPriority)]>, Disposable)?
|
||||
private var processedAtLeastOneFetch: Bool = false
|
||||
|
||||
init?(queue: Queue, manager: MediaBoxFileManager, path: String, metaPath: String, completePath: String, completed: @escaping (Int64) -> Void) {
|
||||
init?(queue: Queue, manager: MediaBoxFileManager, storageBox: StorageBox, resourceId: Data, path: String, metaPath: String, completePath: String, completed: @escaping (Int64) -> Void) {
|
||||
assert(queue.isCurrent())
|
||||
self.manager = manager
|
||||
self.storageBox = storageBox
|
||||
self.resourceId = resourceId
|
||||
|
||||
if let fd = manager.open(path: path, mode: .readwrite) {
|
||||
self.queue = queue
|
||||
self.path = path
|
||||
@ -504,6 +509,7 @@ final class MediaBoxPartialFile {
|
||||
} else {
|
||||
self.fileMap = MediaBoxFileMap()
|
||||
}
|
||||
self.storageBox.update(id: self.resourceId, size: self.fileMap.sum)
|
||||
self.missingRanges = MediaBoxFileMissingRanges()
|
||||
} else {
|
||||
return nil
|
||||
@ -586,6 +592,8 @@ final class MediaBoxPartialFile {
|
||||
}
|
||||
self.statusRequests.removeAll()
|
||||
|
||||
self.storageBox.update(id: self.resourceId, size: self.fileMap.sum)
|
||||
|
||||
self.completed(self.fileMap.sum)
|
||||
} else {
|
||||
assertionFailure()
|
||||
@ -629,6 +637,8 @@ final class MediaBoxPartialFile {
|
||||
}
|
||||
self.statusRequests.removeAll()
|
||||
|
||||
self.storageBox.update(id: self.resourceId, size: size)
|
||||
|
||||
self.completed(size)
|
||||
} else {
|
||||
assertionFailure()
|
||||
@ -675,6 +685,8 @@ final class MediaBoxPartialFile {
|
||||
self.fileMap.fill(range)
|
||||
self.fileMap.serialize(manager: self.manager, to: self.metaPath)
|
||||
|
||||
self.storageBox.update(id: self.resourceId, size: self.fileMap.sum)
|
||||
|
||||
self.checkDataRequestsAfterFill(range: range)
|
||||
}
|
||||
|
||||
@ -1184,7 +1196,7 @@ final class MediaBoxFileContext {
|
||||
return self.references.isEmpty
|
||||
}
|
||||
|
||||
init?(queue: Queue, manager: MediaBoxFileManager, path: String, partialPath: String, metaPath: String) {
|
||||
init?(queue: Queue, manager: MediaBoxFileManager, storageBox: StorageBox, resourceId: Data, path: String, partialPath: String, metaPath: String) {
|
||||
assert(queue.isCurrent())
|
||||
|
||||
self.queue = queue
|
||||
@ -1195,7 +1207,7 @@ final class MediaBoxFileContext {
|
||||
var completeImpl: ((Int64) -> Void)?
|
||||
if let size = fileSize(path) {
|
||||
self.content = .complete(path, size)
|
||||
} else if let file = MediaBoxPartialFile(queue: queue, manager: manager, path: partialPath, metaPath: metaPath, completePath: path, completed: { size in
|
||||
} else if let file = MediaBoxPartialFile(queue: queue, manager: manager, storageBox: storageBox, resourceId: resourceId, path: partialPath, metaPath: metaPath, completePath: path, completed: { size in
|
||||
completeImpl?(size)
|
||||
}) {
|
||||
self.content = .partial(file)
|
||||
|
@ -50,14 +50,14 @@ public final class MediaResourceStorageLocation {
|
||||
}
|
||||
|
||||
public enum MediaResourceUserContentType: UInt8, Equatable {
|
||||
case image = 0
|
||||
case video = 1
|
||||
case audio = 2
|
||||
case file = 3
|
||||
case gif = 4
|
||||
case emoji = 5
|
||||
case other = 0
|
||||
case image = 1
|
||||
case video = 2
|
||||
case audio = 3
|
||||
case file = 4
|
||||
case gif = 5
|
||||
case sticker = 6
|
||||
case other = 7
|
||||
case avatar = 7
|
||||
}
|
||||
|
||||
public struct MediaResourceFetchParameters {
|
||||
|
@ -19,6 +19,14 @@ private func md5Hash(_ data: Data) -> HashId {
|
||||
}
|
||||
|
||||
public final class StorageBox {
|
||||
public struct Stats {
|
||||
public var contentTypes: [UInt8: Int64]
|
||||
|
||||
public init(contentTypes: [UInt8: Int64]) {
|
||||
self.contentTypes = contentTypes
|
||||
}
|
||||
}
|
||||
|
||||
public struct Reference {
|
||||
public var peerId: Int64
|
||||
public var messageNamespace: UInt8
|
||||
@ -53,15 +61,78 @@ public final class StorageBox {
|
||||
}
|
||||
}
|
||||
|
||||
private struct ItemInfo {
|
||||
var id: Data
|
||||
var contentType: UInt8
|
||||
var size: Int64
|
||||
|
||||
init(id: Data, contentType: UInt8, size: Int64) {
|
||||
self.id = id
|
||||
self.contentType = contentType
|
||||
self.size = size
|
||||
}
|
||||
|
||||
init(buffer: MemoryBuffer) {
|
||||
var id = Data()
|
||||
var contentType: UInt8 = 0
|
||||
var size: Int64 = 0
|
||||
|
||||
withExtendedLifetime(buffer, {
|
||||
let readBuffer = ReadBuffer(memoryBufferNoCopy: buffer)
|
||||
var version: UInt8 = 0
|
||||
readBuffer.read(&version, offset: 0, length: 1)
|
||||
let _ = version
|
||||
|
||||
var idLength: UInt16 = 0
|
||||
readBuffer.read(&idLength, offset: 0, length: 2)
|
||||
id.count = Int(idLength)
|
||||
id.withUnsafeMutableBytes { buffer -> Void in
|
||||
readBuffer.read(buffer.baseAddress!, offset: 0, length: buffer.count)
|
||||
}
|
||||
|
||||
readBuffer.read(&contentType, offset: 0, length: 1)
|
||||
|
||||
readBuffer.read(&size, offset: 0, length: 8)
|
||||
})
|
||||
|
||||
self.id = id
|
||||
self.contentType = contentType
|
||||
self.size = size
|
||||
}
|
||||
|
||||
func serialize() -> MemoryBuffer {
|
||||
let writeBuffer = WriteBuffer()
|
||||
|
||||
var version: UInt8 = 0
|
||||
writeBuffer.write(&version, length: 1)
|
||||
|
||||
var idLength = UInt16(clamping: self.id.count)
|
||||
writeBuffer.write(&idLength, length: 2)
|
||||
self.id.withUnsafeBytes { buffer in
|
||||
writeBuffer.write(buffer.baseAddress!, length: Int(idLength))
|
||||
}
|
||||
|
||||
var contentType = self.contentType
|
||||
writeBuffer.write(&contentType, length: 1)
|
||||
|
||||
var size = self.size
|
||||
writeBuffer.write(&size, length: 8)
|
||||
|
||||
return writeBuffer
|
||||
}
|
||||
}
|
||||
|
||||
private final class Impl {
|
||||
let queue: Queue
|
||||
let logger: StorageBox.Logger
|
||||
let basePath: String
|
||||
let valueBox: SqliteValueBox
|
||||
let hashIdToIdTable: ValueBoxTable
|
||||
let hashIdToInfoTable: ValueBoxTable
|
||||
let idToReferenceTable: ValueBoxTable
|
||||
let peerIdToIdTable: ValueBoxTable
|
||||
let peerIdTable: ValueBoxTable
|
||||
let peerContentTypeStatsTable: ValueBoxTable
|
||||
let contentTypeStatsTable: ValueBoxTable
|
||||
|
||||
init(queue: Queue, logger: StorageBox.Logger, basePath: String) {
|
||||
self.queue = queue
|
||||
@ -80,20 +151,81 @@ public final class StorageBox {
|
||||
}
|
||||
self.valueBox = valueBox
|
||||
|
||||
self.hashIdToIdTable = ValueBoxTable(id: 5, keyType: .binary, compactValuesOnCreation: true)
|
||||
self.idToReferenceTable = ValueBoxTable(id: 6, keyType: .binary, compactValuesOnCreation: true)
|
||||
self.peerIdToIdTable = ValueBoxTable(id: 7, keyType: .binary, compactValuesOnCreation: true)
|
||||
self.peerIdTable = ValueBoxTable(id: 8, keyType: .binary, compactValuesOnCreation: true)
|
||||
self.hashIdToInfoTable = ValueBoxTable(id: 15, keyType: .binary, compactValuesOnCreation: true)
|
||||
self.idToReferenceTable = ValueBoxTable(id: 16, keyType: .binary, compactValuesOnCreation: true)
|
||||
self.peerIdToIdTable = ValueBoxTable(id: 17, keyType: .binary, compactValuesOnCreation: true)
|
||||
self.peerIdTable = ValueBoxTable(id: 18, keyType: .binary, compactValuesOnCreation: true)
|
||||
self.peerContentTypeStatsTable = ValueBoxTable(id: 19, keyType: .binary, compactValuesOnCreation: true)
|
||||
self.contentTypeStatsTable = ValueBoxTable(id: 20, keyType: .binary, compactValuesOnCreation: true)
|
||||
}
|
||||
|
||||
func add(reference: Reference, to id: Data) {
|
||||
private func internalAddSize(contentType: UInt8, delta: Int64) {
|
||||
let key = ValueBoxKey(length: 1)
|
||||
key.setUInt8(0, value: contentType)
|
||||
|
||||
var currentSize: Int64 = 0
|
||||
if let value = self.valueBox.get(self.contentTypeStatsTable, key: key) {
|
||||
value.read(¤tSize, offset: 0, length: 8)
|
||||
}
|
||||
|
||||
currentSize += delta
|
||||
|
||||
if currentSize < 0 {
|
||||
assertionFailure()
|
||||
currentSize = 0
|
||||
}
|
||||
|
||||
self.valueBox.set(self.contentTypeStatsTable, key: key, value: MemoryBuffer(memory: ¤tSize, capacity: 8, length: 8, freeWhenDone: false))
|
||||
}
|
||||
|
||||
private func internalAddSize(peerId: Int64, contentType: UInt8, delta: Int64) {
|
||||
let key = ValueBoxKey(length: 8 + 1)
|
||||
key.setInt64(0, value: peerId)
|
||||
key.setUInt8(8, value: contentType)
|
||||
|
||||
var currentSize: Int64 = 0
|
||||
if let value = self.valueBox.get(self.contentTypeStatsTable, key: key) {
|
||||
value.read(¤tSize, offset: 0, length: 8)
|
||||
}
|
||||
|
||||
currentSize += delta
|
||||
|
||||
if currentSize < 0 {
|
||||
assertionFailure()
|
||||
currentSize = 0
|
||||
}
|
||||
|
||||
self.valueBox.set(self.contentTypeStatsTable, key: key, value: MemoryBuffer(memory: ¤tSize, capacity: 8, length: 8, freeWhenDone: false))
|
||||
}
|
||||
|
||||
func add(reference: Reference, to id: Data, contentType: UInt8) {
|
||||
self.valueBox.begin()
|
||||
|
||||
let hashId = md5Hash(id)
|
||||
|
||||
let mainKey = ValueBoxKey(length: 16)
|
||||
mainKey.setData(0, value: hashId.data)
|
||||
self.valueBox.setOrIgnore(self.hashIdToIdTable, key: mainKey, value: MemoryBuffer(data: id))
|
||||
|
||||
var previousContentType: UInt8?
|
||||
var size: Int64 = 0
|
||||
if let currentInfoValue = self.valueBox.get(self.hashIdToInfoTable, key: mainKey) {
|
||||
var info = ItemInfo(buffer: currentInfoValue)
|
||||
if info.contentType != contentType {
|
||||
previousContentType = info.contentType
|
||||
}
|
||||
size = info.size
|
||||
info.contentType = contentType
|
||||
self.valueBox.set(self.hashIdToInfoTable, key: mainKey, value: info.serialize())
|
||||
} else {
|
||||
self.valueBox.set(self.hashIdToInfoTable, key: mainKey, value: ItemInfo(id: id, contentType: contentType, size: 0).serialize())
|
||||
}
|
||||
|
||||
if let previousContentType = previousContentType, previousContentType != contentType {
|
||||
if size != 0 {
|
||||
self.internalAddSize(contentType: previousContentType, delta: -size)
|
||||
self.internalAddSize(contentType: contentType, delta: size)
|
||||
}
|
||||
}
|
||||
|
||||
let idKey = ValueBoxKey(length: hashId.data.count + 8 + 1 + 4)
|
||||
idKey.setData(0, value: hashId.data)
|
||||
@ -145,25 +277,62 @@ public final class StorageBox {
|
||||
self.valueBox.commit()
|
||||
}
|
||||
|
||||
func addEmptyReferencesIfNotReferenced(ids: [Data]) -> Int {
|
||||
func update(id: Data, size: Int64) {
|
||||
self.valueBox.begin()
|
||||
|
||||
let hashId = md5Hash(id)
|
||||
|
||||
let mainKey = ValueBoxKey(length: 16)
|
||||
mainKey.setData(0, value: hashId.data)
|
||||
|
||||
if let currentInfoValue = self.valueBox.get(self.hashIdToInfoTable, key: mainKey) {
|
||||
var info = ItemInfo(buffer: currentInfoValue)
|
||||
|
||||
var sizeDelta: Int64 = 0
|
||||
if info.size != size {
|
||||
sizeDelta = size - info.size
|
||||
info.size = size
|
||||
|
||||
self.valueBox.set(self.hashIdToInfoTable, key: mainKey, value: info.serialize())
|
||||
}
|
||||
|
||||
if sizeDelta != 0 {
|
||||
self.internalAddSize(contentType: info.contentType, delta: sizeDelta)
|
||||
}
|
||||
|
||||
var peerIds: [Int64] = []
|
||||
self.valueBox.range(self.idToReferenceTable, start: mainKey, end: mainKey.successor, keys: { key in
|
||||
peerIds.append(key.getInt64(0))
|
||||
return true
|
||||
}, limit: 0)
|
||||
|
||||
for peerId in peerIds {
|
||||
let _ = peerId
|
||||
}
|
||||
}
|
||||
|
||||
self.valueBox.commit()
|
||||
}
|
||||
|
||||
func addEmptyReferencesIfNotReferenced(ids: [(id: Data, size: Int64)], contentType: UInt8) -> Int {
|
||||
self.valueBox.begin()
|
||||
|
||||
var addedCount = 0
|
||||
|
||||
for id in ids {
|
||||
for (id, size) in ids {
|
||||
let reference = Reference(peerId: 0, messageNamespace: 0, messageId: 0)
|
||||
|
||||
let hashId = md5Hash(id)
|
||||
|
||||
let mainKey = ValueBoxKey(length: 16)
|
||||
mainKey.setData(0, value: hashId.data)
|
||||
if self.valueBox.exists(self.hashIdToIdTable, key: mainKey) {
|
||||
if self.valueBox.exists(self.hashIdToInfoTable, key: mainKey) {
|
||||
continue
|
||||
}
|
||||
|
||||
addedCount += 1
|
||||
|
||||
self.valueBox.setOrIgnore(self.hashIdToIdTable, key: mainKey, value: MemoryBuffer(data: id))
|
||||
self.valueBox.set(self.hashIdToInfoTable, key: mainKey, value: ItemInfo(id: id, contentType: contentType, size: size).serialize())
|
||||
|
||||
let idKey = ValueBoxKey(length: hashId.data.count + 8 + 1 + 4)
|
||||
idKey.setData(0, value: hashId.data)
|
||||
@ -229,7 +398,15 @@ public final class StorageBox {
|
||||
let hashId = md5Hash(id)
|
||||
mainKey.setData(0, value: hashId.data)
|
||||
|
||||
self.valueBox.remove(self.hashIdToIdTable, key: mainKey, secure: false)
|
||||
guard let infoValue = self.valueBox.get(self.hashIdToInfoTable, key: mainKey) else {
|
||||
continue
|
||||
}
|
||||
let info = ItemInfo(buffer: infoValue)
|
||||
self.valueBox.remove(self.hashIdToInfoTable, key: mainKey, secure: false)
|
||||
|
||||
if info.size != 0 {
|
||||
self.internalAddSize(contentType: info.contentType, delta: -info.size)
|
||||
}
|
||||
|
||||
var referenceKeys: [ValueBoxKey] = []
|
||||
self.valueBox.range(self.idToReferenceTable, start: mainKey, end: mainKey.successor, keys: { key in
|
||||
@ -302,8 +479,9 @@ public final class StorageBox {
|
||||
let mainKey = ValueBoxKey(length: 16)
|
||||
for hashId in hashIds {
|
||||
mainKey.setData(0, value: hashId)
|
||||
if let value = self.valueBox.get(self.hashIdToIdTable, key: mainKey) {
|
||||
result.append(value.makeData())
|
||||
if let infoValue = self.valueBox.get(self.hashIdToInfoTable, key: mainKey) {
|
||||
let info = ItemInfo(buffer: infoValue)
|
||||
result.append(info.id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -336,8 +514,9 @@ public final class StorageBox {
|
||||
} else {
|
||||
if let currentId = currentId, !currentReferences.isEmpty {
|
||||
mainKey.setData(0, value: currentId)
|
||||
if let value = self.valueBox.get(self.hashIdToIdTable, key: mainKey) {
|
||||
result.append(StorageBox.Entry(id: value.makeData(), references: currentReferences))
|
||||
if let infoValue = self.valueBox.get(self.hashIdToInfoTable, key: mainKey) {
|
||||
let info = ItemInfo(buffer: infoValue)
|
||||
result.append(StorageBox.Entry(id: info.id, references: currentReferences))
|
||||
}
|
||||
currentReferences.removeAll(keepingCapacity: true)
|
||||
}
|
||||
@ -384,6 +563,20 @@ public final class StorageBox {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func getStats() -> Stats {
|
||||
var contentTypes: [UInt8: Int64] = [:]
|
||||
|
||||
self.valueBox.scan(self.contentTypeStatsTable, values: { key, value in
|
||||
var size: Int64 = 0
|
||||
value.read(&size, offset: 0, length: 8)
|
||||
contentTypes[key.getUInt8(0)] = size
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return Stats(contentTypes: contentTypes)
|
||||
}
|
||||
}
|
||||
|
||||
private let queue: Queue
|
||||
@ -396,15 +589,21 @@ public final class StorageBox {
|
||||
})
|
||||
}
|
||||
|
||||
public func add(reference: Reference, to id: Data) {
|
||||
public func add(reference: Reference, to id: Data, contentType: UInt8) {
|
||||
self.impl.with { impl in
|
||||
impl.add(reference: reference, to: id)
|
||||
impl.add(reference: reference, to: id, contentType: contentType)
|
||||
}
|
||||
}
|
||||
|
||||
public func addEmptyReferencesIfNotReferenced(ids: [Data], completion: @escaping (Int) -> Void) {
|
||||
public func update(id: Data, size: Int64) {
|
||||
self.impl.with { impl in
|
||||
let addedCount = impl.addEmptyReferencesIfNotReferenced(ids: ids)
|
||||
impl.update(id: id, size: size)
|
||||
}
|
||||
}
|
||||
|
||||
public func addEmptyReferencesIfNotReferenced(ids: [(id: Data, size: Int64)], contentType: UInt8, completion: @escaping (Int) -> Void) {
|
||||
self.impl.with { impl in
|
||||
let addedCount = impl.addEmptyReferencesIfNotReferenced(ids: ids, contentType: contentType)
|
||||
|
||||
completion(addedCount)
|
||||
}
|
||||
@ -451,4 +650,13 @@ public final class StorageBox {
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public func getStats() -> Signal<Stats, NoError> {
|
||||
return self.impl.signalWith { impl, subscriber in
|
||||
subscriber.putNext(impl.getStats())
|
||||
subscriber.putCompletion()
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,6 +109,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/NotificationPeerExceptionController",
|
||||
"//submodules/TelegramUI/Components/ChatTimerScreen",
|
||||
"//submodules/AnimatedAvatarSetNode",
|
||||
"//submodules/TelegramUI/Components/StorageUsageScreen",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -12,6 +12,7 @@ import PresentationDataUtils
|
||||
import AccountContext
|
||||
import OpenInExternalAppUI
|
||||
import ItemListPeerActionItem
|
||||
import StorageUsageScreen
|
||||
|
||||
private final class DataAndStorageControllerArguments {
|
||||
let openStorageUsage: () -> Void
|
||||
@ -594,7 +595,10 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
|
||||
})
|
||||
|
||||
let arguments = DataAndStorageControllerArguments(openStorageUsage: {
|
||||
pushControllerImpl?(storageUsageController(context: context, cacheUsagePromise: cacheUsagePromise))
|
||||
pushControllerImpl?(StorageUsageScreen(context: context, makeStorageUsageExceptionsScreen: { category in
|
||||
return storageUsageExceptionsScreen(context: context, category: category)
|
||||
}))
|
||||
//pushControllerImpl?(storageUsageController(context: context, cacheUsagePromise: cacheUsagePromise))
|
||||
}, openNetworkUsage: {
|
||||
pushControllerImpl?(networkUsageStatsController(context: context))
|
||||
}, openProxy: {
|
||||
|
@ -765,6 +765,14 @@ public final class SolidRoundedButtonView: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
public var label: String? {
|
||||
didSet {
|
||||
if let width = self.validLayout {
|
||||
_ = self.updateLayout(width: width, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var subtitle: String? {
|
||||
didSet {
|
||||
if let width = self.validLayout {
|
||||
@ -779,6 +787,14 @@ public final class SolidRoundedButtonView: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
public var isEnabled: Bool = true {
|
||||
didSet {
|
||||
if self.isEnabled != oldValue {
|
||||
self.titleNode.alpha = self.isEnabled ? 1.0 : 0.6
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var animationTimer: SwiftSignalKit.Timer?
|
||||
public var animation: String? {
|
||||
didSet {
|
||||
@ -854,13 +870,14 @@ public final class SolidRoundedButtonView: UIView {
|
||||
|
||||
public var progressType: SolidRoundedButtonProgressType = .fullSize
|
||||
|
||||
public init(title: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) {
|
||||
public init(title: String? = nil, label: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) {
|
||||
self.theme = theme
|
||||
self.font = font
|
||||
self.fontSize = fontSize
|
||||
self.buttonHeight = height
|
||||
self.buttonCornerRadius = cornerRadius
|
||||
self.title = title
|
||||
self.label = label
|
||||
self.gloss = gloss
|
||||
|
||||
self.buttonBackgroundNode = UIImageView()
|
||||
@ -1174,7 +1191,13 @@ public final class SolidRoundedButtonView: UIView {
|
||||
self.buttonBackgroundAnimationView?.image = nil
|
||||
}
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor)
|
||||
let titleText = NSMutableAttributedString()
|
||||
titleText.append(NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor))
|
||||
if let label = self.label {
|
||||
titleText.append(NSAttributedString(string: " " + label, font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor.withAlphaComponent(0.6)))
|
||||
}
|
||||
|
||||
self.titleNode.attributedText = titleText
|
||||
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: theme.foregroundColor)
|
||||
|
||||
self.iconNode.image = generateTintedImage(image: self.iconNode.image, color: theme.foregroundColor)
|
||||
@ -1219,10 +1242,14 @@ public final class SolidRoundedButtonView: UIView {
|
||||
|
||||
transition.updateFrame(view: self.buttonNode, frame: buttonFrame)
|
||||
|
||||
if self.title != self.titleNode.attributedText?.string {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: self.theme.foregroundColor)
|
||||
let titleText = NSMutableAttributedString()
|
||||
titleText.append(NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor))
|
||||
if let label = self.label {
|
||||
titleText.append(NSAttributedString(string: " " + label, font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor.withAlphaComponent(0.6)))
|
||||
}
|
||||
|
||||
self.titleNode.attributedText = titleText
|
||||
|
||||
let iconSize: CGSize
|
||||
if let _ = self.animationNode {
|
||||
iconSize = CGSize(width: 30.0, height: 30.0)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
import MtProtoKit
|
||||
|
||||
public enum PeerCacheUsageCategory: Int32 {
|
||||
case image = 0
|
||||
@ -52,6 +52,130 @@ private final class CacheUsageStatsState {
|
||||
var upperBound: MessageIndex?
|
||||
}
|
||||
|
||||
public struct StorageUsageStats: Equatable {
|
||||
public enum CategoryKey: Hashable {
|
||||
case photos
|
||||
case videos
|
||||
case files
|
||||
case music
|
||||
case stickers
|
||||
case avatars
|
||||
case misc
|
||||
}
|
||||
|
||||
public struct CategoryData: Equatable {
|
||||
public var size: Int64
|
||||
|
||||
public init(size: Int64) {
|
||||
self.size = size
|
||||
}
|
||||
}
|
||||
|
||||
public var categories: [CategoryKey: CategoryData]
|
||||
|
||||
public init(categories: [CategoryKey: CategoryData]) {
|
||||
self.categories = categories
|
||||
}
|
||||
}
|
||||
|
||||
public struct AllStorageUsageStats: Equatable {
|
||||
public struct PeerStats: Equatable {
|
||||
public var peer: EnginePeer
|
||||
public var stats: StorageUsageStats
|
||||
|
||||
public init(peer: EnginePeer, stats: StorageUsageStats) {
|
||||
self.peer = peer
|
||||
self.stats = stats
|
||||
}
|
||||
}
|
||||
|
||||
public var totalStats: StorageUsageStats
|
||||
public var peers: [EnginePeer.Id: PeerStats]
|
||||
|
||||
public init(totalStats: StorageUsageStats, peers: [EnginePeer.Id: PeerStats]) {
|
||||
self.totalStats = totalStats
|
||||
self.peers = peers
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_collectStorageUsageStats(account: Account) -> Signal<AllStorageUsageStats, NoError> {
|
||||
let additionalStats = Signal<Int64, NoError> { subscriber in
|
||||
DispatchQueue.global().async {
|
||||
var totalSize: Int64 = 0
|
||||
|
||||
let additionalPaths: [String] = [
|
||||
"cache",
|
||||
"animation-cache",
|
||||
"short-cache",
|
||||
]
|
||||
|
||||
for path in additionalPaths {
|
||||
let fullPath: String
|
||||
if path.isEmpty {
|
||||
fullPath = account.postbox.mediaBox.basePath
|
||||
} else {
|
||||
fullPath = account.postbox.mediaBox.basePath + "/\(path)"
|
||||
}
|
||||
|
||||
var s = darwin_dirstat()
|
||||
var result = dirstat_np(fullPath, 1, &s, MemoryLayout<darwin_dirstat>.size)
|
||||
if result != -1 {
|
||||
totalSize += Int64(s.total_size)
|
||||
} else {
|
||||
result = dirstat_np(fullPath, 0, &s, MemoryLayout<darwin_dirstat>.size)
|
||||
if result != -1 {
|
||||
totalSize += Int64(s.total_size)
|
||||
print(s.descendants)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subscriber.putNext(totalSize)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
|
||||
return combineLatest(
|
||||
additionalStats,
|
||||
account.postbox.mediaBox.storageBox.getStats()
|
||||
)
|
||||
|> deliverOnMainQueue
|
||||
|> mapToSignal { additionalStats, allStats -> Signal<AllStorageUsageStats, NoError> in
|
||||
var mappedCategories: [StorageUsageStats.CategoryKey: StorageUsageStats.CategoryData] = [:]
|
||||
for (key, value) in allStats.contentTypes {
|
||||
let mappedCategory: StorageUsageStats.CategoryKey
|
||||
switch key {
|
||||
case MediaResourceUserContentType.image.rawValue:
|
||||
mappedCategory = .photos
|
||||
case MediaResourceUserContentType.video.rawValue:
|
||||
mappedCategory = .videos
|
||||
case MediaResourceUserContentType.file.rawValue:
|
||||
mappedCategory = .files
|
||||
case MediaResourceUserContentType.audio.rawValue:
|
||||
mappedCategory = .music
|
||||
case MediaResourceUserContentType.avatar.rawValue:
|
||||
mappedCategory = .avatars
|
||||
case MediaResourceUserContentType.sticker.rawValue:
|
||||
mappedCategory = .stickers
|
||||
default:
|
||||
mappedCategory = .misc
|
||||
}
|
||||
mappedCategories[mappedCategory] = StorageUsageStats.CategoryData(size: value)
|
||||
}
|
||||
|
||||
if additionalStats != 0 {
|
||||
mappedCategories[.misc, default: StorageUsageStats.CategoryData(size: 0)].size += additionalStats
|
||||
}
|
||||
|
||||
return .single(AllStorageUsageStats(
|
||||
totalStats: StorageUsageStats(categories: mappedCategories),
|
||||
peers: [:]
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_collectCacheUsageStats(account: Account, peerId: PeerId? = nil, additionalCachePaths: [String] = [], logFilesPath: String? = nil) -> Signal<CacheUsageStatsResult, NoError> {
|
||||
return account.postbox.mediaBox.storageBox.all()
|
||||
|> mapToSignal { entries -> Signal<CacheUsageStatsResult, NoError> in
|
||||
|
@ -8,10 +8,12 @@ public typealias EngineTempBoxFile = TempBoxFile
|
||||
|
||||
public extension MediaResourceUserContentType {
|
||||
init(file: TelegramMediaFile) {
|
||||
if file.isSticker || file.isAnimatedSticker {
|
||||
if file.isMusic || file.isVoice {
|
||||
self = .audio
|
||||
} else if file.isSticker || file.isAnimatedSticker {
|
||||
self = .sticker
|
||||
} else if file.isCustomEmoji {
|
||||
self = .emoji
|
||||
self = .sticker
|
||||
} else if file.isVideo {
|
||||
if file.isAnimated {
|
||||
self = .gif
|
||||
@ -220,6 +222,10 @@ public extension TelegramEngine {
|
||||
public func collectCacheUsageStats(peerId: PeerId? = nil, additionalCachePaths: [String] = [], logFilesPath: String? = nil) -> Signal<CacheUsageStatsResult, NoError> {
|
||||
return _internal_collectCacheUsageStats(account: self.account, peerId: peerId, additionalCachePaths: additionalCachePaths, logFilesPath: logFilesPath)
|
||||
}
|
||||
|
||||
public func collectStorageUsageStats() -> Signal<AllStorageUsageStats, NoError> {
|
||||
return _internal_collectStorageUsageStats(account: self.account)
|
||||
}
|
||||
|
||||
public func clearCachedMediaResources(mediaResourceIds: Set<MediaResourceId>) -> Signal<Float, NoError> {
|
||||
return _internal_clearCachedMediaResources(account: self.account, mediaResourceIds: mediaResourceIds)
|
||||
|
37
submodules/TelegramUI/Components/StorageUsageScreen/BUILD
Normal file
37
submodules/TelegramUI/Components/StorageUsageScreen/BUILD
Normal file
@ -0,0 +1,37 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "StorageUsageScreen",
|
||||
module_name = "StorageUsageScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent:ViewControllerComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters",
|
||||
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
|
||||
"//submodules/Components/SolidRoundedButtonComponent",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
"//submodules/CheckNode",
|
||||
"//submodules/Markdown",
|
||||
"//submodules/ContextUI",
|
||||
"//submodules/AnimatedAvatarSetNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,226 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import ViewControllerComponent
|
||||
import ComponentDisplayAdapters
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import MultilineTextComponent
|
||||
import EmojiStatusComponent
|
||||
import Postbox
|
||||
|
||||
final class PieChartComponent: Component {
|
||||
struct ChartData: Equatable {
|
||||
struct Item: Equatable {
|
||||
var id: AnyHashable
|
||||
var value: Double
|
||||
var color: UIColor
|
||||
|
||||
init(id: AnyHashable, value: Double, color: UIColor) {
|
||||
self.id = id
|
||||
self.value = value
|
||||
self.color = color
|
||||
}
|
||||
}
|
||||
|
||||
var items: [Item]
|
||||
|
||||
init(items: [Item]) {
|
||||
self.items = items
|
||||
}
|
||||
}
|
||||
|
||||
let theme: PresentationTheme
|
||||
let chartData: ChartData
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
chartData: ChartData
|
||||
) {
|
||||
self.theme = theme
|
||||
self.chartData = chartData
|
||||
}
|
||||
|
||||
static func ==(lhs: PieChartComponent, rhs: PieChartComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.chartData != rhs.chartData {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
class View: UIView {
|
||||
private var shapeLayers: [AnyHashable: SimpleShapeLayer] = [:]
|
||||
private var labels: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
var selectedKey: AnyHashable?
|
||||
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
let point = recognizer.location(in: self)
|
||||
for (key, layer) in self.shapeLayers {
|
||||
if layer.frame.contains(point), let path = layer.path {
|
||||
if path.contains(self.layer.convert(point, to: layer)) {
|
||||
if self.selectedKey == key {
|
||||
self.selectedKey = nil
|
||||
} else {
|
||||
self.selectedKey = key
|
||||
}
|
||||
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: PieChartComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.state = state
|
||||
|
||||
let innerDiameter: CGFloat = 100.0
|
||||
let spacing: CGFloat = 2.0
|
||||
let innerAngleSpacing: CGFloat = spacing / (innerDiameter * 0.5)
|
||||
|
||||
var valueSum: Double = 0.0
|
||||
for item in component.chartData.items {
|
||||
valueSum += item.value
|
||||
}
|
||||
|
||||
let diameter: CGFloat = 200.0
|
||||
let reducedDiameter: CGFloat = 170.0
|
||||
|
||||
var startAngle: CGFloat = 0.0
|
||||
for i in 0 ..< component.chartData.items.count {
|
||||
let item = component.chartData.items[i]
|
||||
|
||||
let itemOuterDiameter: CGFloat
|
||||
if let selectedKey = self.selectedKey {
|
||||
if selectedKey == item.id {
|
||||
itemOuterDiameter = diameter
|
||||
} else {
|
||||
itemOuterDiameter = reducedDiameter
|
||||
}
|
||||
} else {
|
||||
itemOuterDiameter = diameter
|
||||
}
|
||||
|
||||
let shapeLayerFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - diameter) / 2.0), y: 0.0), size: CGSize(width: diameter, height: diameter))
|
||||
|
||||
let angleSpacing: CGFloat = spacing / (itemOuterDiameter * 0.5)
|
||||
|
||||
let shapeLayer: SimpleShapeLayer
|
||||
if let current = self.shapeLayers[item.id] {
|
||||
shapeLayer = current
|
||||
} else {
|
||||
shapeLayer = SimpleShapeLayer()
|
||||
self.shapeLayers[item.id] = shapeLayer
|
||||
self.layer.insertSublayer(shapeLayer, at: 0)
|
||||
}
|
||||
|
||||
transition.setFrame(layer: shapeLayer, frame: shapeLayerFrame)
|
||||
|
||||
let angleValue: CGFloat = item.value / valueSum * CGFloat.pi * 2.0
|
||||
|
||||
let innerStartAngle = startAngle + innerAngleSpacing * 0.5
|
||||
var innerEndAngle = startAngle + angleValue - innerAngleSpacing * 0.5
|
||||
innerEndAngle = max(innerEndAngle, innerStartAngle)
|
||||
|
||||
let outerStartAngle = startAngle + angleSpacing * 0.5
|
||||
var outerEndAngle = startAngle + angleValue - angleSpacing * 0.5
|
||||
outerEndAngle = max(outerEndAngle, outerStartAngle)
|
||||
|
||||
let path = CGMutablePath()
|
||||
|
||||
path.addArc(center: CGPoint(x: diameter * 0.5, y: diameter * 0.5), radius: innerDiameter * 0.5, startAngle: innerEndAngle, endAngle: innerStartAngle, clockwise: true)
|
||||
path.addArc(center: CGPoint(x: diameter * 0.5, y: diameter * 0.5), radius: itemOuterDiameter * 0.5, startAngle: outerStartAngle, endAngle: outerEndAngle, clockwise: false)
|
||||
|
||||
transition.setShapeLayerPath(layer: shapeLayer, path: path)
|
||||
|
||||
startAngle += angleValue
|
||||
shapeLayer.fillColor = item.color.cgColor
|
||||
|
||||
let fractionValue: Double = floor(item.value * 100.0 * 10.0) / 10.0
|
||||
let fractionString: String
|
||||
if abs(Double(Int(fractionValue)) - fractionValue) < 0.001 {
|
||||
fractionString = "\(Int(fractionValue))"
|
||||
} else {
|
||||
fractionString = "\(fractionValue)"
|
||||
}
|
||||
|
||||
let label: ComponentView<Empty>
|
||||
if let current = self.labels[item.id] {
|
||||
label = current
|
||||
} else {
|
||||
label = ComponentView<Empty>()
|
||||
self.labels[item.id] = label
|
||||
}
|
||||
let labelSize = label.update(transition: .immediate, component: AnyComponent(Text(text: "\(fractionString)%", font: Font.with(size: 15.0, design: .round, weight: .medium), color: component.theme.list.itemCheckColors.foregroundColor)), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0))
|
||||
|
||||
let midAngle: CGFloat = (innerStartAngle + innerEndAngle) * 0.5
|
||||
let centerDistance: CGFloat = (innerDiameter * 0.5 + (diameter - innerDiameter) * 0.25)
|
||||
let labelCenter = CGPoint(
|
||||
x: shapeLayerFrame.midX + cos(midAngle) * centerDistance,
|
||||
y: shapeLayerFrame.midY + sin(midAngle) * centerDistance
|
||||
)
|
||||
let labelFrame = CGRect(origin: CGPoint(x: labelCenter.x - labelSize.width * 0.5, y: labelCenter.y - labelSize.height * 0.5), size: labelSize)
|
||||
|
||||
//x2 + y2 = r2
|
||||
//x = sqrt(r2 - y2)
|
||||
//y = sqrt(r2 - x2)
|
||||
|
||||
/*let localLabelRect = labelFrame.offsetBy(dx: -shapeLayerFrame.midX, dy: -shapeLayerFrame.midY)
|
||||
let outerIntersectionX1 = sqrt(pow(diameter * 0.5, 2.0) - pow(localLabelRect.minY, 2.0))
|
||||
let outerIntersectionX2 = sqrt(pow(diameter * 0.5, 2.0) - pow(localLabelRect.maxY, 2.0))
|
||||
let outerIntersectionY1 = sqrt(pow(diameter * 0.5, 2.0) - pow(localLabelRect.minX, 2.0))
|
||||
let outerIntersectionY2 = sqrt(pow(diameter * 0.5, 2.0) - pow(localLabelRect.maxX, 2.0))*/
|
||||
|
||||
if let labelView = label.view {
|
||||
if labelView.superview == nil {
|
||||
self.addSubview(labelView)
|
||||
}
|
||||
labelView.bounds = CGRect(origin: CGPoint(), size: labelFrame.size)
|
||||
transition.setPosition(view: labelView, position: labelFrame.center)
|
||||
|
||||
if let selectedKey = self.selectedKey {
|
||||
if selectedKey == item.id {
|
||||
transition.setAlpha(view: labelView, alpha: 1.0)
|
||||
} else {
|
||||
transition.setAlpha(view: labelView, alpha: 0.0)
|
||||
}
|
||||
} else {
|
||||
transition.setAlpha(view: labelView, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: 200.0)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
@ -0,0 +1,254 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import ViewControllerComponent
|
||||
import ComponentDisplayAdapters
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import MultilineTextComponent
|
||||
import EmojiStatusComponent
|
||||
import Postbox
|
||||
import CheckNode
|
||||
import SolidRoundedButtonComponent
|
||||
|
||||
final class StorageCategoriesComponent: Component {
|
||||
struct CategoryData: Equatable {
|
||||
var key: AnyHashable
|
||||
var color: UIColor
|
||||
var title: String
|
||||
var size: Int64
|
||||
var sizeFraction: Double
|
||||
var isSelected: Bool
|
||||
var subcategories: [CategoryData]
|
||||
|
||||
init(key: AnyHashable, color: UIColor, title: String, size: Int64, sizeFraction: Double, isSelected: Bool, subcategories: [CategoryData]) {
|
||||
self.key = key
|
||||
self.title = title
|
||||
self.color = color
|
||||
self.size = size
|
||||
self.sizeFraction = sizeFraction
|
||||
self.isSelected = isSelected
|
||||
self.subcategories = subcategories
|
||||
}
|
||||
}
|
||||
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let categories: [CategoryData]
|
||||
let toggleCategorySelection: (AnyHashable) -> Void
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
categories: [CategoryData],
|
||||
toggleCategorySelection: @escaping (AnyHashable) -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.categories = categories
|
||||
self.toggleCategorySelection = toggleCategorySelection
|
||||
}
|
||||
|
||||
static func ==(lhs: StorageCategoriesComponent, rhs: StorageCategoriesComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.categories != rhs.categories {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
class View: UIView {
|
||||
private var itemViews: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
private let button = ComponentView<Empty>()
|
||||
|
||||
private var expandedCategory: AnyHashable?
|
||||
private var component: StorageCategoriesComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.clipsToBounds = true
|
||||
self.layer.cornerRadius = 10.0
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: StorageCategoriesComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
var totalSelectedSize: Int64 = 0
|
||||
var hasDeselected = false
|
||||
for category in component.categories {
|
||||
if !category.subcategories.isEmpty {
|
||||
for subcategory in category.subcategories {
|
||||
if subcategory.isSelected {
|
||||
totalSelectedSize += subcategory.size
|
||||
} else {
|
||||
hasDeselected = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if category.isSelected {
|
||||
totalSelectedSize += category.size
|
||||
} else {
|
||||
hasDeselected = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
|
||||
var validKeys = Set<AnyHashable>()
|
||||
for i in 0 ..< component.categories.count {
|
||||
let category = component.categories[i]
|
||||
validKeys.insert(category.key)
|
||||
|
||||
var itemTransition = transition
|
||||
let itemView: ComponentView<Empty>
|
||||
if let current = self.itemViews[category.key] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemTransition = .immediate
|
||||
itemView = ComponentView()
|
||||
itemView.parentState = state
|
||||
self.itemViews[category.key] = itemView
|
||||
}
|
||||
|
||||
let itemSize = itemView.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(StorageCategoryItemComponent(
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
category: category,
|
||||
isExpandedLevel: false,
|
||||
isExpanded: self.expandedCategory == category.key,
|
||||
hasNext: i != component.categories.count - 1,
|
||||
action: { [weak self] key, actionType in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
switch actionType {
|
||||
case .generic:
|
||||
if let category = component.categories.first(where: { $0.key == key }), !category.subcategories.isEmpty {
|
||||
if self.expandedCategory == category.key {
|
||||
self.expandedCategory = nil
|
||||
} else {
|
||||
self.expandedCategory = category.key
|
||||
}
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
} else {
|
||||
component.toggleCategorySelection(key)
|
||||
}
|
||||
case .toggle:
|
||||
component.toggleCategorySelection(key)
|
||||
}
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 1000.0)
|
||||
)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: itemSize)
|
||||
if let itemComponentView = itemView.view {
|
||||
if itemComponentView.superview == nil {
|
||||
self.addSubview(itemComponentView)
|
||||
}
|
||||
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
|
||||
}
|
||||
|
||||
contentHeight += itemSize.height
|
||||
}
|
||||
|
||||
var removeKeys: [AnyHashable] = []
|
||||
for (key, itemView) in self.itemViews {
|
||||
if !validKeys.contains(key) {
|
||||
if let itemComponentView = itemView.view {
|
||||
transition.setAlpha(view: itemComponentView, alpha: 0.0, completion: { [weak itemComponentView] _ in
|
||||
itemComponentView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
removeKeys.append(key)
|
||||
}
|
||||
}
|
||||
for key in removeKeys {
|
||||
self.itemViews.removeValue(forKey: key)
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let clearTitle: String
|
||||
let label: String?
|
||||
if totalSelectedSize == 0 {
|
||||
clearTitle = "Clear"
|
||||
label = nil
|
||||
} else if hasDeselected {
|
||||
clearTitle = "Clear Selected"
|
||||
label = dataSizeString(totalSelectedSize, formatting: DataSizeStringFormatting(strings: component.strings, decimalSeparator: "."))
|
||||
} else {
|
||||
clearTitle = "Clear All Cache"
|
||||
label = dataSizeString(totalSelectedSize, formatting: DataSizeStringFormatting(strings: component.strings, decimalSeparator: "."))
|
||||
}
|
||||
|
||||
contentHeight += 8.0
|
||||
let buttonSize = self.button.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(SolidRoundedButtonComponent(
|
||||
title: clearTitle,
|
||||
label: label,
|
||||
theme: SolidRoundedButtonComponent.Theme(
|
||||
backgroundColor: component.theme.list.itemCheckColors.fillColor,
|
||||
backgroundColors: [],
|
||||
foregroundColor: component.theme.list.itemCheckColors.foregroundColor
|
||||
),
|
||||
font: .bold,
|
||||
fontSize: 17.0,
|
||||
height: 50.0,
|
||||
cornerRadius: 10.0,
|
||||
gloss: false,
|
||||
isEnabled: totalSelectedSize != 0,
|
||||
animationName: nil,
|
||||
iconPosition: .right,
|
||||
iconSpacing: 4.0,
|
||||
action: {
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - 16.0 * 2.0, height: 50.0)
|
||||
)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: 16.0, y: contentHeight), size: buttonSize)
|
||||
if let buttonView = button.view {
|
||||
if buttonView.superview == nil {
|
||||
self.addSubview(buttonView)
|
||||
}
|
||||
transition.setFrame(view: buttonView, frame: buttonFrame)
|
||||
}
|
||||
contentHeight += buttonSize.height
|
||||
|
||||
contentHeight += 16.0
|
||||
|
||||
self.backgroundColor = component.theme.list.itemBlocksBackgroundColor
|
||||
|
||||
return CGSize(width: availableSize.width, height: contentHeight)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
@ -0,0 +1,387 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import ViewControllerComponent
|
||||
import ComponentDisplayAdapters
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import MultilineTextComponent
|
||||
import EmojiStatusComponent
|
||||
import Postbox
|
||||
import TelegramStringFormatting
|
||||
import CheckNode
|
||||
|
||||
final class StorageCategoryItemComponent: Component {
|
||||
enum ActionType {
|
||||
case toggle
|
||||
case generic
|
||||
}
|
||||
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let category: StorageCategoriesComponent.CategoryData
|
||||
let isExpandedLevel: Bool
|
||||
let isExpanded: Bool
|
||||
let hasNext: Bool
|
||||
let action: (AnyHashable, ActionType) -> Void
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
category: StorageCategoriesComponent.CategoryData,
|
||||
isExpandedLevel: Bool,
|
||||
isExpanded: Bool,
|
||||
hasNext: Bool,
|
||||
action: @escaping (AnyHashable, ActionType) -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.category = category
|
||||
self.isExpandedLevel = isExpandedLevel
|
||||
self.isExpanded = isExpanded
|
||||
self.hasNext = hasNext
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: StorageCategoryItemComponent, rhs: StorageCategoryItemComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.category != rhs.category {
|
||||
return false
|
||||
}
|
||||
if lhs.isExpandedLevel != rhs.isExpandedLevel {
|
||||
return false
|
||||
}
|
||||
if lhs.isExpanded != rhs.isExpanded {
|
||||
return false
|
||||
}
|
||||
if lhs.hasNext != rhs.hasNext {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
class View: HighlightTrackingButton {
|
||||
private let checkLayer: CheckLayer
|
||||
private let title = ComponentView<Empty>()
|
||||
private let titleValue = ComponentView<Empty>()
|
||||
private let label = ComponentView<Empty>()
|
||||
private var iconView: UIImageView?
|
||||
private let separatorLayer: SimpleLayer
|
||||
|
||||
private let checkButtonArea: HighlightTrackingButton
|
||||
|
||||
private let subcategoryClippingContainer: UIView
|
||||
private var itemViews: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
|
||||
private var component: StorageCategoryItemComponent?
|
||||
|
||||
private var highlightBackgroundFrame: CGRect?
|
||||
private var highlightBackgroundLayer: SimpleLayer?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.checkLayer = CheckLayer()
|
||||
self.separatorLayer = SimpleLayer()
|
||||
|
||||
self.checkButtonArea = HighlightTrackingButton()
|
||||
|
||||
self.subcategoryClippingContainer = UIView()
|
||||
self.subcategoryClippingContainer.clipsToBounds = true
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.subcategoryClippingContainer)
|
||||
|
||||
self.layer.addSublayer(self.separatorLayer)
|
||||
self.layer.addSublayer(self.checkLayer)
|
||||
|
||||
self.addSubview(self.checkButtonArea)
|
||||
|
||||
self.highligthedChanged = { [weak self] isHighlighted in
|
||||
guard let self, let component = self.component, let highlightBackgroundFrame = self.highlightBackgroundFrame else {
|
||||
return
|
||||
}
|
||||
|
||||
if isHighlighted {
|
||||
self.superview?.bringSubviewToFront(self)
|
||||
|
||||
let highlightBackgroundLayer: SimpleLayer
|
||||
if let current = self.highlightBackgroundLayer {
|
||||
highlightBackgroundLayer = current
|
||||
} else {
|
||||
highlightBackgroundLayer = SimpleLayer()
|
||||
self.highlightBackgroundLayer = highlightBackgroundLayer
|
||||
self.layer.insertSublayer(highlightBackgroundLayer, above: self.separatorLayer)
|
||||
highlightBackgroundLayer.backgroundColor = component.theme.list.itemHighlightedBackgroundColor.cgColor
|
||||
}
|
||||
highlightBackgroundLayer.frame = highlightBackgroundFrame
|
||||
highlightBackgroundLayer.opacity = 1.0
|
||||
} else {
|
||||
if let highlightBackgroundLayer = self.highlightBackgroundLayer {
|
||||
self.highlightBackgroundLayer = nil
|
||||
highlightBackgroundLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak highlightBackgroundLayer] _ in
|
||||
highlightBackgroundLayer?.removeFromSuperlayer()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
|
||||
self.checkButtonArea.addTarget(self, action: #selector(self.checkPressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.action(component.category.key, .generic)
|
||||
}
|
||||
|
||||
@objc private func checkPressed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.action(component.category.key, .toggle)
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
guard let result = super.hitTest(point, with: event) else {
|
||||
return nil
|
||||
}
|
||||
if result === self.subcategoryClippingContainer {
|
||||
return self
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func update(component: StorageCategoryItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let themeUpdated = self.component?.theme !== component.theme
|
||||
|
||||
self.component = component
|
||||
|
||||
var leftInset: CGFloat = 62.0
|
||||
var additionalLeftInset: CGFloat = 0.0
|
||||
|
||||
if component.isExpandedLevel {
|
||||
additionalLeftInset += 45.0
|
||||
}
|
||||
leftInset += additionalLeftInset
|
||||
|
||||
let rightInset: CGFloat = 16.0
|
||||
|
||||
var availableWidth: CGFloat = availableSize.width - leftInset
|
||||
|
||||
if !component.category.subcategories.isEmpty {
|
||||
let iconView: UIImageView
|
||||
if let current = self.iconView {
|
||||
iconView = current
|
||||
if themeUpdated {
|
||||
iconView.image = PresentationResourcesItemList.disclosureArrowImage(component.theme)
|
||||
}
|
||||
} else {
|
||||
iconView = UIImageView()
|
||||
iconView.image = PresentationResourcesItemList.disclosureArrowImage(component.theme)
|
||||
self.iconView = iconView
|
||||
self.addSubview(iconView)
|
||||
}
|
||||
|
||||
if let image = iconView.image {
|
||||
availableWidth -= image.size.width + 6.0
|
||||
transition.setBounds(view: iconView, bounds: CGRect(origin: CGPoint(), size: image.size))
|
||||
}
|
||||
} else if let iconView = self.iconView {
|
||||
self.iconView = nil
|
||||
iconView.removeFromSuperview()
|
||||
}
|
||||
|
||||
let fractionValue: Double = floor(component.category.sizeFraction * 100.0 * 10.0) / 10.0
|
||||
let fractionString: String
|
||||
if abs(Double(Int(fractionValue)) - fractionValue) < 0.001 {
|
||||
fractionString = "\(Int(fractionValue))"
|
||||
} else {
|
||||
fractionString = "\(fractionValue)"
|
||||
}
|
||||
|
||||
let labelSize = self.label.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Text(text: dataSizeString(Int(component.category.size), formatting: DataSizeStringFormatting(strings: component.strings, decimalSeparator: ".")), font: Font.regular(17.0), color: component.theme.list.itemSecondaryTextColor)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableWidth, height: 100.0)
|
||||
)
|
||||
availableWidth = max(1.0, availableWidth - labelSize.width - 4.0)
|
||||
|
||||
let titleValueSize = self.titleValue.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Text(text: "\(fractionString)%", font: Font.regular(17.0), color: component.theme.list.itemSecondaryTextColor)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableWidth, height: 100.0)
|
||||
)
|
||||
availableWidth = max(1.0, availableWidth - titleValueSize.width - 1.0)
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Text(text: component.category.title, font: Font.regular(17.0), color: component.theme.list.itemPrimaryTextColor)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableWidth, height: 100.0)
|
||||
)
|
||||
|
||||
var height: CGFloat = 44.0
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
|
||||
let titleValueFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floor((height - titleValueSize.height) / 2.0)), size: titleValueSize)
|
||||
|
||||
var labelFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - labelSize.width, y: floor((height - labelSize.height) / 2.0)), size: labelSize)
|
||||
|
||||
if let iconView = self.iconView, let image = iconView.image {
|
||||
labelFrame.origin.x -= image.size.width - 6.0
|
||||
|
||||
transition.setPosition(view: iconView, position: CGPoint(x: availableSize.width - rightInset + 6.0 - floor(image.size.width * 0.5), y: floor(height * 0.5)))
|
||||
let angle: CGFloat = component.isExpanded ? CGFloat.pi : 0.0
|
||||
transition.setTransform(view: iconView, transform: CATransform3DMakeRotation(CGFloat.pi * 0.5 - angle, 0.0, 0.0, 1.0))
|
||||
}
|
||||
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
titleView.isUserInteractionEnabled = false
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
transition.setFrame(view: titleView, frame: titleFrame)
|
||||
}
|
||||
if let titleValueView = self.titleValue.view {
|
||||
if titleValueView.superview == nil {
|
||||
titleValueView.isUserInteractionEnabled = false
|
||||
self.addSubview(titleValueView)
|
||||
}
|
||||
transition.setFrame(view: titleValueView, frame: titleValueFrame)
|
||||
}
|
||||
if let labelView = self.label.view {
|
||||
if labelView.superview == nil {
|
||||
labelView.isUserInteractionEnabled = false
|
||||
self.addSubview(labelView)
|
||||
}
|
||||
transition.setFrame(view: labelView, frame: labelFrame)
|
||||
}
|
||||
|
||||
if themeUpdated {
|
||||
self.checkLayer.theme = CheckNodeTheme(
|
||||
backgroundColor: component.category.color,
|
||||
strokeColor: component.theme.list.itemCheckColors.foregroundColor,
|
||||
borderColor: component.theme.list.itemCheckColors.strokeColor,
|
||||
overlayBorder: false,
|
||||
hasInset: false,
|
||||
hasShadow: false
|
||||
)
|
||||
}
|
||||
|
||||
let checkDiameter: CGFloat = 22.0
|
||||
let checkFrame = CGRect(origin: CGPoint(x: titleFrame.minX - 20.0 - checkDiameter, y: floor((height - checkDiameter) / 2.0)), size: CGSize(width: checkDiameter, height: checkDiameter))
|
||||
self.checkLayer.frame = checkFrame
|
||||
|
||||
transition.setFrame(view: self.checkButtonArea, frame: CGRect(origin: CGPoint(x: additionalLeftInset, y: 0.0), size: CGSize(width: leftInset - additionalLeftInset, height: height)))
|
||||
|
||||
if self.checkLayer.selected != component.category.isSelected {
|
||||
self.checkLayer.setSelected(component.category.isSelected, animated: !transition.animation.isImmediate)
|
||||
}
|
||||
|
||||
if themeUpdated {
|
||||
self.separatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor
|
||||
}
|
||||
transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: leftInset, y: height), size: CGSize(width: availableSize.width - leftInset, height: UIScreenPixel)))
|
||||
|
||||
transition.setAlpha(layer: self.separatorLayer, alpha: (component.isExpanded || component.hasNext) ? 1.0 : 0.0)
|
||||
|
||||
self.highlightBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height + ((component.isExpanded || component.hasNext) ? UIScreenPixel : 0.0)))
|
||||
|
||||
var validKeys = Set<AnyHashable>()
|
||||
if component.isExpanded {
|
||||
for i in 0 ..< component.category.subcategories.count {
|
||||
let category = component.category.subcategories[i]
|
||||
validKeys.insert(category.key)
|
||||
|
||||
var itemTransition = transition
|
||||
let itemView: ComponentView<Empty>
|
||||
if let current = self.itemViews[category.key] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemTransition = .immediate
|
||||
itemView = ComponentView()
|
||||
self.itemViews[category.key] = itemView
|
||||
}
|
||||
|
||||
itemView.parentState = state
|
||||
let itemSize = itemView.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(StorageCategoryItemComponent(
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
category: category,
|
||||
isExpandedLevel: true,
|
||||
isExpanded: false,
|
||||
hasNext: i != component.category.subcategories.count - 1,
|
||||
action: { [weak self] key, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.component?.action(key, .toggle)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 1000.0)
|
||||
)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: height), size: itemSize)
|
||||
if let itemComponentView = itemView.view {
|
||||
if itemComponentView.superview == nil {
|
||||
self.subcategoryClippingContainer.addSubview(itemComponentView)
|
||||
if !transition.animation.isImmediate {
|
||||
itemComponentView.alpha = 0.0
|
||||
transition.setAlpha(view: itemComponentView, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
|
||||
}
|
||||
|
||||
height += itemSize.height
|
||||
}
|
||||
}
|
||||
|
||||
var removeKeys: [AnyHashable] = []
|
||||
for (key, itemView) in self.itemViews {
|
||||
if !validKeys.contains(key) {
|
||||
if let itemComponentView = itemView.view {
|
||||
transition.setAlpha(view: itemComponentView, alpha: 0.0, completion: { [weak itemComponentView] _ in
|
||||
itemComponentView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
removeKeys.append(key)
|
||||
}
|
||||
}
|
||||
for key in removeKeys {
|
||||
self.itemViews.removeValue(forKey: key)
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.subcategoryClippingContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height)))
|
||||
|
||||
return CGSize(width: availableSize.width, height: height)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
@ -0,0 +1,224 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import ViewControllerComponent
|
||||
import ComponentDisplayAdapters
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import MultilineTextComponent
|
||||
import EmojiStatusComponent
|
||||
import Postbox
|
||||
import TelegramStringFormatting
|
||||
import CheckNode
|
||||
|
||||
final class StoragePeerTypeItemComponent: Component {
|
||||
enum ActionType {
|
||||
case toggle
|
||||
case generic
|
||||
}
|
||||
|
||||
let theme: PresentationTheme
|
||||
let iconName: String
|
||||
let title: String
|
||||
let value: String
|
||||
let hasNext: Bool
|
||||
let action: (View) -> Void
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
iconName: String,
|
||||
title: String,
|
||||
value: String,
|
||||
hasNext: Bool,
|
||||
action: @escaping (View) -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.iconName = iconName
|
||||
self.title = title
|
||||
self.value = value
|
||||
self.hasNext = hasNext
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: StoragePeerTypeItemComponent, rhs: StoragePeerTypeItemComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.iconName != rhs.iconName {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.value != rhs.value {
|
||||
return false
|
||||
}
|
||||
if lhs.hasNext != rhs.hasNext {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
class View: HighlightTrackingButton {
|
||||
private let iconView: UIImageView
|
||||
private let title = ComponentView<Empty>()
|
||||
private let label = ComponentView<Empty>()
|
||||
private let separatorLayer: SimpleLayer
|
||||
private let arrowIconView: UIImageView
|
||||
|
||||
private var component: StoragePeerTypeItemComponent?
|
||||
|
||||
private var highlightBackgroundFrame: CGRect?
|
||||
private var highlightBackgroundLayer: SimpleLayer?
|
||||
|
||||
var labelView: UIView? {
|
||||
return self.label.view
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.separatorLayer = SimpleLayer()
|
||||
self.iconView = UIImageView()
|
||||
self.arrowIconView = UIImageView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.layer.addSublayer(self.separatorLayer)
|
||||
|
||||
self.addSubview(self.iconView)
|
||||
self.addSubview(self.arrowIconView)
|
||||
|
||||
self.highligthedChanged = { [weak self] isHighlighted in
|
||||
guard let self, let component = self.component, let highlightBackgroundFrame = self.highlightBackgroundFrame else {
|
||||
return
|
||||
}
|
||||
|
||||
if isHighlighted {
|
||||
self.superview?.bringSubviewToFront(self)
|
||||
|
||||
let highlightBackgroundLayer: SimpleLayer
|
||||
if let current = self.highlightBackgroundLayer {
|
||||
highlightBackgroundLayer = current
|
||||
} else {
|
||||
highlightBackgroundLayer = SimpleLayer()
|
||||
self.highlightBackgroundLayer = highlightBackgroundLayer
|
||||
self.layer.insertSublayer(highlightBackgroundLayer, above: self.separatorLayer)
|
||||
highlightBackgroundLayer.backgroundColor = component.theme.list.itemHighlightedBackgroundColor.cgColor
|
||||
}
|
||||
highlightBackgroundLayer.frame = highlightBackgroundFrame
|
||||
highlightBackgroundLayer.opacity = 1.0
|
||||
} else {
|
||||
if let highlightBackgroundLayer = self.highlightBackgroundLayer {
|
||||
self.highlightBackgroundLayer = nil
|
||||
highlightBackgroundLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak highlightBackgroundLayer] _ in
|
||||
highlightBackgroundLayer?.removeFromSuperlayer()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.action(self)
|
||||
}
|
||||
|
||||
func setHasAssociatedMenu(_ hasAssociatedMenu: Bool) {
|
||||
let transition: Transition
|
||||
if hasAssociatedMenu {
|
||||
transition = .immediate
|
||||
} else {
|
||||
transition = .easeInOut(duration: 0.25)
|
||||
}
|
||||
if let view = self.label.view {
|
||||
transition.setAlpha(view: view, alpha: hasAssociatedMenu ? 0.5 : 1.0)
|
||||
}
|
||||
transition.setAlpha(view: self.arrowIconView, alpha: hasAssociatedMenu ? 0.5 : 1.0)
|
||||
}
|
||||
|
||||
func update(component: StoragePeerTypeItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let themeUpdated = self.component?.theme !== component.theme
|
||||
|
||||
self.component = component
|
||||
|
||||
let leftInset: CGFloat = 62.0
|
||||
let rightInset: CGFloat = 32.0
|
||||
|
||||
var availableWidth: CGFloat = availableSize.width - leftInset - rightInset
|
||||
|
||||
let labelSize = self.label.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Text(text: component.value, font: Font.regular(17.0), color: component.theme.list.itemSecondaryTextColor)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableWidth, height: 100.0)
|
||||
)
|
||||
availableWidth = max(1.0, availableWidth - labelSize.width - 4.0)
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Text(text: component.title, font: Font.regular(17.0), color: component.theme.list.itemPrimaryTextColor)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableWidth, height: 100.0)
|
||||
)
|
||||
|
||||
let height: CGFloat = 44.0
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
|
||||
let labelFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - labelSize.width, y: floor((height - labelSize.height) / 2.0)), size: labelSize)
|
||||
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
titleView.isUserInteractionEnabled = false
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
transition.setFrame(view: titleView, frame: titleFrame)
|
||||
}
|
||||
if let labelView = self.label.view {
|
||||
if labelView.superview == nil {
|
||||
labelView.isUserInteractionEnabled = false
|
||||
self.addSubview(labelView)
|
||||
}
|
||||
transition.setFrame(view: labelView, frame: labelFrame)
|
||||
}
|
||||
|
||||
if themeUpdated {
|
||||
self.separatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor
|
||||
self.iconView.image = UIImage(bundleImageName: component.iconName)
|
||||
self.arrowIconView.image = PresentationResourcesItemList.disclosureOptionArrowsImage(component.theme)
|
||||
}
|
||||
|
||||
if let image = self.iconView.image {
|
||||
transition.setFrame(view: self.iconView, frame: CGRect(origin: CGPoint(x: floor((leftInset - image.size.width) / 2.0), y: floor((height - image.size.height) / 2.0)), size: image.size))
|
||||
}
|
||||
if let image = self.arrowIconView.image {
|
||||
transition.setFrame(view: self.arrowIconView, frame: CGRect(origin: CGPoint(x: availableSize.width - rightInset + 5.0, y: floor((height - image.size.height) / 2.0)), size: image.size))
|
||||
}
|
||||
|
||||
transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: leftInset, y: height), size: CGSize(width: availableSize.width - leftInset, height: UIScreenPixel)))
|
||||
|
||||
transition.setAlpha(layer: self.separatorLayer, alpha: component.hasNext ? 1.0 : 0.0)
|
||||
|
||||
self.highlightBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height + (component.hasNext ? UIScreenPixel : 0.0)))
|
||||
|
||||
return CGSize(width: availableSize.width, height: height)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -84,6 +84,7 @@ import NotificationPeerExceptionController
|
||||
import StickerPackPreviewUI
|
||||
import ChatListHeaderComponent
|
||||
import ChatControllerInteraction
|
||||
import StorageUsageScreen
|
||||
|
||||
enum PeerInfoAvatarEditingMode {
|
||||
case generic
|
||||
|
Loading…
x
Reference in New Issue
Block a user