Storage calculation

This commit is contained in:
Ali 2022-12-20 21:26:55 +04:00
parent 34909d0de9
commit 45ff6ba714
23 changed files with 2778 additions and 60 deletions

View File

@ -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()

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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);

View File

@ -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")
}

View File

@ -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)

View 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 {

View File

@ -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(&currentSize, offset: 0, length: 8)
}
currentSize += delta
if currentSize < 0 {
assertionFailure()
currentSize = 0
}
self.valueBox.set(self.contentTypeStatsTable, key: key, value: MemoryBuffer(memory: &currentSize, 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(&currentSize, offset: 0, length: 8)
}
currentSize += delta
if currentSize < 0 {
assertionFailure()
currentSize = 0
}
self.valueBox.set(self.contentTypeStatsTable, key: key, value: MemoryBuffer(memory: &currentSize, 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
}
}
}

View File

@ -109,6 +109,7 @@ swift_library(
"//submodules/TelegramUI/Components/NotificationPeerExceptionController",
"//submodules/TelegramUI/Components/ChatTimerScreen",
"//submodules/AnimatedAvatarSetNode",
"//submodules/TelegramUI/Components/StorageUsageScreen",
],
visibility = [
"//visibility:public",

View File

@ -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: {

View File

@ -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)

View File

@ -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

View File

@ -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)

View 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",
],
)

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -84,6 +84,7 @@ import NotificationPeerExceptionController
import StickerPackPreviewUI
import ChatListHeaderComponent
import ChatControllerInteraction
import StorageUsageScreen
enum PeerInfoAvatarEditingMode {
case generic