mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
A coarse approximation of storage management UI
This commit is contained in:
parent
808f5b80ff
commit
d3f078410d
@ -7299,6 +7299,7 @@ Sorry for the inconvenience.";
|
|||||||
"Contacts.Sort.ByLastSeen" = "by Last Seen";
|
"Contacts.Sort.ByLastSeen" = "by Last Seen";
|
||||||
|
|
||||||
"ClearCache.Progress" = "Clearing the Cache • %d%";
|
"ClearCache.Progress" = "Clearing the Cache • %d%";
|
||||||
|
"ClearCache.NoProgress" = "Clearing the Cache";
|
||||||
"ClearCache.KeepOpenedDescription" = "Please keep this window open until the clearing is completed.";
|
"ClearCache.KeepOpenedDescription" = "Please keep this window open until the clearing is completed.";
|
||||||
|
|
||||||
"Share.ShareAsLink" = "Share as Link";
|
"Share.ShareAsLink" = "Share as Link";
|
||||||
|
@ -77,6 +77,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
case resetDatabaseAndCache(PresentationTheme)
|
case resetDatabaseAndCache(PresentationTheme)
|
||||||
case resetHoles(PresentationTheme)
|
case resetHoles(PresentationTheme)
|
||||||
case reindexUnread(PresentationTheme)
|
case reindexUnread(PresentationTheme)
|
||||||
|
case resetCacheIndex
|
||||||
case reindexCache
|
case reindexCache
|
||||||
case resetBiometricsData(PresentationTheme)
|
case resetBiometricsData(PresentationTheme)
|
||||||
case resetWebViewCache(PresentationTheme)
|
case resetWebViewCache(PresentationTheme)
|
||||||
@ -112,7 +113,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return DebugControllerSection.logging.rawValue
|
return DebugControllerSection.logging.rawValue
|
||||||
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
||||||
return DebugControllerSection.experiments.rawValue
|
return DebugControllerSection.experiments.rawValue
|
||||||
case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .reindexCache, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineForums, .localTranscription, . enableReactionOverrides, .restorePurchases:
|
case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineForums, .localTranscription, . enableReactionOverrides, .restorePurchases:
|
||||||
return DebugControllerSection.experiments.rawValue
|
return DebugControllerSection.experiments.rawValue
|
||||||
case .preferredVideoCodec:
|
case .preferredVideoCodec:
|
||||||
return DebugControllerSection.videoExperiments.rawValue
|
return DebugControllerSection.videoExperiments.rawValue
|
||||||
@ -171,42 +172,44 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return 21
|
return 21
|
||||||
case .reindexUnread:
|
case .reindexUnread:
|
||||||
return 22
|
return 22
|
||||||
case .reindexCache:
|
case .resetCacheIndex:
|
||||||
return 23
|
return 23
|
||||||
case .resetBiometricsData:
|
case .reindexCache:
|
||||||
return 24
|
return 24
|
||||||
case .resetWebViewCache:
|
case .resetBiometricsData:
|
||||||
return 25
|
return 25
|
||||||
case .optimizeDatabase:
|
case .resetWebViewCache:
|
||||||
return 26
|
return 26
|
||||||
case .photoPreview:
|
case .optimizeDatabase:
|
||||||
return 27
|
return 27
|
||||||
case .knockoutWallpaper:
|
case .photoPreview:
|
||||||
return 28
|
return 28
|
||||||
case .experimentalCompatibility:
|
case .knockoutWallpaper:
|
||||||
return 29
|
return 29
|
||||||
case .enableDebugDataDisplay:
|
case .experimentalCompatibility:
|
||||||
return 30
|
return 30
|
||||||
case .acceleratedStickers:
|
case .enableDebugDataDisplay:
|
||||||
return 31
|
return 31
|
||||||
case .experimentalBackground:
|
case .acceleratedStickers:
|
||||||
return 32
|
return 32
|
||||||
case .inlineForums:
|
case .experimentalBackground:
|
||||||
return 33
|
return 33
|
||||||
case .localTranscription:
|
case .inlineForums:
|
||||||
return 34
|
return 34
|
||||||
case .enableReactionOverrides:
|
case .localTranscription:
|
||||||
return 35
|
return 35
|
||||||
case .restorePurchases:
|
case .enableReactionOverrides:
|
||||||
return 36
|
return 36
|
||||||
case .playerEmbedding:
|
case .restorePurchases:
|
||||||
return 37
|
return 37
|
||||||
case .playlistPlayback:
|
case .playerEmbedding:
|
||||||
return 38
|
return 38
|
||||||
case .voiceConference:
|
case .playlistPlayback:
|
||||||
return 39
|
return 39
|
||||||
|
case .voiceConference:
|
||||||
|
return 40
|
||||||
case let .preferredVideoCodec(index, _, _, _):
|
case let .preferredVideoCodec(index, _, _, _):
|
||||||
return 40 + index
|
return 41 + index
|
||||||
case .disableVideoAspectScaling:
|
case .disableVideoAspectScaling:
|
||||||
return 100
|
return 100
|
||||||
case .enableVoipTcp:
|
case .enableVoipTcp:
|
||||||
@ -970,6 +973,14 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
controller.dismiss()
|
controller.dismiss()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
case .resetCacheIndex:
|
||||||
|
return ItemListActionItem(presentationData: presentationData, title: "Reset Cache Index [!]", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
|
guard let context = arguments.context else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
context.account.postbox.mediaBox.storageBox.reset()
|
||||||
|
})
|
||||||
case .reindexCache:
|
case .reindexCache:
|
||||||
return ItemListActionItem(presentationData: presentationData, title: "Reindex Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(presentationData: presentationData, title: "Reindex Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
guard let context = arguments.context else {
|
guard let context = arguments.context else {
|
||||||
@ -1253,6 +1264,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
|||||||
entries.append(.resetHoles(presentationData.theme))
|
entries.append(.resetHoles(presentationData.theme))
|
||||||
if isMainApp {
|
if isMainApp {
|
||||||
entries.append(.reindexUnread(presentationData.theme))
|
entries.append(.reindexUnread(presentationData.theme))
|
||||||
|
entries.append(.resetCacheIndex)
|
||||||
entries.append(.reindexCache)
|
entries.append(.reindexCache)
|
||||||
entries.append(.resetWebViewCache(presentationData.theme))
|
entries.append(.resetWebViewCache(presentationData.theme))
|
||||||
}
|
}
|
||||||
|
@ -230,6 +230,19 @@ public final class StorageBox {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reset() {
|
||||||
|
self.valueBox.begin()
|
||||||
|
|
||||||
|
self.valueBox.removeAllFromTable(self.hashIdToInfoTable)
|
||||||
|
self.valueBox.removeAllFromTable(self.idToReferenceTable)
|
||||||
|
self.valueBox.removeAllFromTable(self.peerIdToIdTable)
|
||||||
|
self.valueBox.removeAllFromTable(self.peerContentTypeStatsTable)
|
||||||
|
self.valueBox.removeAllFromTable(self.contentTypeStatsTable)
|
||||||
|
self.valueBox.removeAllFromTable(self.metadataTable)
|
||||||
|
|
||||||
|
self.valueBox.commit()
|
||||||
|
}
|
||||||
|
|
||||||
private func internalAddSize(contentType: UInt8, delta: Int64) {
|
private func internalAddSize(contentType: UInt8, delta: Int64) {
|
||||||
let key = ValueBoxKey(length: 1)
|
let key = ValueBoxKey(length: 1)
|
||||||
key.setUInt8(0, value: contentType)
|
key.setUInt8(0, value: contentType)
|
||||||
@ -893,4 +906,10 @@ public final class StorageBox {
|
|||||||
completion(ids)
|
completion(ids)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func reset() {
|
||||||
|
self.impl.with { impl in
|
||||||
|
impl.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,10 +91,14 @@ public final class AllStorageUsageStats {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var deviceAvailableSpace: Int64
|
||||||
|
public var deviceFreeSpace: Int64
|
||||||
public fileprivate(set) var totalStats: StorageUsageStats
|
public fileprivate(set) var totalStats: StorageUsageStats
|
||||||
public fileprivate(set) var peers: [EnginePeer.Id: PeerStats]
|
public fileprivate(set) var peers: [EnginePeer.Id: PeerStats]
|
||||||
|
|
||||||
public init(totalStats: StorageUsageStats, peers: [EnginePeer.Id: PeerStats]) {
|
public init(deviceAvailableSpace: Int64, deviceFreeSpace: Int64, totalStats: StorageUsageStats, peers: [EnginePeer.Id: PeerStats]) {
|
||||||
|
self.deviceAvailableSpace = deviceAvailableSpace
|
||||||
|
self.deviceFreeSpace = deviceFreeSpace
|
||||||
self.totalStats = totalStats
|
self.totalStats = totalStats
|
||||||
self.peers = peers
|
self.peers = peers
|
||||||
}
|
}
|
||||||
@ -242,7 +246,13 @@ func _internal_collectStorageUsageStats(account: Account) -> Signal<AllStorageUs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
|
||||||
|
let deviceAvailableSpace = (systemAttributes?[FileAttributeKey.systemSize] as? NSNumber)?.int64Value ?? 0
|
||||||
|
let deviceFreeSpace = (systemAttributes?[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0
|
||||||
|
|
||||||
return AllStorageUsageStats(
|
return AllStorageUsageStats(
|
||||||
|
deviceAvailableSpace: deviceAvailableSpace,
|
||||||
|
deviceFreeSpace: deviceFreeSpace,
|
||||||
totalStats: total,
|
totalStats: total,
|
||||||
peers: peers
|
peers: peers
|
||||||
)
|
)
|
||||||
@ -257,7 +267,8 @@ func _internal_renderStorageUsageStatsMessages(account: Account, stats: StorageU
|
|||||||
if !categories.contains(category) {
|
if !categories.contains(category) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for id in value.messages.keys {
|
|
||||||
|
for (id, _) in value.messages.sorted(by: { $0.value >= $1.value }).prefix(1000) {
|
||||||
if result[id] == nil {
|
if result[id] == nil {
|
||||||
if let message = existingMessages[id] {
|
if let message = existingMessages[id] {
|
||||||
result[id] = message
|
result[id] = message
|
||||||
|
@ -33,6 +33,10 @@ swift_library(
|
|||||||
"//submodules/AvatarNode",
|
"//submodules/AvatarNode",
|
||||||
"//submodules/PhotoResources",
|
"//submodules/PhotoResources",
|
||||||
"//submodules/SemanticStatusNode",
|
"//submodules/SemanticStatusNode",
|
||||||
|
"//submodules/RadialStatusNode",
|
||||||
|
"//submodules/UndoUI",
|
||||||
|
"//submodules/AnimatedStickerNode",
|
||||||
|
"//submodules/TelegramAnimatedStickerNode",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -87,12 +87,12 @@ private func processChartData(data: PieChartComponent.ChartData) -> PieChartComp
|
|||||||
final class PieChartComponent: Component {
|
final class PieChartComponent: Component {
|
||||||
struct ChartData: Equatable {
|
struct ChartData: Equatable {
|
||||||
struct Item: Equatable {
|
struct Item: Equatable {
|
||||||
var id: AnyHashable
|
var id: StorageUsageScreenComponent.Category
|
||||||
var displayValue: Double
|
var displayValue: Double
|
||||||
var value: Double
|
var value: Double
|
||||||
var color: UIColor
|
var color: UIColor
|
||||||
|
|
||||||
init(id: AnyHashable, displayValue: Double, value: Double, color: UIColor) {
|
init(id: StorageUsageScreenComponent.Category, displayValue: Double, value: Double, color: UIColor) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.displayValue = displayValue
|
self.displayValue = displayValue
|
||||||
self.value = value
|
self.value = value
|
||||||
@ -131,12 +131,12 @@ final class PieChartComponent: Component {
|
|||||||
private final class ChartDataView: UIView {
|
private final class ChartDataView: UIView {
|
||||||
private(set) var theme: PresentationTheme?
|
private(set) var theme: PresentationTheme?
|
||||||
private(set) var data: ChartData?
|
private(set) var data: ChartData?
|
||||||
private(set) var selectedKey: AnyHashable?
|
private(set) var selectedKey: StorageUsageScreenComponent.Category?
|
||||||
|
|
||||||
private var currentAnimation: (start: ChartData, end: ChartData, current: ChartData, progress: CGFloat)?
|
private var currentAnimation: (start: ChartData, end: ChartData, current: ChartData, progress: CGFloat)?
|
||||||
private var animator: DisplayLinkAnimator?
|
private var animator: DisplayLinkAnimator?
|
||||||
|
|
||||||
private var labels: [AnyHashable: ComponentView<Empty>] = [:]
|
private var labels: [StorageUsageScreenComponent.Category: ComponentView<Empty>] = [:]
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
@ -153,7 +153,7 @@ final class PieChartComponent: Component {
|
|||||||
self.animator?.invalidate()
|
self.animator?.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setItems(theme: PresentationTheme, data: ChartData, selectedKey: AnyHashable?, animated: Bool) {
|
func setItems(theme: PresentationTheme, data: ChartData, selectedKey: StorageUsageScreenComponent.Category?, animated: Bool) {
|
||||||
let data = processChartData(data: data)
|
let data = processChartData(data: data)
|
||||||
|
|
||||||
if self.theme !== theme || self.data != data || self.selectedKey != selectedKey {
|
if self.theme !== theme || self.data != data || self.selectedKey != selectedKey {
|
||||||
@ -366,7 +366,7 @@ final class PieChartComponent: Component {
|
|||||||
|
|
||||||
var minDistance: CGFloat = 1000.0
|
var minDistance: CGFloat = 1000.0
|
||||||
for distance in distances {
|
for distance in distances {
|
||||||
minDistance = min(minDistance, distance + 1.0)
|
minDistance = min(minDistance, distance)
|
||||||
}
|
}
|
||||||
|
|
||||||
let diagonalAngle = atan2(labelSize.height, labelSize.width)
|
let diagonalAngle = atan2(labelSize.height, labelSize.width)
|
||||||
@ -467,8 +467,8 @@ final class PieChartComponent: Component {
|
|||||||
|
|
||||||
class View: UIView {
|
class View: UIView {
|
||||||
private let dataView: ChartDataView
|
private let dataView: ChartDataView
|
||||||
private var labels: [AnyHashable: ComponentView<Empty>] = [:]
|
private var labels: [StorageUsageScreenComponent.Category: ComponentView<Empty>] = [:]
|
||||||
var selectedKey: AnyHashable?
|
var selectedKey: StorageUsageScreenComponent.Category?
|
||||||
|
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
|
|
||||||
|
@ -167,7 +167,7 @@ final class StorageCategoryItemComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func update(component: StorageCategoryItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(component: StorageCategoryItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
let themeUpdated = self.component?.theme !== component.theme
|
let themeUpdated = self.component?.theme !== component.theme || self.component?.category.color != component.category.color
|
||||||
|
|
||||||
self.component = component
|
self.component = component
|
||||||
|
|
||||||
@ -276,7 +276,21 @@ final class StorageCategoryItemComponent: Component {
|
|||||||
transition.setFrame(view: labelView, frame: labelFrame)
|
transition.setFrame(view: labelView, frame: labelFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var copyCheckLayer: CheckLayer?
|
||||||
if themeUpdated {
|
if themeUpdated {
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
let copyLayer = CheckLayer(theme: self.checkLayer.theme)
|
||||||
|
copyLayer.frame = self.checkLayer.frame
|
||||||
|
copyLayer.setSelected(self.checkLayer.selected, animated: false)
|
||||||
|
self.layer.addSublayer(copyLayer)
|
||||||
|
copyCheckLayer = copyLayer
|
||||||
|
transition.setAlpha(layer: copyLayer, alpha: 0.0, completion: { [weak copyLayer] _ in
|
||||||
|
copyLayer?.removeFromSuperlayer()
|
||||||
|
})
|
||||||
|
self.checkLayer.opacity = 0.0
|
||||||
|
transition.setAlpha(layer: self.checkLayer, alpha: 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
self.checkLayer.theme = CheckNodeTheme(
|
self.checkLayer.theme = CheckNodeTheme(
|
||||||
backgroundColor: component.category.color,
|
backgroundColor: component.category.color,
|
||||||
strokeColor: component.theme.list.itemCheckColors.foregroundColor,
|
strokeColor: component.theme.list.itemCheckColors.foregroundColor,
|
||||||
@ -289,7 +303,11 @@ final class StorageCategoryItemComponent: Component {
|
|||||||
|
|
||||||
let checkDiameter: CGFloat = 22.0
|
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))
|
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(layer: self.checkLayer, frame: checkFrame)
|
||||||
|
|
||||||
|
if let copyCheckLayer {
|
||||||
|
transition.setFrame(layer: copyCheckLayer, frame: checkFrame)
|
||||||
|
}
|
||||||
|
|
||||||
transition.setFrame(view: self.checkButtonArea, frame: CGRect(origin: CGPoint(x: additionalLeftInset, y: 0.0), size: CGSize(width: leftInset - additionalLeftInset, height: height)))
|
transition.setFrame(view: self.checkButtonArea, frame: CGRect(origin: CGPoint(x: additionalLeftInset, y: 0.0), size: CGSize(width: leftInset - additionalLeftInset, height: height)))
|
||||||
|
|
||||||
|
@ -16,6 +16,11 @@ import Markdown
|
|||||||
import ContextUI
|
import ContextUI
|
||||||
import AnimatedAvatarSetNode
|
import AnimatedAvatarSetNode
|
||||||
import AvatarNode
|
import AvatarNode
|
||||||
|
import RadialStatusNode
|
||||||
|
import UndoUI
|
||||||
|
import AnimatedStickerNode
|
||||||
|
import TelegramAnimatedStickerNode
|
||||||
|
import TelegramStringFormatting
|
||||||
|
|
||||||
private extension StorageUsageScreenComponent.Category {
|
private extension StorageUsageScreenComponent.Category {
|
||||||
init(_ category: StorageUsageStats.CategoryKey) {
|
init(_ category: StorageUsageStats.CategoryKey) {
|
||||||
@ -110,6 +115,13 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
self.selectedMessages = selectedMessages
|
self.selectedMessages = selectedMessages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convenience init() {
|
||||||
|
self.init(
|
||||||
|
selectedPeers: Set(),
|
||||||
|
selectedMessages: Set()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
static func ==(lhs: SelectionState, rhs: SelectionState) -> Bool {
|
static func ==(lhs: SelectionState, rhs: SelectionState) -> Bool {
|
||||||
if lhs.selectedPeers != rhs.selectedPeers {
|
if lhs.selectedPeers != rhs.selectedPeers {
|
||||||
return false
|
return false
|
||||||
@ -206,6 +218,8 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
private let scrollView: ScrollViewImpl
|
private let scrollView: ScrollViewImpl
|
||||||
|
|
||||||
private var currentStats: AllStorageUsageStats?
|
private var currentStats: AllStorageUsageStats?
|
||||||
|
private var existingCategories: Set<Category> = Set()
|
||||||
|
|
||||||
private var currentMessages: [MessageId: Message] = [:]
|
private var currentMessages: [MessageId: Message] = [:]
|
||||||
private var cacheSettings: CacheStorageSettings?
|
private var cacheSettings: CacheStorageSettings?
|
||||||
private var peerItems: StoragePeerListPanelComponent.Items?
|
private var peerItems: StoragePeerListPanelComponent.Items?
|
||||||
@ -215,6 +229,8 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
|
|
||||||
private var selectionState: SelectionState?
|
private var selectionState: SelectionState?
|
||||||
|
|
||||||
|
private var isClearing: Bool = false
|
||||||
|
|
||||||
private var selectedCategories: Set<Category> = Set()
|
private var selectedCategories: Set<Category> = Set()
|
||||||
private var isOtherCategoryExpanded: Bool = false
|
private var isOtherCategoryExpanded: Bool = false
|
||||||
|
|
||||||
@ -232,6 +248,9 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
|
|
||||||
private var chartAvatarNode: AvatarNode?
|
private var chartAvatarNode: AvatarNode?
|
||||||
|
|
||||||
|
private var doneStatusCircle: SimpleShapeLayer?
|
||||||
|
private var doneStatusNode: RadialStatusNode?
|
||||||
|
|
||||||
private let pieChartView = ComponentView<Empty>()
|
private let pieChartView = ComponentView<Empty>()
|
||||||
private let chartTotalLabel = ComponentView<Empty>()
|
private let chartTotalLabel = ComponentView<Empty>()
|
||||||
private let categoriesView = ComponentView<Empty>()
|
private let categoriesView = ComponentView<Empty>()
|
||||||
@ -246,6 +265,8 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
|
|
||||||
private var selectionPanel: ComponentView<Empty>?
|
private var selectionPanel: ComponentView<Empty>?
|
||||||
|
|
||||||
|
private var clearingNode: StorageUsageClearProgressOverlayNode?
|
||||||
|
|
||||||
private var component: StorageUsageScreenComponent?
|
private var component: StorageUsageScreenComponent?
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
private var navigationMetrics: (navigationHeight: CGFloat, statusBarHeight: CGFloat)?
|
private var navigationMetrics: (navigationHeight: CGFloat, statusBarHeight: CGFloat)?
|
||||||
@ -267,6 +288,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
|
|
||||||
self.navigationBackgroundView = BlurredBackgroundView(color: nil, enableBlur: true)
|
self.navigationBackgroundView = BlurredBackgroundView(color: nil, enableBlur: true)
|
||||||
self.navigationSeparatorLayer = SimpleLayer()
|
self.navigationSeparatorLayer = SimpleLayer()
|
||||||
|
self.navigationSeparatorLayer.opacity = 0.0
|
||||||
|
|
||||||
self.scrollView = ScrollViewImpl()
|
self.scrollView = ScrollViewImpl()
|
||||||
|
|
||||||
@ -434,7 +456,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.reloadStats(firstTime: true)
|
self.reloadStats(firstTime: true, completion: {})
|
||||||
}
|
}
|
||||||
|
|
||||||
var wasLockedAtPanels = false
|
var wasLockedAtPanels = false
|
||||||
@ -446,13 +468,20 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
|
|
||||||
let animationHint = transition.userData(AnimationHint.self)
|
let animationHint = transition.userData(AnimationHint.self)
|
||||||
|
|
||||||
if let animationHint, case .firstStatsUpdate = animationHint.value {
|
if let animationHint {
|
||||||
var alphaTransition = transition
|
|
||||||
if case .firstStatsUpdate = animationHint.value {
|
if case .firstStatsUpdate = animationHint.value {
|
||||||
alphaTransition = .easeInOut(duration: 0.25)
|
let alphaTransition: Transition = .easeInOut(duration: 0.25)
|
||||||
|
alphaTransition.setAlpha(view: self.scrollView, alpha: self.currentStats != nil ? 1.0 : 0.0)
|
||||||
|
alphaTransition.setAlpha(view: self.headerOffsetContainer, alpha: self.currentStats != nil ? 1.0 : 0.0)
|
||||||
|
} else if case .clearedItems = animationHint.value {
|
||||||
|
if let snapshotView = self.snapshotView(afterScreenUpdates: false) {
|
||||||
|
snapshotView.frame = self.bounds
|
||||||
|
self.addSubview(snapshotView)
|
||||||
|
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||||
|
snapshotView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
alphaTransition.setAlpha(view: self.scrollView, alpha: self.currentStats != nil ? 1.0 : 0.0)
|
|
||||||
alphaTransition.setAlpha(view: self.headerOffsetContainer, alpha: self.currentStats != nil ? 1.0 : 0.0)
|
|
||||||
} else {
|
} else {
|
||||||
transition.setAlpha(view: self.scrollView, alpha: self.currentStats != nil ? 1.0 : 0.0)
|
transition.setAlpha(view: self.scrollView, alpha: self.currentStats != nil ? 1.0 : 0.0)
|
||||||
transition.setAlpha(view: self.headerOffsetContainer, alpha: self.currentStats != nil ? 1.0 : 0.0)
|
transition.setAlpha(view: self.headerOffsetContainer, alpha: self.currentStats != nil ? 1.0 : 0.0)
|
||||||
@ -620,19 +649,17 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
.misc
|
.misc
|
||||||
]
|
]
|
||||||
|
|
||||||
if let animationHint, case .firstStatsUpdate = animationHint.value, let currentStats = self.currentStats {
|
if let _ = self.currentStats {
|
||||||
let contextStats: StorageUsageStats
|
if let animationHint {
|
||||||
if let peer = component.peer {
|
switch animationHint.value {
|
||||||
contextStats = currentStats.peers[peer.id]?.stats ?? StorageUsageStats(categories: [:])
|
case .firstStatsUpdate, .clearedItems:
|
||||||
} else {
|
self.selectedCategories = self.existingCategories
|
||||||
contextStats = currentStats.totalStats
|
|
||||||
}
|
|
||||||
|
|
||||||
for (category, value) in contextStats.categories {
|
|
||||||
if value.size != 0 {
|
|
||||||
self.selectedCategories.insert(StorageUsageScreenComponent.Category(category))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.selectedCategories.formIntersection(self.existingCategories)
|
||||||
|
} else {
|
||||||
|
self.selectedCategories.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
var chartItems: [PieChartComponent.ChartData.Item] = []
|
var chartItems: [PieChartComponent.ChartData.Item] = []
|
||||||
@ -697,7 +724,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
|
|
||||||
var chartCategoryColor = category.color
|
var chartCategoryColor = category.color
|
||||||
if !self.isOtherCategoryExpanded && otherCategories.contains(category) {
|
if !self.isOtherCategoryExpanded && otherCategories.contains(category) {
|
||||||
chartCategoryColor = Category.other.color
|
chartCategoryColor = Category.misc.color
|
||||||
}
|
}
|
||||||
|
|
||||||
chartItems.append(PieChartComponent.ChartData.Item(id: category, displayValue: categoryFraction, value: categoryChartFraction, color: chartCategoryColor))
|
chartItems.append(PieChartComponent.ChartData.Item(id: category, displayValue: categoryFraction, value: categoryChartFraction, color: chartCategoryColor))
|
||||||
@ -732,8 +759,36 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
let isSelected = otherListCategories.allSatisfy { item in
|
let isSelected = otherListCategories.allSatisfy { item in
|
||||||
return self.selectedCategories.contains(item.key)
|
return self.selectedCategories.contains(item.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let listColor: UIColor
|
||||||
|
if self.isOtherCategoryExpanded {
|
||||||
|
listColor = Category.other.color
|
||||||
|
} else {
|
||||||
|
listColor = Category.misc.color
|
||||||
|
}
|
||||||
|
|
||||||
listCategories.append(StorageCategoriesComponent.CategoryData(
|
listCategories.append(StorageCategoriesComponent.CategoryData(
|
||||||
key: Category.other, color: Category.other.color, title: Category.other.title(strings: environment.strings), size: totalOtherSize, sizeFraction: categoryFraction, isSelected: isSelected, subcategories: otherListCategories))
|
key: Category.other, color: listColor, title: Category.other.title(strings: environment.strings), size: totalOtherSize, sizeFraction: categoryFraction, isSelected: isSelected, subcategories: otherListCategories))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.isOtherCategoryExpanded {
|
||||||
|
var otherSum: CGFloat = 0.0
|
||||||
|
for i in 0 ..< chartItems.count {
|
||||||
|
if otherCategories.contains(chartItems[i].id) {
|
||||||
|
var itemValue = chartItems[i].value
|
||||||
|
if itemValue > 0.00001 {
|
||||||
|
itemValue = max(itemValue, 0.01)
|
||||||
|
}
|
||||||
|
otherSum += itemValue
|
||||||
|
if case .misc = chartItems[i].id {
|
||||||
|
} else {
|
||||||
|
chartItems[i].value = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let index = chartItems.firstIndex(where: { $0.id == .misc }) {
|
||||||
|
chartItems[index].value = otherSum
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let chartData = PieChartComponent.ChartData(items: chartItems)
|
let chartData = PieChartComponent.ChartData(items: chartItems)
|
||||||
@ -754,13 +809,76 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
transition.setFrame(view: pieChartComponentView, frame: pieChartFrame)
|
transition.setFrame(view: pieChartComponentView, frame: pieChartFrame)
|
||||||
|
transition.setAlpha(view: pieChartComponentView, alpha: listCategories.isEmpty ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
contentHeight += pieChartSize.height
|
if let _ = self.currentStats, listCategories.isEmpty {
|
||||||
|
let checkColor = UIColor(rgb: 0x34C759)
|
||||||
|
|
||||||
|
let doneStatusNode: RadialStatusNode
|
||||||
|
var animateIn = false
|
||||||
|
if let current = self.doneStatusNode {
|
||||||
|
doneStatusNode = current
|
||||||
|
} else {
|
||||||
|
doneStatusNode = RadialStatusNode(backgroundNodeColor: .clear)
|
||||||
|
self.doneStatusNode = doneStatusNode
|
||||||
|
self.addSubnode(doneStatusNode)
|
||||||
|
animateIn = true
|
||||||
|
}
|
||||||
|
let doneSize = CGSize(width: 100.0, height: 100.0)
|
||||||
|
doneStatusNode.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - doneSize.width) / 2.0), y: contentHeight), size: doneSize)
|
||||||
|
|
||||||
|
let doneStatusCircle: SimpleShapeLayer
|
||||||
|
if let current = self.doneStatusCircle {
|
||||||
|
doneStatusCircle = current
|
||||||
|
} else {
|
||||||
|
doneStatusCircle = SimpleShapeLayer()
|
||||||
|
self.doneStatusCircle = doneStatusCircle
|
||||||
|
self.layer.addSublayer(doneStatusCircle)
|
||||||
|
doneStatusCircle.opacity = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
if animateIn {
|
||||||
|
Queue.mainQueue().after(0.18, {
|
||||||
|
doneStatusNode.transitionToState(.check(checkColor), animated: true)
|
||||||
|
doneStatusCircle.opacity = 1.0
|
||||||
|
doneStatusCircle.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
doneStatusCircle.lineWidth = 6.0
|
||||||
|
doneStatusCircle.strokeColor = checkColor.cgColor
|
||||||
|
doneStatusCircle.fillColor = nil
|
||||||
|
doneStatusCircle.path = UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: doneStatusCircle.lineWidth * 0.5, y: doneStatusCircle.lineWidth * 0.5), size: CGSize(width: doneSize.width - doneStatusCircle.lineWidth * 0.5, height: doneSize.height - doneStatusCircle.lineWidth * 0.5))).cgPath
|
||||||
|
|
||||||
|
doneStatusCircle.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - doneSize.width) / 2.0), y: contentHeight), size: doneSize).insetBy(dx: -doneStatusCircle.lineWidth * 0.5, dy: -doneStatusCircle.lineWidth * 0.5)
|
||||||
|
|
||||||
|
contentHeight += doneSize.height
|
||||||
|
} else {
|
||||||
|
contentHeight += pieChartSize.height
|
||||||
|
|
||||||
|
if let doneStatusNode = self.doneStatusNode {
|
||||||
|
self.doneStatusNode = nil
|
||||||
|
doneStatusNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
if let doneStatusCircle = self.doneStatusCircle {
|
||||||
|
self.doneStatusCircle = nil
|
||||||
|
doneStatusCircle.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
contentHeight += 26.0
|
contentHeight += 26.0
|
||||||
|
|
||||||
|
let headerText: String
|
||||||
|
if listCategories.isEmpty {
|
||||||
|
headerText = "Storage Cleared"
|
||||||
|
} else if let peer = component.peer {
|
||||||
|
headerText = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
|
||||||
|
} else {
|
||||||
|
headerText = "Storage Usage"
|
||||||
|
}
|
||||||
let headerViewSize = self.headerView.update(
|
let headerViewSize = self.headerView.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(Text(text: "Storage Usage", font: Font.semibold(22.0), color: environment.theme.list.itemPrimaryTextColor)),
|
component: AnyComponent(Text(text: headerText, font: Font.semibold(22.0), color: environment.theme.list.itemPrimaryTextColor)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: floor((availableSize.width - navigationRightButtonMaxWidth * 2.0) / 0.8), height: 100.0)
|
containerSize: CGSize(width: floor((availableSize.width - navigationRightButtonMaxWidth * 2.0) / 0.8), height: 100.0)
|
||||||
)
|
)
|
||||||
@ -780,14 +898,78 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.freeTextColor)
|
let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.freeTextColor)
|
||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
|
var usageFraction: Double = 0.0
|
||||||
|
let totalUsageText: String
|
||||||
|
if listCategories.isEmpty {
|
||||||
|
totalUsageText = "All media can be re-downloaded from the Telegram cloud if you need it again."
|
||||||
|
} else if let currentStats = self.currentStats {
|
||||||
|
let contextStats: StorageUsageStats
|
||||||
|
if let peer = component.peer {
|
||||||
|
contextStats = currentStats.peers[peer.id]?.stats ?? StorageUsageStats(categories: [:])
|
||||||
|
} else {
|
||||||
|
contextStats = currentStats.totalStats
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalStatsSize: Int64 = 0
|
||||||
|
for (_, value) in contextStats.categories {
|
||||||
|
totalStatsSize += value.size
|
||||||
|
}
|
||||||
|
|
||||||
|
if let _ = component.peer {
|
||||||
|
var allStatsSize: Int64 = 0
|
||||||
|
for (_, value) in currentStats.totalStats.categories {
|
||||||
|
allStatsSize += value.size
|
||||||
|
}
|
||||||
|
|
||||||
|
let fraction: Double
|
||||||
|
if allStatsSize != 0 {
|
||||||
|
fraction = Double(totalStatsSize) / Double(allStatsSize)
|
||||||
|
} else {
|
||||||
|
fraction = 0.0
|
||||||
|
}
|
||||||
|
usageFraction = fraction
|
||||||
|
let fractionValue: Double = floor(fraction * 100.0 * 10.0) / 10.0
|
||||||
|
let fractionString: String
|
||||||
|
if fractionValue < 0.1 {
|
||||||
|
fractionString = "<0.1"
|
||||||
|
} else if abs(Double(Int(fractionValue)) - fractionValue) < 0.001 {
|
||||||
|
fractionString = "\(Int(fractionValue))"
|
||||||
|
} else {
|
||||||
|
fractionString = "\(fractionValue)"
|
||||||
|
}
|
||||||
|
|
||||||
|
totalUsageText = "This chat uses \(fractionString)% of your Telegram cache."
|
||||||
|
} else {
|
||||||
|
let fraction: Double
|
||||||
|
if currentStats.deviceFreeSpace != 0 && totalStatsSize != 0 {
|
||||||
|
fraction = Double(totalStatsSize) / Double(currentStats.deviceFreeSpace + totalStatsSize)
|
||||||
|
} else {
|
||||||
|
fraction = 0.0
|
||||||
|
}
|
||||||
|
usageFraction = fraction
|
||||||
|
let fractionValue: Double = floor(fraction * 100.0 * 10.0) / 10.0
|
||||||
|
let fractionString: String
|
||||||
|
if fractionValue < 0.1 {
|
||||||
|
fractionString = "<0.1"
|
||||||
|
} else if abs(Double(Int(fractionValue)) - fractionValue) < 0.001 {
|
||||||
|
fractionString = "\(Int(fractionValue))"
|
||||||
|
} else {
|
||||||
|
fractionString = "\(fractionValue)"
|
||||||
|
}
|
||||||
|
|
||||||
|
totalUsageText = "Telegram uses \(fractionString)% of your free disk space."
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
totalUsageText = " "
|
||||||
|
}
|
||||||
let headerDescriptionSize = self.headerDescriptionView.update(
|
let headerDescriptionSize = self.headerDescriptionView.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(MultilineTextComponent(text: .markdown(text: "Telegram users 9.7% of your free disk space.", attributes: MarkdownAttributes(
|
component: AnyComponent(MultilineTextComponent(text: .markdown(text: totalUsageText, attributes: MarkdownAttributes(
|
||||||
body: body,
|
body: body,
|
||||||
bold: bold,
|
bold: bold,
|
||||||
link: body,
|
link: body,
|
||||||
linkAttribute: { _ in nil }
|
linkAttribute: { _ in nil }
|
||||||
)), maximumNumberOfLines: 0)),
|
)), horizontalAlignment: .center, maximumNumberOfLines: 0)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 15.0 * 2.0, height: 10000.0)
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 15.0 * 2.0, height: 10000.0)
|
||||||
)
|
)
|
||||||
@ -807,12 +989,15 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
transition.setCornerRadius(layer: self.headerProgressBackgroundLayer, cornerRadius: headerProgressFrame.height * 0.5)
|
transition.setCornerRadius(layer: self.headerProgressBackgroundLayer, cornerRadius: headerProgressFrame.height * 0.5)
|
||||||
self.headerProgressBackgroundLayer.backgroundColor = environment.theme.list.itemAccentColor.withMultipliedAlpha(0.2).cgColor
|
self.headerProgressBackgroundLayer.backgroundColor = environment.theme.list.itemAccentColor.withMultipliedAlpha(0.2).cgColor
|
||||||
|
|
||||||
let headerProgress: CGFloat = 0.097
|
let headerProgress: CGFloat = usageFraction
|
||||||
transition.setFrame(layer: self.headerProgressForegroundLayer, frame: CGRect(origin: headerProgressFrame.origin, size: CGSize(width: floorToScreenPixels(headerProgress * headerProgressFrame.width), height: headerProgressFrame.height)))
|
transition.setFrame(layer: self.headerProgressForegroundLayer, frame: CGRect(origin: headerProgressFrame.origin, size: CGSize(width: max(headerProgressFrame.height, floorToScreenPixels(headerProgress * headerProgressFrame.width)), height: headerProgressFrame.height)))
|
||||||
transition.setCornerRadius(layer: self.headerProgressForegroundLayer, cornerRadius: headerProgressFrame.height * 0.5)
|
transition.setCornerRadius(layer: self.headerProgressForegroundLayer, cornerRadius: headerProgressFrame.height * 0.5)
|
||||||
self.headerProgressForegroundLayer.backgroundColor = environment.theme.list.itemAccentColor.cgColor
|
self.headerProgressForegroundLayer.backgroundColor = environment.theme.list.itemAccentColor.cgColor
|
||||||
contentHeight += 4.0
|
contentHeight += 4.0
|
||||||
|
|
||||||
|
transition.setAlpha(layer: self.headerProgressBackgroundLayer, alpha: listCategories.isEmpty ? 0.0 : 1.0)
|
||||||
|
transition.setAlpha(layer: self.headerProgressForegroundLayer, alpha: listCategories.isEmpty ? 0.0 : 1.0)
|
||||||
|
|
||||||
contentHeight += 24.0
|
contentHeight += 24.0
|
||||||
|
|
||||||
if let peer = component.peer {
|
if let peer = component.peer {
|
||||||
@ -831,6 +1016,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
|
|
||||||
chartAvatarNode.setPeer(context: component.context, theme: environment.theme, peer: peer, displayDimensions: avatarSize)
|
chartAvatarNode.setPeer(context: component.context, theme: environment.theme, peer: peer, displayDimensions: avatarSize)
|
||||||
}
|
}
|
||||||
|
transition.setAlpha(view: chartAvatarNode.view, alpha: listCategories.isEmpty ? 0.0 : 1.0)
|
||||||
} else {
|
} else {
|
||||||
let chartTotalLabelSize = self.chartTotalLabel.update(
|
let chartTotalLabelSize = self.chartTotalLabel.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
@ -841,6 +1027,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
self.scrollView.addSubview(chartTotalLabelView)
|
self.scrollView.addSubview(chartTotalLabelView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: chartTotalLabelView, frame: CGRect(origin: CGPoint(x: pieChartFrame.minX + floor((pieChartFrame.width - chartTotalLabelSize.width) / 2.0), y: pieChartFrame.minY + floor((pieChartFrame.height - chartTotalLabelSize.height) / 2.0)), size: chartTotalLabelSize))
|
transition.setFrame(view: chartTotalLabelView, frame: CGRect(origin: CGPoint(x: pieChartFrame.minX + floor((pieChartFrame.width - chartTotalLabelSize.width) / 2.0), y: pieChartFrame.minY + floor((pieChartFrame.height - chartTotalLabelSize.height) / 2.0)), size: chartTotalLabelSize))
|
||||||
|
transition.setAlpha(view: chartTotalLabelView, alpha: listCategories.isEmpty ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -858,17 +1045,18 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if key == Category.other {
|
if key == Category.other {
|
||||||
let otherCategories: [Category] = [.stickers, .avatars, .misc]
|
var otherCategories: [Category] = [.stickers, .avatars, .misc]
|
||||||
if otherCategories.allSatisfy(self.selectedCategories.contains) {
|
otherCategories = otherCategories.filter(self.existingCategories.contains)
|
||||||
for item in otherCategories {
|
if !otherCategories.isEmpty {
|
||||||
self.selectedCategories.remove(item)
|
if otherCategories.allSatisfy(self.selectedCategories.contains) {
|
||||||
|
for item in otherCategories {
|
||||||
|
self.selectedCategories.remove(item)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for item in otherCategories {
|
||||||
|
let _ = self.selectedCategories.insert(item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.selectedCategories.remove(Category.other)
|
|
||||||
} else {
|
|
||||||
for item in otherCategories {
|
|
||||||
let _ = self.selectedCategories.insert(item)
|
|
||||||
}
|
|
||||||
let _ = self.selectedCategories.insert(Category.other)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if self.selectedCategories.contains(key) {
|
if self.selectedCategories.contains(key) {
|
||||||
@ -1097,7 +1285,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.selectionState == nil {
|
if self.selectionState == nil {
|
||||||
self.selectionState = SelectionState(selectedPeers: Set(), selectedMessages: Set())
|
self.selectionState = SelectionState()
|
||||||
}
|
}
|
||||||
self.selectionState = self.selectionState?.toggleMessage(id: messageId)
|
self.selectionState = self.selectionState?.toggleMessage(id: messageId)
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
@ -1118,7 +1306,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.selectionState == nil {
|
if self.selectionState == nil {
|
||||||
self.selectionState = SelectionState(selectedPeers: Set(), selectedMessages: Set())
|
self.selectionState = SelectionState()
|
||||||
}
|
}
|
||||||
self.selectionState = self.selectionState?.toggleMessage(id: messageId)
|
self.selectionState = self.selectionState?.toggleMessage(id: messageId)
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
@ -1139,7 +1327,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.selectionState == nil {
|
if self.selectionState == nil {
|
||||||
self.selectionState = SelectionState(selectedPeers: Set(), selectedMessages: Set())
|
self.selectionState = SelectionState()
|
||||||
}
|
}
|
||||||
self.selectionState = self.selectionState?.toggleMessage(id: messageId)
|
self.selectionState = self.selectionState?.toggleMessage(id: messageId)
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
@ -1199,24 +1387,81 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
|
|
||||||
self.updateScrolling(transition: transition)
|
self.updateScrolling(transition: transition)
|
||||||
|
|
||||||
|
if self.isClearing {
|
||||||
|
let clearingNode: StorageUsageClearProgressOverlayNode
|
||||||
|
var animateIn = false
|
||||||
|
if let current = self.clearingNode {
|
||||||
|
clearingNode = current
|
||||||
|
} else {
|
||||||
|
animateIn = true
|
||||||
|
clearingNode = StorageUsageClearProgressOverlayNode(presentationData: component.context.sharedContext.currentPresentationData.with { $0 })
|
||||||
|
self.clearingNode = clearingNode
|
||||||
|
self.addSubnode(clearingNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
let clearingSize = CGSize(width: availableSize.width, height: availableSize.height)
|
||||||
|
clearingNode.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - clearingSize.width) / 2.0), y: floor((availableSize.height - clearingSize.height) / 2.0)), size: clearingSize)
|
||||||
|
clearingNode.updateLayout(size: clearingSize, transition: .immediate)
|
||||||
|
|
||||||
|
if animateIn {
|
||||||
|
clearingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.15)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let clearingNode = self.clearingNode {
|
||||||
|
self.clearingNode = nil
|
||||||
|
|
||||||
|
let animationTransition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut))
|
||||||
|
animationTransition.setAlpha(view: clearingNode.view, alpha: 0.0, completion: { [weak clearingNode] _ in
|
||||||
|
clearingNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return availableSize
|
return availableSize
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reloadStats(firstTime: Bool) {
|
private func reportClearedStorage(size: Int64) {
|
||||||
if let controller = self.controller?() as? StorageUsageScreen {
|
guard let component = self.component else {
|
||||||
controller.reloadParent?()
|
return
|
||||||
|
}
|
||||||
|
guard let controller = self.controller?() else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
controller.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(size, formatting: DataSizeStringFormatting(presentationData: presentationData)))", stringForDeviceType()).string), elevatedLayout: false, action: { _ in return false }), in: .window(.root))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func reloadStats(firstTime: Bool, completion: @escaping () -> Void) {
|
||||||
guard let component = self.component else {
|
guard let component = self.component else {
|
||||||
|
completion()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.statsDisposable = (component.context.engine.resources.collectStorageUsageStats()
|
self.statsDisposable = (component.context.engine.resources.collectStorageUsageStats()
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] stats in
|
|> deliverOnMainQueue).start(next: { [weak self] stats in
|
||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
|
completion()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.currentStats = stats
|
|
||||||
|
var existingCategories = Set<Category>()
|
||||||
|
let contextStats: StorageUsageStats
|
||||||
|
if let peer = component.peer {
|
||||||
|
contextStats = stats.peers[peer.id]?.stats ?? StorageUsageStats(categories: [:])
|
||||||
|
} else {
|
||||||
|
contextStats = stats.totalStats
|
||||||
|
}
|
||||||
|
for (category, value) in contextStats.categories {
|
||||||
|
if value.size != 0 {
|
||||||
|
existingCategories.insert(StorageUsageScreenComponent.Category(category))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstTime {
|
||||||
|
self.currentStats = stats
|
||||||
|
self.existingCategories = existingCategories
|
||||||
|
}
|
||||||
|
|
||||||
var peerItems: [StoragePeerListPanelComponent.Item] = []
|
var peerItems: [StoragePeerListPanelComponent.Item] = []
|
||||||
|
|
||||||
@ -1240,9 +1485,10 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.peerItems = StoragePeerListPanelComponent.Items(items: peerItems)
|
if firstTime {
|
||||||
|
self.peerItems = StoragePeerListPanelComponent.Items(items: peerItems)
|
||||||
self.state?.updated(transition: Transition(animation: .none).withUserData(AnimationHint(value: firstTime ? .firstStatsUpdate : .clearedItems)))
|
self.state?.updated(transition: Transition(animation: .none).withUserData(AnimationHint(value: .firstStatsUpdate)))
|
||||||
|
}
|
||||||
|
|
||||||
class RenderResult {
|
class RenderResult {
|
||||||
var messages: [MessageId: Message] = [:]
|
var messages: [MessageId: Message] = [:]
|
||||||
@ -1251,13 +1497,6 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
var musicItems: [StorageFileListPanelComponent.Item] = []
|
var musicItems: [StorageFileListPanelComponent.Item] = []
|
||||||
}
|
}
|
||||||
|
|
||||||
let contextStats: StorageUsageStats
|
|
||||||
if let peer = component.peer {
|
|
||||||
contextStats = stats.peers[peer.id]?.stats ?? StorageUsageStats(categories: [:])
|
|
||||||
} else {
|
|
||||||
contextStats = stats.totalStats
|
|
||||||
}
|
|
||||||
|
|
||||||
self.messagesDisposable = (component.context.engine.resources.renderStorageUsageStatsMessages(stats: contextStats, categories: [.files, .photos, .videos, .music], existingMessages: self.currentMessages)
|
self.messagesDisposable = (component.context.engine.resources.renderStorageUsageStatsMessages(stats: contextStats, categories: [.files, .photos, .videos, .music], existingMessages: self.currentMessages)
|
||||||
|> deliverOn(Queue())
|
|> deliverOn(Queue())
|
||||||
|> map { messages -> RenderResult in
|
|> map { messages -> RenderResult in
|
||||||
@ -1344,17 +1583,59 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
guard let self else {
|
guard let self, let component = self.component else {
|
||||||
|
completion()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !firstTime {
|
||||||
|
if let peer = component.peer, let controller = self.controller?() as? StorageUsageScreen, let childCompleted = controller.childCompleted {
|
||||||
|
let contextStats: StorageUsageStats = stats.peers[peer.id]?.stats ?? StorageUsageStats(categories: [:])
|
||||||
|
var totalSize: Int64 = 0
|
||||||
|
for (_, value) in contextStats.categories {
|
||||||
|
totalSize += value.size
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalSize == 0 {
|
||||||
|
childCompleted({ [weak self] in
|
||||||
|
completion()
|
||||||
|
|
||||||
|
if let self {
|
||||||
|
self.controller?()?.dismiss(animated: true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
childCompleted({})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !firstTime {
|
||||||
|
self.currentStats = stats
|
||||||
|
self.existingCategories = existingCategories
|
||||||
|
self.peerItems = StoragePeerListPanelComponent.Items(items: peerItems)
|
||||||
|
}
|
||||||
|
|
||||||
self.currentMessages = result.messages
|
self.currentMessages = result.messages
|
||||||
|
|
||||||
self.imageItems = StorageFileListPanelComponent.Items(items: result.imageItems)
|
self.imageItems = StorageFileListPanelComponent.Items(items: result.imageItems)
|
||||||
self.fileItems = StorageFileListPanelComponent.Items(items: result.fileItems)
|
self.fileItems = StorageFileListPanelComponent.Items(items: result.fileItems)
|
||||||
self.musicItems = StorageFileListPanelComponent.Items(items: result.musicItems)
|
self.musicItems = StorageFileListPanelComponent.Items(items: result.musicItems)
|
||||||
|
|
||||||
self.state?.updated(transition: Transition(animation: .none))
|
if self.selectionState != nil {
|
||||||
|
if result.imageItems.isEmpty && result.fileItems.isEmpty && result.musicItems.isEmpty && peerItems.isEmpty {
|
||||||
|
self.selectionState = nil
|
||||||
|
} else {
|
||||||
|
self.selectionState = SelectionState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isClearing = false
|
||||||
|
|
||||||
|
self.state?.updated(transition: Transition(animation: .none).withUserData(AnimationHint(value: .clearedItems)))
|
||||||
|
|
||||||
|
completion()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1368,11 +1649,13 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let childController = StorageUsageScreen(context: component.context, makeStorageUsageExceptionsScreen: component.makeStorageUsageExceptionsScreen, peer: peer)
|
let childController = StorageUsageScreen(context: component.context, makeStorageUsageExceptionsScreen: component.makeStorageUsageExceptionsScreen, peer: peer)
|
||||||
childController.reloadParent = { [weak self] in
|
childController.childCompleted = { [weak self] completed in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.reloadStats(firstTime: false)
|
self.reloadStats(firstTime: false, completion: {
|
||||||
|
completed()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
controller.push(childController)
|
controller.push(childController)
|
||||||
}
|
}
|
||||||
@ -1429,6 +1712,10 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
mappedCategories.append(.misc)
|
mappedCategories.append(.misc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.isClearing = true
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
|
||||||
let _ = (component.context.engine.resources.clearStorage(peerId: peerId, categories: mappedCategories)
|
let _ = (component.context.engine.resources.clearStorage(peerId: peerId, categories: mappedCategories)
|
||||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||||
guard let self, let component = self.component, let currentStats = self.currentStats else {
|
guard let self, let component = self.component, let currentStats = self.currentStats else {
|
||||||
@ -1469,41 +1756,84 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for category in categories {
|
self.reloadStats(firstTime: false, completion: { [weak self] in
|
||||||
self.selectedCategories.remove(category)
|
guard let self else {
|
||||||
}
|
return
|
||||||
self.selectionState = nil
|
}
|
||||||
|
if totalSize != 0 {
|
||||||
self.reloadStats(firstTime: false)
|
self.reportClearedStorage(size: totalSize)
|
||||||
|
}
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.45, curve: .spring)).withUserData(AnimationHint(value: .clearedItems)))
|
})
|
||||||
})
|
})
|
||||||
} else if !peers.isEmpty {
|
} else if !peers.isEmpty {
|
||||||
|
self.isClearing = true
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
|
||||||
|
var totalSize: Int64 = 0
|
||||||
|
if let peerItems = self.peerItems {
|
||||||
|
for item in peerItems.items {
|
||||||
|
if peers.contains(item.peer.id) {
|
||||||
|
totalSize += item.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let _ = (component.context.engine.resources.clearStorage(peerIds: peers)
|
let _ = (component.context.engine.resources.clearStorage(peerIds: peers)
|
||||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.selectionState = nil
|
self.reloadStats(firstTime: false, completion: { [weak self] in
|
||||||
self.reloadStats(firstTime: false)
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if totalSize != 0 {
|
||||||
|
self.reportClearedStorage(size: totalSize)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
} else if !messages.isEmpty {
|
} else if !messages.isEmpty {
|
||||||
var messageItems: [Message] = []
|
var messageItems: [Message] = []
|
||||||
|
var totalSize: Int64 = 0
|
||||||
|
|
||||||
|
let contextStats: StorageUsageStats
|
||||||
|
if let peer = component.peer {
|
||||||
|
contextStats = self.currentStats?.peers[peer.id]?.stats ?? StorageUsageStats(categories: [:])
|
||||||
|
} else {
|
||||||
|
contextStats = self.currentStats?.totalStats ?? StorageUsageStats(categories: [:])
|
||||||
|
}
|
||||||
|
|
||||||
for id in messages {
|
for id in messages {
|
||||||
if let message = self.currentMessages[id] {
|
if let message = self.currentMessages[id] {
|
||||||
messageItems.append(message)
|
messageItems.append(message)
|
||||||
|
|
||||||
|
for (_, value) in contextStats.categories {
|
||||||
|
if let size = value.messages[id] {
|
||||||
|
totalSize += size
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.isClearing = true
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
|
||||||
let _ = (component.context.engine.resources.clearStorage(messages: messageItems)
|
let _ = (component.context.engine.resources.clearStorage(messages: messageItems)
|
||||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.selectionState = nil
|
self.reloadStats(firstTime: false, completion: { [weak self] in
|
||||||
self.reloadStats(firstTime: false)
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalSize != 0 {
|
||||||
|
self.reportClearedStorage(size: totalSize)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1716,7 +2046,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
public final class StorageUsageScreen: ViewControllerComponentContainer {
|
public final class StorageUsageScreen: ViewControllerComponentContainer {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
|
||||||
fileprivate var reloadParent: (() -> Void)?
|
fileprivate var childCompleted: ((@escaping () -> Void) -> Void)?
|
||||||
|
|
||||||
public init(context: AccountContext, makeStorageUsageExceptionsScreen: @escaping (CacheStorageSettings.PeerStorageCategory) -> ViewController?, peer: EnginePeer? = nil) {
|
public init(context: AccountContext, makeStorageUsageExceptionsScreen: @escaping (CacheStorageSettings.PeerStorageCategory) -> ViewController?, peer: EnginePeer? = nil) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -1948,3 +2278,122 @@ private final class MultiplePeerAvatarsContextItemNode: ASDisplayNode, ContextMe
|
|||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class StorageUsageClearProgressOverlayNode: ASDisplayNode {
|
||||||
|
private let presentationData: PresentationData
|
||||||
|
|
||||||
|
private let blurredView: BlurredBackgroundView
|
||||||
|
private let animationNode: AnimatedStickerNode
|
||||||
|
private let progressTextNode: ImmediateTextNode
|
||||||
|
private let descriptionTextNode: ImmediateTextNode
|
||||||
|
private let progressBackgroundNode: ASDisplayNode
|
||||||
|
private let progressForegroundNode: ASDisplayNode
|
||||||
|
|
||||||
|
private let progressDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
private var validLayout: CGSize?
|
||||||
|
|
||||||
|
init(presentationData: PresentationData) {
|
||||||
|
self.presentationData = presentationData
|
||||||
|
|
||||||
|
self.blurredView = BlurredBackgroundView(color: presentationData.theme.list.plainBackgroundColor.withMultipliedAlpha(0.7), enableBlur: true)
|
||||||
|
|
||||||
|
self.animationNode = DefaultAnimatedStickerNodeImpl()
|
||||||
|
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "ClearCache"), width: 256, height: 256, playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
|
||||||
|
self.animationNode.visibility = true
|
||||||
|
|
||||||
|
self.progressTextNode = ImmediateTextNode()
|
||||||
|
self.progressTextNode.textAlignment = .center
|
||||||
|
|
||||||
|
self.descriptionTextNode = ImmediateTextNode()
|
||||||
|
self.descriptionTextNode.textAlignment = .center
|
||||||
|
self.descriptionTextNode.maximumNumberOfLines = 0
|
||||||
|
|
||||||
|
self.progressBackgroundNode = ASDisplayNode()
|
||||||
|
self.progressBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.controlAccentColor.withMultipliedAlpha(0.2)
|
||||||
|
self.progressBackgroundNode.cornerRadius = 3.0
|
||||||
|
|
||||||
|
self.progressForegroundNode = ASDisplayNode()
|
||||||
|
self.progressForegroundNode.backgroundColor = self.presentationData.theme.actionSheet.controlAccentColor
|
||||||
|
self.progressForegroundNode.cornerRadius = 3.0
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.view.addSubview(self.blurredView)
|
||||||
|
self.addSubnode(self.animationNode)
|
||||||
|
self.addSubnode(self.progressTextNode)
|
||||||
|
self.addSubnode(self.descriptionTextNode)
|
||||||
|
//self.addSubnode(self.progressBackgroundNode)
|
||||||
|
//self.addSubnode(self.progressForegroundNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.progressDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setProgressSignal(_ signal: Signal<Float, NoError>) {
|
||||||
|
self.progressDisposable.set((signal
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] progress in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.setProgress(progress)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
private var progress: Float = 0.0
|
||||||
|
private func setProgress(_ progress: Float) {
|
||||||
|
self.progress = progress
|
||||||
|
|
||||||
|
if let size = self.validLayout {
|
||||||
|
self.updateLayout(size: size, transition: .animated(duration: 0.5, curve: .linear))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.validLayout = size
|
||||||
|
|
||||||
|
transition.updateFrame(view: self.blurredView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
self.blurredView.update(size: size, transition: transition)
|
||||||
|
|
||||||
|
let inset: CGFloat = 24.0
|
||||||
|
let progressHeight: CGFloat = 6.0
|
||||||
|
let spacing: CGFloat = 16.0
|
||||||
|
|
||||||
|
let imageSide = min(160.0, size.height - 30.0)
|
||||||
|
let imageSize = CGSize(width: imageSide, height: imageSide)
|
||||||
|
|
||||||
|
let animationFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floorToScreenPixels((size.height - imageSize.height) / 2.0) - 50.0), size: imageSize)
|
||||||
|
self.animationNode.frame = animationFrame
|
||||||
|
self.animationNode.updateLayout(size: imageSize)
|
||||||
|
|
||||||
|
let progressFrame = CGRect(x: inset, y: size.height - inset - progressHeight, width: size.width - inset * 2.0, height: progressHeight)
|
||||||
|
self.progressBackgroundNode.frame = progressFrame
|
||||||
|
let progressForegroundFrame = CGRect(x: inset, y: size.height - inset - progressHeight, width: floorToScreenPixels(progressFrame.width * CGFloat(self.progress)), height: progressHeight)
|
||||||
|
if !self.progressForegroundNode.frame.origin.x.isZero {
|
||||||
|
transition.updateFrame(node: self.progressForegroundNode, frame: progressForegroundFrame, beginWithCurrentState: true)
|
||||||
|
} else {
|
||||||
|
self.progressForegroundNode.frame = progressForegroundFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
self.descriptionTextNode.attributedText = NSAttributedString(string: self.presentationData.strings.ClearCache_KeepOpenedDescription, font: Font.regular(15.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor)
|
||||||
|
let descriptionTextSize = self.descriptionTextNode.updateLayout(CGSize(width: size.width - inset * 3.0, height: size.height))
|
||||||
|
var descriptionTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - descriptionTextSize.width) / 2.0), y: animationFrame.maxY + 52.0), size: descriptionTextSize)
|
||||||
|
|
||||||
|
self.progressTextNode.attributedText = NSAttributedString(string: self.presentationData.strings.ClearCache_NoProgress, font: Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
|
||||||
|
let progressTextSize = self.progressTextNode.updateLayout(size)
|
||||||
|
var progressTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - progressTextSize.width) / 2.0), y: descriptionTextFrame.minY - spacing - progressTextSize.height), size: progressTextSize)
|
||||||
|
|
||||||
|
let availableHeight = progressTextFrame.minY
|
||||||
|
if availableHeight < 100.0 {
|
||||||
|
let offset = availableHeight / 2.0 - spacing
|
||||||
|
descriptionTextFrame = descriptionTextFrame.offsetBy(dx: 0.0, dy: -offset)
|
||||||
|
progressTextFrame = progressTextFrame.offsetBy(dx: 0.0, dy: -offset)
|
||||||
|
self.animationNode.alpha = 0.0
|
||||||
|
} else {
|
||||||
|
self.animationNode.alpha = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
self.progressTextNode.frame = progressTextFrame
|
||||||
|
self.descriptionTextNode.frame = descriptionTextFrame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user