mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-02 12:48:45 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
ae0d70e52e
@ -7299,6 +7299,7 @@ Sorry for the inconvenience.";
|
||||
"Contacts.Sort.ByLastSeen" = "by Last Seen";
|
||||
|
||||
"ClearCache.Progress" = "Clearing the Cache • %d%";
|
||||
"ClearCache.NoProgress" = "Clearing the Cache";
|
||||
"ClearCache.KeepOpenedDescription" = "Please keep this window open until the clearing is completed.";
|
||||
|
||||
"Share.ShareAsLink" = "Share as Link";
|
||||
|
||||
@ -77,6 +77,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
case resetDatabaseAndCache(PresentationTheme)
|
||||
case resetHoles(PresentationTheme)
|
||||
case reindexUnread(PresentationTheme)
|
||||
case resetCacheIndex
|
||||
case reindexCache
|
||||
case resetBiometricsData(PresentationTheme)
|
||||
case resetWebViewCache(PresentationTheme)
|
||||
@ -112,7 +113,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return DebugControllerSection.logging.rawValue
|
||||
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
||||
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
|
||||
case .preferredVideoCodec:
|
||||
return DebugControllerSection.videoExperiments.rawValue
|
||||
@ -171,42 +172,44 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return 21
|
||||
case .reindexUnread:
|
||||
return 22
|
||||
case .reindexCache:
|
||||
case .resetCacheIndex:
|
||||
return 23
|
||||
case .resetBiometricsData:
|
||||
case .reindexCache:
|
||||
return 24
|
||||
case .resetWebViewCache:
|
||||
case .resetBiometricsData:
|
||||
return 25
|
||||
case .optimizeDatabase:
|
||||
case .resetWebViewCache:
|
||||
return 26
|
||||
case .photoPreview:
|
||||
case .optimizeDatabase:
|
||||
return 27
|
||||
case .knockoutWallpaper:
|
||||
case .photoPreview:
|
||||
return 28
|
||||
case .experimentalCompatibility:
|
||||
case .knockoutWallpaper:
|
||||
return 29
|
||||
case .enableDebugDataDisplay:
|
||||
case .experimentalCompatibility:
|
||||
return 30
|
||||
case .acceleratedStickers:
|
||||
case .enableDebugDataDisplay:
|
||||
return 31
|
||||
case .experimentalBackground:
|
||||
case .acceleratedStickers:
|
||||
return 32
|
||||
case .inlineForums:
|
||||
case .experimentalBackground:
|
||||
return 33
|
||||
case .localTranscription:
|
||||
case .inlineForums:
|
||||
return 34
|
||||
case .enableReactionOverrides:
|
||||
case .localTranscription:
|
||||
return 35
|
||||
case .restorePurchases:
|
||||
case .enableReactionOverrides:
|
||||
return 36
|
||||
case .playerEmbedding:
|
||||
case .restorePurchases:
|
||||
return 37
|
||||
case .playlistPlayback:
|
||||
case .playerEmbedding:
|
||||
return 38
|
||||
case .voiceConference:
|
||||
case .playlistPlayback:
|
||||
return 39
|
||||
case .voiceConference:
|
||||
return 40
|
||||
case let .preferredVideoCodec(index, _, _, _):
|
||||
return 40 + index
|
||||
return 41 + index
|
||||
case .disableVideoAspectScaling:
|
||||
return 100
|
||||
case .enableVoipTcp:
|
||||
@ -970,6 +973,14 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
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:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Reindex Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
@ -1253,6 +1264,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
||||
entries.append(.resetHoles(presentationData.theme))
|
||||
if isMainApp {
|
||||
entries.append(.reindexUnread(presentationData.theme))
|
||||
entries.append(.resetCacheIndex)
|
||||
entries.append(.reindexCache)
|
||||
entries.append(.resetWebViewCache(presentationData.theme))
|
||||
}
|
||||
|
||||
@ -338,15 +338,17 @@ final class DrawingSettings: Codable, Equatable {
|
||||
|
||||
private final class ReferenceContentSource: ContextReferenceContentSource {
|
||||
private let sourceView: UIView
|
||||
private let contentArea: CGRect
|
||||
private let customPosition: CGPoint
|
||||
|
||||
init(sourceView: UIView, customPosition: CGPoint) {
|
||||
init(sourceView: UIView, contentArea: CGRect, customPosition: CGPoint) {
|
||||
self.sourceView = sourceView
|
||||
self.contentArea = contentArea
|
||||
self.customPosition = customPosition
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, customPosition: customPosition)
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: self.contentArea, customPosition: customPosition)
|
||||
}
|
||||
}
|
||||
|
||||
@ -849,7 +851,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
||||
)
|
||||
]
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
|
||||
let contextController = ContextController(account: self.context.account, presentationData: presentationData, source: .reference(ReferenceContentSource(sourceView: sourceView, customPosition: CGPoint(x: 7.0, y: 3.0))), items: .single(ContextController.Items(content: .list(items))))
|
||||
let contextController = ContextController(account: self.context.account, presentationData: presentationData, source: .reference(ReferenceContentSource(sourceView: sourceView, contentArea: UIScreen.main.bounds, customPosition: CGPoint(x: 7.0, y: 3.0))), items: .single(ContextController.Items(content: .list(items))))
|
||||
self.present(contextController)
|
||||
}
|
||||
|
||||
@ -2294,18 +2296,18 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
||||
|
||||
private weak var currentFontPicker: ContextController?
|
||||
func presentFontPicker(sourceView: UIView) {
|
||||
guard !self.dismissFontPicker() else {
|
||||
guard !self.dismissFontPicker(), let validLayout = self.validLayout?.0 else {
|
||||
return
|
||||
}
|
||||
let fonts: [DrawingTextFont] = [
|
||||
.sanFrancisco,
|
||||
.newYork,
|
||||
.other("MarkerFelt-Wide", "Marker Felt"),
|
||||
.other("Chalkduster", "Chalkduster"),
|
||||
.other("Menlo-Bold", "Menlo"),
|
||||
.other("Copperplate-Bold", "Copperplate"),
|
||||
.other("GillSans-SemiBold", "Gill Sans"),
|
||||
.other("Papyrus", "Papyrus")
|
||||
.other("AmericanTypewriter", "American Typewriter"),
|
||||
.other("AvenirNext-DemiBoldItalic", "Avenir Next"),
|
||||
.other("CourierNewPS-BoldMT", "Courier New"),
|
||||
.other("Noteworthy-Bold", "Noteworthy"),
|
||||
.other("Georgia-Bold", "Georgia"),
|
||||
.other("Papyrus", "Papyrus"),
|
||||
.other("SnellRoundhand-Bold", "Snell Roundhand")
|
||||
]
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
@ -2325,7 +2327,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
||||
}
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
|
||||
let contextController = ContextController(account: self.context.account, presentationData: presentationData, source: .reference(ReferenceContentSource(sourceView: sourceView, customPosition: CGPoint(x: 7.0, y: 0.0))), items: .single(ContextController.Items(content: .list(items))))
|
||||
let contextController = ContextController(account: self.context.account, presentationData: presentationData, source: .reference(ReferenceContentSource(sourceView: sourceView, contentArea: CGRect(origin: .zero, size: CGSize(width: validLayout.size.width, height: validLayout.size.height - (validLayout.inputHeight ?? 0.0))), customPosition: CGPoint(x: 0.0, y: 1.0))), items: .single(ContextController.Items(content: .list(items))))
|
||||
self.controller?.present(contextController, in: .window(.root))
|
||||
self.currentFontPicker = contextController
|
||||
}
|
||||
|
||||
@ -78,7 +78,6 @@ public final class DrawingTextEntity: DrawingEntity, Codable {
|
||||
|
||||
enum Font: Codable {
|
||||
case sanFrancisco
|
||||
case newYork
|
||||
case other(String, String)
|
||||
}
|
||||
|
||||
@ -258,7 +257,7 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
|
||||
self.textView.minimumZoomScale = 1.0
|
||||
self.textView.maximumZoomScale = 1.0
|
||||
self.textView.keyboardAppearance = .dark
|
||||
self.textView.autocorrectionType = .default
|
||||
self.textView.autocorrectionType = .no
|
||||
self.textView.spellCheckingType = .no
|
||||
|
||||
super.init(context: context, entity: entity)
|
||||
@ -551,11 +550,9 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
|
||||
var font: UIFont
|
||||
switch self.textEntity.font {
|
||||
case .sanFrancisco:
|
||||
font = Font.with(size: fontSize, design: .regular, weight: .semibold)
|
||||
case .newYork:
|
||||
font = Font.with(size: fontSize, design: .serif, weight: .semibold)
|
||||
font = Font.with(size: fontSize, design: .round, weight: .semibold)
|
||||
case let .other(fontName, _):
|
||||
font = UIFont(name: fontName, size: fontSize) ?? Font.with(size: fontSize, design: .regular, weight: .semibold)
|
||||
font = UIFont(name: fontName, size: fontSize) ?? Font.with(size: fontSize, design: .round, weight: .semibold)
|
||||
}
|
||||
|
||||
text.addAttribute(.font, value: font, range: range)
|
||||
@ -1286,6 +1283,7 @@ private var availableFonts: [String: (String, String)] = {
|
||||
|
||||
var preferredFont: String?
|
||||
for name in names {
|
||||
print(name)
|
||||
let originalName = name
|
||||
let name = name.lowercased()
|
||||
if (!name.contains("-") || name.contains("regular")) && preferredFont == nil {
|
||||
@ -1301,6 +1299,6 @@ private var availableFonts: [String: (String, String)] = {
|
||||
result[shortname] = (preferredFont, family)
|
||||
}
|
||||
}
|
||||
print(result)
|
||||
//print(result)
|
||||
return result
|
||||
}()
|
||||
|
||||
@ -996,7 +996,7 @@ private class DrawingSlice {
|
||||
self.rect = rect
|
||||
self.path = NSTemporaryDirectory() + "/drawing_\(uuid.hashValue).slice"
|
||||
|
||||
DrawingSlice.queue.async {
|
||||
DrawingSlice.queue.after(2.0) {
|
||||
let image = UIImage(cgImage: image)
|
||||
if let data = image.pngData() as? NSData {
|
||||
try? data.write(toFile: self.path)
|
||||
|
||||
@ -198,7 +198,7 @@ final class PenTool: DrawingElement {
|
||||
let minRenderArrowLength = max(10.0, max(drawingSize.width, drawingSize.height) * 0.02)
|
||||
|
||||
self.renderLineWidth = lineWidth
|
||||
self.renderMinLineWidth = isEraser || isBlur ? lineWidth : minLineWidth + (lineWidth - minLineWidth) * 0.3
|
||||
self.renderMinLineWidth = isEraser || isBlur ? lineWidth : minLineWidth + (lineWidth - minLineWidth) * 0.2
|
||||
self.renderArrowLength = max(minRenderArrowLength, lineWidth * 3.0)
|
||||
self.renderArrowLineWidth = max(minLineWidth * 1.8, lineWidth * 0.75)
|
||||
|
||||
@ -234,14 +234,8 @@ final class PenTool: DrawingElement {
|
||||
guard case let .point(point) = path else {
|
||||
return
|
||||
}
|
||||
|
||||
let filterDistance: CGFloat
|
||||
if point.velocity > 1200.0 {
|
||||
filterDistance = 25.0
|
||||
} else {
|
||||
filterDistance = 15.0
|
||||
}
|
||||
|
||||
|
||||
let filterDistance: CGFloat = 20.0
|
||||
if let previousPoint, point.location.distance(to: previousPoint) < filterDistance, state == .changed, self.segments.count > 1 {
|
||||
return
|
||||
}
|
||||
@ -252,14 +246,13 @@ final class PenTool: DrawingElement {
|
||||
velocity = 1000.0
|
||||
}
|
||||
|
||||
var effectiveRenderLineWidth = max(self.renderMinLineWidth, min(self.renderLineWidth - (velocity / 150.0), self.renderLineWidth))
|
||||
var effectiveRenderLineWidth = max(self.renderMinLineWidth, min(self.renderLineWidth - (velocity / 100.0), self.renderLineWidth))
|
||||
if let previousRenderLineWidth = self.previousRenderLineWidth {
|
||||
effectiveRenderLineWidth = effectiveRenderLineWidth * 0.2 + previousRenderLineWidth * 0.8
|
||||
}
|
||||
self.previousRenderLineWidth = effectiveRenderLineWidth
|
||||
|
||||
let rect = append(point: Point(position: point.location, width: effectiveRenderLineWidth))
|
||||
|
||||
if let currentRenderView = self.currentRenderView as? RenderView, let rect = rect {
|
||||
currentRenderView.draw(element: self, rect: rect)
|
||||
}
|
||||
@ -461,7 +454,7 @@ final class PenTool: DrawingElement {
|
||||
|
||||
let segmentDistance: CGFloat = 6.0
|
||||
let distance = midPoint1.distance(to: midPoint2)
|
||||
let numberOfSegments = min(128, max(floor(distance / segmentDistance), 32))
|
||||
let numberOfSegments = min(48, max(floor(distance / segmentDistance), 24))
|
||||
|
||||
let step = 1.0 / numberOfSegments
|
||||
for t in stride(from: 0, to: 1, by: step) {
|
||||
|
||||
@ -46,15 +46,12 @@ enum DrawingTextAlignment: Equatable {
|
||||
|
||||
enum DrawingTextFont: Equatable, Hashable {
|
||||
case sanFrancisco
|
||||
case newYork
|
||||
case other(String, String)
|
||||
|
||||
init(font: DrawingTextEntity.Font) {
|
||||
switch font {
|
||||
case .sanFrancisco:
|
||||
self = .sanFrancisco
|
||||
case .newYork:
|
||||
self = .newYork
|
||||
case let .other(font, name):
|
||||
self = .other(font, name)
|
||||
}
|
||||
@ -64,8 +61,6 @@ enum DrawingTextFont: Equatable, Hashable {
|
||||
switch self {
|
||||
case .sanFrancisco:
|
||||
return .sanFrancisco
|
||||
case .newYork:
|
||||
return .newYork
|
||||
case let .other(font, name):
|
||||
return .other(font, name)
|
||||
}
|
||||
@ -75,8 +70,6 @@ enum DrawingTextFont: Equatable, Hashable {
|
||||
switch self {
|
||||
case .sanFrancisco:
|
||||
return "San Francisco"
|
||||
case .newYork:
|
||||
return "New York"
|
||||
case let .other(_, name):
|
||||
return name
|
||||
}
|
||||
@ -85,9 +78,7 @@ enum DrawingTextFont: Equatable, Hashable {
|
||||
func uiFont(size: CGFloat) -> UIFont {
|
||||
switch self {
|
||||
case .sanFrancisco:
|
||||
return Font.semibold(size)
|
||||
case .newYork:
|
||||
return Font.with(size: size, design: .serif, weight: .semibold)
|
||||
return Font.with(size: size, design: .round, weight: .semibold)
|
||||
case let .other(font, _):
|
||||
return UIFont(name: font, size: size) ?? Font.semibold(size)
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
let key = ValueBoxKey(length: 1)
|
||||
key.setUInt8(0, value: contentType)
|
||||
@ -893,4 +906,10 @@ public final class StorageBox {
|
||||
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 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.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(
|
||||
deviceAvailableSpace: deviceAvailableSpace,
|
||||
deviceFreeSpace: deviceFreeSpace,
|
||||
totalStats: total,
|
||||
peers: peers
|
||||
)
|
||||
@ -257,7 +267,8 @@ func _internal_renderStorageUsageStatsMessages(account: Account, stats: StorageU
|
||||
if !categories.contains(category) {
|
||||
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 let message = existingMessages[id] {
|
||||
result[id] = message
|
||||
|
||||
@ -33,6 +33,10 @@ swift_library(
|
||||
"//submodules/AvatarNode",
|
||||
"//submodules/PhotoResources",
|
||||
"//submodules/SemanticStatusNode",
|
||||
"//submodules/RadialStatusNode",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -87,12 +87,12 @@ private func processChartData(data: PieChartComponent.ChartData) -> PieChartComp
|
||||
final class PieChartComponent: Component {
|
||||
struct ChartData: Equatable {
|
||||
struct Item: Equatable {
|
||||
var id: AnyHashable
|
||||
var id: StorageUsageScreenComponent.Category
|
||||
var displayValue: Double
|
||||
var value: Double
|
||||
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.displayValue = displayValue
|
||||
self.value = value
|
||||
@ -131,12 +131,12 @@ final class PieChartComponent: Component {
|
||||
private final class ChartDataView: UIView {
|
||||
private(set) var theme: PresentationTheme?
|
||||
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 animator: DisplayLinkAnimator?
|
||||
|
||||
private var labels: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
private var labels: [StorageUsageScreenComponent.Category: ComponentView<Empty>] = [:]
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
@ -153,7 +153,7 @@ final class PieChartComponent: Component {
|
||||
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)
|
||||
|
||||
if self.theme !== theme || self.data != data || self.selectedKey != selectedKey {
|
||||
@ -366,7 +366,7 @@ final class PieChartComponent: Component {
|
||||
|
||||
var minDistance: CGFloat = 1000.0
|
||||
for distance in distances {
|
||||
minDistance = min(minDistance, distance + 1.0)
|
||||
minDistance = min(minDistance, distance)
|
||||
}
|
||||
|
||||
let diagonalAngle = atan2(labelSize.height, labelSize.width)
|
||||
@ -467,8 +467,8 @@ final class PieChartComponent: Component {
|
||||
|
||||
class View: UIView {
|
||||
private let dataView: ChartDataView
|
||||
private var labels: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
var selectedKey: AnyHashable?
|
||||
private var labels: [StorageUsageScreenComponent.Category: ComponentView<Empty>] = [:]
|
||||
var selectedKey: StorageUsageScreenComponent.Category?
|
||||
|
||||
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 {
|
||||
let themeUpdated = self.component?.theme !== component.theme
|
||||
let themeUpdated = self.component?.theme !== component.theme || self.component?.category.color != component.category.color
|
||||
|
||||
self.component = component
|
||||
|
||||
@ -276,7 +276,21 @@ final class StorageCategoryItemComponent: Component {
|
||||
transition.setFrame(view: labelView, frame: labelFrame)
|
||||
}
|
||||
|
||||
var copyCheckLayer: CheckLayer?
|
||||
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(
|
||||
backgroundColor: component.category.color,
|
||||
strokeColor: component.theme.list.itemCheckColors.foregroundColor,
|
||||
@ -289,7 +303,11 @@ final class StorageCategoryItemComponent: Component {
|
||||
|
||||
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(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)))
|
||||
|
||||
|
||||
@ -381,6 +381,7 @@ final class StorageUsagePanelContainerComponent: Component {
|
||||
|
||||
class View: UIView, UIGestureRecognizerDelegate {
|
||||
private let topPanelBackgroundView: UIView
|
||||
private let topPanelMergedBackgroundView: UIView
|
||||
private let topPanelSeparatorLayer: SimpleLayer
|
||||
private let header = ComponentView<Empty>()
|
||||
|
||||
@ -396,6 +397,10 @@ final class StorageUsagePanelContainerComponent: Component {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.topPanelBackgroundView = UIView()
|
||||
|
||||
self.topPanelMergedBackgroundView = UIView()
|
||||
self.topPanelMergedBackgroundView.alpha = 0.0
|
||||
|
||||
self.topPanelSeparatorLayer = SimpleLayer()
|
||||
|
||||
self.panelsBackgroundLayer = SimpleLayer()
|
||||
@ -404,6 +409,7 @@ final class StorageUsagePanelContainerComponent: Component {
|
||||
|
||||
self.layer.addSublayer(self.panelsBackgroundLayer)
|
||||
self.addSubview(self.topPanelBackgroundView)
|
||||
self.addSubview(self.topPanelMergedBackgroundView)
|
||||
self.layer.addSublayer(self.topPanelSeparatorLayer)
|
||||
|
||||
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
|
||||
@ -531,6 +537,11 @@ final class StorageUsagePanelContainerComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
func updateNavigationMergeFactor(value: CGFloat, transition: Transition) {
|
||||
transition.setAlpha(view: self.topPanelMergedBackgroundView, alpha: value)
|
||||
transition.setAlpha(view: self.topPanelBackgroundView, alpha: 1.0 - value)
|
||||
}
|
||||
|
||||
func update(component: StorageUsagePanelContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StorageUsagePanelContainerEnvironment>, transition: Transition) -> CGSize {
|
||||
let environment = environment[StorageUsagePanelContainerEnvironment.self].value
|
||||
|
||||
@ -542,20 +553,15 @@ final class StorageUsagePanelContainerComponent: Component {
|
||||
if themeUpdated {
|
||||
self.panelsBackgroundLayer.backgroundColor = component.theme.list.itemBlocksBackgroundColor.cgColor
|
||||
self.topPanelSeparatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor
|
||||
self.topPanelBackgroundView.backgroundColor = component.theme.list.itemBlocksBackgroundColor
|
||||
self.topPanelMergedBackgroundView.backgroundColor = component.theme.rootController.navigationBar.blurredBackgroundColor
|
||||
}
|
||||
|
||||
let topPanelCoverHeight: CGFloat = 10.0
|
||||
|
||||
let topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: -topPanelCoverHeight), size: CGSize(width: availableSize.width, height: 44.0))
|
||||
transition.setFrame(view: self.topPanelBackgroundView, frame: topPanelFrame)
|
||||
|
||||
let lockScrollingTransition: Transition
|
||||
if themeUpdated {
|
||||
lockScrollingTransition = transition
|
||||
} else {
|
||||
lockScrollingTransition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
lockScrollingTransition.setBackgroundColor(view: self.topPanelBackgroundView, color: environment.isScrollable ? component.theme.rootController.navigationBar.blurredBackgroundColor : component.theme.list.itemBlocksBackgroundColor)
|
||||
transition.setFrame(view: self.topPanelMergedBackgroundView, frame: topPanelFrame)
|
||||
|
||||
transition.setFrame(layer: self.panelsBackgroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelFrame.maxY)))
|
||||
|
||||
|
||||
@ -16,6 +16,11 @@ import Markdown
|
||||
import ContextUI
|
||||
import AnimatedAvatarSetNode
|
||||
import AvatarNode
|
||||
import RadialStatusNode
|
||||
import UndoUI
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import TelegramStringFormatting
|
||||
|
||||
private extension StorageUsageScreenComponent.Category {
|
||||
init(_ category: StorageUsageStats.CategoryKey) {
|
||||
@ -110,6 +115,13 @@ final class StorageUsageScreenComponent: Component {
|
||||
self.selectedMessages = selectedMessages
|
||||
}
|
||||
|
||||
convenience init() {
|
||||
self.init(
|
||||
selectedPeers: Set(),
|
||||
selectedMessages: Set()
|
||||
)
|
||||
}
|
||||
|
||||
static func ==(lhs: SelectionState, rhs: SelectionState) -> Bool {
|
||||
if lhs.selectedPeers != rhs.selectedPeers {
|
||||
return false
|
||||
@ -206,6 +218,8 @@ final class StorageUsageScreenComponent: Component {
|
||||
private let scrollView: ScrollViewImpl
|
||||
|
||||
private var currentStats: AllStorageUsageStats?
|
||||
private var existingCategories: Set<Category> = Set()
|
||||
|
||||
private var currentMessages: [MessageId: Message] = [:]
|
||||
private var cacheSettings: CacheStorageSettings?
|
||||
private var peerItems: StoragePeerListPanelComponent.Items?
|
||||
@ -215,11 +229,14 @@ final class StorageUsageScreenComponent: Component {
|
||||
|
||||
private var selectionState: SelectionState?
|
||||
|
||||
private var isClearing: Bool = false
|
||||
|
||||
private var selectedCategories: Set<Category> = Set()
|
||||
private var isOtherCategoryExpanded: Bool = false
|
||||
|
||||
private let navigationBackgroundView: BlurredBackgroundView
|
||||
private let navigationSeparatorLayer: SimpleLayer
|
||||
private let navigationSeparatorLayerContainer: SimpleLayer
|
||||
private let navigationEditButton = ComponentView<Empty>()
|
||||
private let navigationDoneButton = ComponentView<Empty>()
|
||||
|
||||
@ -232,6 +249,9 @@ final class StorageUsageScreenComponent: Component {
|
||||
|
||||
private var chartAvatarNode: AvatarNode?
|
||||
|
||||
private var doneStatusCircle: SimpleShapeLayer?
|
||||
private var doneStatusNode: RadialStatusNode?
|
||||
|
||||
private let pieChartView = ComponentView<Empty>()
|
||||
private let chartTotalLabel = ComponentView<Empty>()
|
||||
private let categoriesView = ComponentView<Empty>()
|
||||
@ -246,6 +266,8 @@ final class StorageUsageScreenComponent: Component {
|
||||
|
||||
private var selectionPanel: ComponentView<Empty>?
|
||||
|
||||
private var clearingNode: StorageUsageClearProgressOverlayNode?
|
||||
|
||||
private var component: StorageUsageScreenComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
private var navigationMetrics: (navigationHeight: CGFloat, statusBarHeight: CGFloat)?
|
||||
@ -266,7 +288,12 @@ final class StorageUsageScreenComponent: Component {
|
||||
self.headerOffsetContainer.isUserInteractionEnabled = false
|
||||
|
||||
self.navigationBackgroundView = BlurredBackgroundView(color: nil, enableBlur: true)
|
||||
self.navigationBackgroundView.alpha = 0.0
|
||||
|
||||
self.navigationSeparatorLayer = SimpleLayer()
|
||||
self.navigationSeparatorLayer.opacity = 0.0
|
||||
self.navigationSeparatorLayerContainer = SimpleLayer()
|
||||
self.navigationSeparatorLayerContainer.opacity = 0.0
|
||||
|
||||
self.scrollView = ScrollViewImpl()
|
||||
|
||||
@ -302,7 +329,9 @@ final class StorageUsageScreenComponent: Component {
|
||||
self.scrollView.layer.addSublayer(self.headerProgressForegroundLayer)
|
||||
|
||||
self.addSubview(self.navigationBackgroundView)
|
||||
self.layer.addSublayer(self.navigationSeparatorLayer)
|
||||
|
||||
self.navigationSeparatorLayerContainer.addSublayer(self.navigationSeparatorLayer)
|
||||
self.layer.addSublayer(self.navigationSeparatorLayerContainer)
|
||||
|
||||
self.addSubview(self.headerOffsetContainer)
|
||||
}
|
||||
@ -334,10 +363,9 @@ final class StorageUsageScreenComponent: Component {
|
||||
}
|
||||
|
||||
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||
guard let navigationMetrics = self.navigationMetrics else {
|
||||
guard let _ = self.navigationMetrics else {
|
||||
return
|
||||
}
|
||||
let _ = navigationMetrics
|
||||
|
||||
let paneAreaExpansionDistance: CGFloat = 32.0
|
||||
let paneAreaExpansionFinalPoint: CGFloat = scrollView.contentSize.height - scrollView.bounds.height
|
||||
@ -377,6 +405,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
let navigationBackgroundAlpha: CGFloat = abs(headerOffset - minOffset) < 4.0 ? 1.0 : 0.0
|
||||
|
||||
animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha)
|
||||
animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha)
|
||||
|
||||
if let navigationEditButtonView = self.navigationEditButton.view {
|
||||
animatedTransition.setAlpha(view: navigationEditButtonView, alpha: (self.selectionState == nil ? 1.0 : 0.0) * navigationBackgroundAlpha)
|
||||
@ -385,10 +414,13 @@ final class StorageUsageScreenComponent: Component {
|
||||
animatedTransition.setAlpha(view: navigationDoneButtonView, alpha: (self.selectionState == nil ? 0.0 : 1.0) * navigationBackgroundAlpha)
|
||||
}
|
||||
|
||||
if abs(headerOffset - minOffset) < 4.0 && !isLockedAtPanels {
|
||||
animatedTransition.setAlpha(layer: self.navigationSeparatorLayer, alpha: 1.0)
|
||||
} else {
|
||||
animatedTransition.setAlpha(layer: self.navigationSeparatorLayer, alpha: 0.0)
|
||||
let expansionDistance: CGFloat = 32.0
|
||||
var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance
|
||||
expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor))
|
||||
|
||||
transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor)
|
||||
if let panelContainerView = self.panelContainer.view as? StorageUsagePanelContainerComponent.View {
|
||||
panelContainerView.updateNavigationMergeFactor(value: 1.0 - expansionDistanceFactor, transition: transition)
|
||||
}
|
||||
|
||||
var offsetFraction: CGFloat = abs(headerOffset - minOffset) / 60.0
|
||||
@ -434,7 +466,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
}
|
||||
})
|
||||
|
||||
self.reloadStats(firstTime: true)
|
||||
self.reloadStats(firstTime: true, completion: {})
|
||||
}
|
||||
|
||||
var wasLockedAtPanels = false
|
||||
@ -446,13 +478,20 @@ final class StorageUsageScreenComponent: Component {
|
||||
|
||||
let animationHint = transition.userData(AnimationHint.self)
|
||||
|
||||
if let animationHint, case .firstStatsUpdate = animationHint.value {
|
||||
var alphaTransition = transition
|
||||
if let animationHint {
|
||||
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 {
|
||||
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)
|
||||
@ -468,7 +507,11 @@ final class StorageUsageScreenComponent: Component {
|
||||
self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
|
||||
self.navigationBackgroundView.update(size: navigationFrame.size, transition: transition.containedViewLayoutTransition)
|
||||
transition.setFrame(view: self.navigationBackgroundView, frame: navigationFrame)
|
||||
transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
|
||||
|
||||
let navigationSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel))
|
||||
|
||||
transition.setFrame(layer: self.navigationSeparatorLayerContainer, frame: navigationSeparatorFrame)
|
||||
transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(), size: navigationSeparatorFrame.size))
|
||||
|
||||
let navigationEditButtonSize = self.navigationEditButton.update(
|
||||
transition: transition,
|
||||
@ -620,19 +663,17 @@ final class StorageUsageScreenComponent: Component {
|
||||
.misc
|
||||
]
|
||||
|
||||
if let animationHint, case .firstStatsUpdate = animationHint.value, let currentStats = self.currentStats {
|
||||
let contextStats: StorageUsageStats
|
||||
if let peer = component.peer {
|
||||
contextStats = currentStats.peers[peer.id]?.stats ?? StorageUsageStats(categories: [:])
|
||||
} else {
|
||||
contextStats = currentStats.totalStats
|
||||
}
|
||||
|
||||
for (category, value) in contextStats.categories {
|
||||
if value.size != 0 {
|
||||
self.selectedCategories.insert(StorageUsageScreenComponent.Category(category))
|
||||
if let _ = self.currentStats {
|
||||
if let animationHint {
|
||||
switch animationHint.value {
|
||||
case .firstStatsUpdate, .clearedItems:
|
||||
self.selectedCategories = self.existingCategories
|
||||
}
|
||||
}
|
||||
|
||||
self.selectedCategories.formIntersection(self.existingCategories)
|
||||
} else {
|
||||
self.selectedCategories.removeAll()
|
||||
}
|
||||
|
||||
var chartItems: [PieChartComponent.ChartData.Item] = []
|
||||
@ -697,7 +738,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
|
||||
var chartCategoryColor = category.color
|
||||
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))
|
||||
@ -732,8 +773,36 @@ final class StorageUsageScreenComponent: Component {
|
||||
let isSelected = otherListCategories.allSatisfy { item in
|
||||
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(
|
||||
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)
|
||||
@ -754,13 +823,76 @@ final class StorageUsageScreenComponent: Component {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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(
|
||||
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: {},
|
||||
containerSize: CGSize(width: floor((availableSize.width - navigationRightButtonMaxWidth * 2.0) / 0.8), height: 100.0)
|
||||
)
|
||||
@ -780,14 +912,78 @@ final class StorageUsageScreenComponent: Component {
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.freeTextColor)
|
||||
|
||||
//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(
|
||||
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,
|
||||
bold: bold,
|
||||
link: body,
|
||||
linkAttribute: { _ in nil }
|
||||
)), maximumNumberOfLines: 0)),
|
||||
)), horizontalAlignment: .center, maximumNumberOfLines: 0)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 15.0 * 2.0, height: 10000.0)
|
||||
)
|
||||
@ -807,12 +1003,15 @@ final class StorageUsageScreenComponent: Component {
|
||||
transition.setCornerRadius(layer: self.headerProgressBackgroundLayer, cornerRadius: headerProgressFrame.height * 0.5)
|
||||
self.headerProgressBackgroundLayer.backgroundColor = environment.theme.list.itemAccentColor.withMultipliedAlpha(0.2).cgColor
|
||||
|
||||
let headerProgress: CGFloat = 0.097
|
||||
transition.setFrame(layer: self.headerProgressForegroundLayer, frame: CGRect(origin: headerProgressFrame.origin, size: CGSize(width: floorToScreenPixels(headerProgress * headerProgressFrame.width), height: headerProgressFrame.height)))
|
||||
let headerProgress: CGFloat = usageFraction
|
||||
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)
|
||||
self.headerProgressForegroundLayer.backgroundColor = environment.theme.list.itemAccentColor.cgColor
|
||||
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
|
||||
|
||||
if let peer = component.peer {
|
||||
@ -831,6 +1030,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
|
||||
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 {
|
||||
let chartTotalLabelSize = self.chartTotalLabel.update(
|
||||
transition: transition,
|
||||
@ -841,6 +1041,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
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.setAlpha(view: chartTotalLabelView, alpha: listCategories.isEmpty ? 0.0 : 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -858,17 +1059,18 @@ final class StorageUsageScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
if key == Category.other {
|
||||
let otherCategories: [Category] = [.stickers, .avatars, .misc]
|
||||
if otherCategories.allSatisfy(self.selectedCategories.contains) {
|
||||
for item in otherCategories {
|
||||
self.selectedCategories.remove(item)
|
||||
var otherCategories: [Category] = [.stickers, .avatars, .misc]
|
||||
otherCategories = otherCategories.filter(self.existingCategories.contains)
|
||||
if !otherCategories.isEmpty {
|
||||
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 {
|
||||
if self.selectedCategories.contains(key) {
|
||||
@ -1097,7 +1299,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
if self.selectionState == nil {
|
||||
self.selectionState = SelectionState(selectedPeers: Set(), selectedMessages: Set())
|
||||
self.selectionState = SelectionState()
|
||||
}
|
||||
self.selectionState = self.selectionState?.toggleMessage(id: messageId)
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
@ -1118,7 +1320,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
if self.selectionState == nil {
|
||||
self.selectionState = SelectionState(selectedPeers: Set(), selectedMessages: Set())
|
||||
self.selectionState = SelectionState()
|
||||
}
|
||||
self.selectionState = self.selectionState?.toggleMessage(id: messageId)
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
@ -1139,7 +1341,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
if self.selectionState == nil {
|
||||
self.selectionState = SelectionState(selectedPeers: Set(), selectedMessages: Set())
|
||||
self.selectionState = SelectionState()
|
||||
}
|
||||
self.selectionState = self.selectionState?.toggleMessage(id: messageId)
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
@ -1199,24 +1401,81 @@ final class StorageUsageScreenComponent: Component {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
private func reloadStats(firstTime: Bool) {
|
||||
if let controller = self.controller?() as? StorageUsageScreen {
|
||||
controller.reloadParent?()
|
||||
private func reportClearedStorage(size: Int64) {
|
||||
guard let component = self.component else {
|
||||
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 {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
self.statsDisposable = (component.context.engine.resources.collectStorageUsageStats()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] stats in
|
||||
guard let self, let component = self.component else {
|
||||
completion()
|
||||
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] = []
|
||||
|
||||
@ -1240,9 +1499,10 @@ final class StorageUsageScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
self.peerItems = StoragePeerListPanelComponent.Items(items: peerItems)
|
||||
|
||||
self.state?.updated(transition: Transition(animation: .none).withUserData(AnimationHint(value: firstTime ? .firstStatsUpdate : .clearedItems)))
|
||||
if firstTime {
|
||||
self.peerItems = StoragePeerListPanelComponent.Items(items: peerItems)
|
||||
self.state?.updated(transition: Transition(animation: .none).withUserData(AnimationHint(value: .firstStatsUpdate)))
|
||||
}
|
||||
|
||||
class RenderResult {
|
||||
var messages: [MessageId: Message] = [:]
|
||||
@ -1251,13 +1511,6 @@ final class StorageUsageScreenComponent: Component {
|
||||
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)
|
||||
|> deliverOn(Queue())
|
||||
|> map { messages -> RenderResult in
|
||||
@ -1344,17 +1597,59 @@ final class StorageUsageScreenComponent: Component {
|
||||
return result
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let self else {
|
||||
guard let self, let component = self.component else {
|
||||
completion()
|
||||
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.imageItems = StorageFileListPanelComponent.Items(items: result.imageItems)
|
||||
self.fileItems = StorageFileListPanelComponent.Items(items: result.fileItems)
|
||||
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 +1663,13 @@ final class StorageUsageScreenComponent: Component {
|
||||
}
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
self.reloadStats(firstTime: false)
|
||||
self.reloadStats(firstTime: false, completion: {
|
||||
completed()
|
||||
})
|
||||
}
|
||||
controller.push(childController)
|
||||
}
|
||||
@ -1429,6 +1726,10 @@ final class StorageUsageScreenComponent: Component {
|
||||
mappedCategories.append(.misc)
|
||||
}
|
||||
}
|
||||
|
||||
self.isClearing = true
|
||||
self.state?.updated(transition: .immediate)
|
||||
|
||||
let _ = (component.context.engine.resources.clearStorage(peerId: peerId, categories: mappedCategories)
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
guard let self, let component = self.component, let currentStats = self.currentStats else {
|
||||
@ -1469,41 +1770,84 @@ final class StorageUsageScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
for category in categories {
|
||||
self.selectedCategories.remove(category)
|
||||
}
|
||||
self.selectionState = nil
|
||||
|
||||
self.reloadStats(firstTime: false)
|
||||
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.45, curve: .spring)).withUserData(AnimationHint(value: .clearedItems)))
|
||||
self.reloadStats(firstTime: false, completion: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if totalSize != 0 {
|
||||
self.reportClearedStorage(size: totalSize)
|
||||
}
|
||||
})
|
||||
})
|
||||
} 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)
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.selectionState = nil
|
||||
self.reloadStats(firstTime: false)
|
||||
self.reloadStats(firstTime: false, completion: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if totalSize != 0 {
|
||||
self.reportClearedStorage(size: totalSize)
|
||||
}
|
||||
})
|
||||
})
|
||||
} else if !messages.isEmpty {
|
||||
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 {
|
||||
if let message = self.currentMessages[id] {
|
||||
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)
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.selectionState = nil
|
||||
self.reloadStats(firstTime: false)
|
||||
self.reloadStats(firstTime: false, completion: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if totalSize != 0 {
|
||||
self.reportClearedStorage(size: totalSize)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1716,7 +2060,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
public final class StorageUsageScreen: ViewControllerComponentContainer {
|
||||
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) {
|
||||
self.context = context
|
||||
@ -1948,3 +2292,122 @@ private final class MultiplePeerAvatarsContextItemNode: ASDisplayNode, ContextMe
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -1379,7 +1379,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
||||
}))
|
||||
|
||||
let setText: String
|
||||
if user.photo.first?.isPersonal == true {
|
||||
if user.photo.first?.isPersonal == true || state.updatingAvatar != nil {
|
||||
setText = presentationData.strings.UserInfo_ChangeCustomPhoto(compactName).string
|
||||
} else {
|
||||
setText = presentationData.strings.UserInfo_SetCustomPhoto(compactName).string
|
||||
@ -3117,7 +3117,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
let firstName = strongSelf.headerNode.editingContentNode.editingTextForKey(.firstName) ?? ""
|
||||
let lastName = strongSelf.headerNode.editingContentNode.editingTextForKey(.lastName) ?? ""
|
||||
|
||||
if peer.firstName != firstName || peer.lastName != lastName {
|
||||
if (peer.firstName ?? "") != firstName || (peer.lastName ?? "") != lastName {
|
||||
if firstName.isEmpty && lastName.isEmpty {
|
||||
if strongSelf.hapticFeedback == nil {
|
||||
strongSelf.hapticFeedback = HapticFeedback()
|
||||
@ -7179,7 +7179,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
confirmationAction = nil
|
||||
}
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasDeleteButton, hasViewButton: false, personalPhoto: strongSelf.isSettings, isVideo: currentIsVideo, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: isForum, title: title, isSuggesting: mode == .suggest)!
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasDeleteButton, hasViewButton: false, personalPhoto: strongSelf.isSettings, isVideo: currentIsVideo, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: isForum, title: title, isSuggesting: [.custom, .suggest].contains(mode))!
|
||||
mixin.stickersContext = paintStickersContext
|
||||
let _ = strongSelf.currentAvatarMixin.swap(mixin)
|
||||
mixin.requestSearchController = { [weak self] assetsController in
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user