Storage management improvements

This commit is contained in:
Ali 2022-12-30 20:03:35 +04:00
parent 3566377d68
commit bf5382c9b4
16 changed files with 383 additions and 15 deletions

View File

@ -791,6 +791,7 @@ public protocol SharedAccountContext: AnyObject {
func makeCreateGroupController(context: AccountContext, peerIds: [PeerId], initialTitle: String?, mode: CreateGroupMode, completion: ((PeerId, @escaping () -> Void) -> Void)?) -> ViewController func makeCreateGroupController(context: AccountContext, peerIds: [PeerId], initialTitle: String?, mode: CreateGroupMode, completion: ((PeerId, @escaping () -> Void) -> Void)?) -> ViewController
func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?) -> ViewController func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?) -> ViewController
func makePrivacyAndSecurityController(context: AccountContext) -> ViewController func makePrivacyAndSecurityController(context: AccountContext) -> ViewController
func makeStorageManagementController(context: AccountContext) -> ViewController
func navigateToChatController(_ params: NavigateToChatControllerParams) func navigateToChatController(_ params: NavigateToChatControllerParams)
func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController) func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController)
func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal<Never, NoError> func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal<Never, NoError>

View File

@ -185,7 +185,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }) }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {})
interaction.isInlineMode = isInlineMode interaction.isInlineMode = isInlineMode
let items = (0 ..< 2).map { _ -> ChatListItem in let items = (0 ..< 2).map { _ -> ChatListItem in

View File

@ -2004,6 +2004,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
interaction.openPeer(peer, peer, threadId, false) interaction.openPeer(peer, peer, threadId, false)
self.listNode.clearHighlightAnimated(true) self.listNode.clearHighlightAnimated(true)
}) })
}, openStorageManagement: {
}) })
chatListInteraction.isSearchMode = true chatListInteraction.isSearchMode = true
@ -3199,7 +3200,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode {
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }) }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {})
var isInlineMode = false var isInlineMode = false
if case .topics = key { if case .topics = key {
isInlineMode = false isInlineMode = false

View File

@ -91,6 +91,7 @@ public final class ChatListNodeInteraction {
let activateChatPreview: (ChatListItem, Int64?, ASDisplayNode, ContextGesture?, CGPoint?) -> Void let activateChatPreview: (ChatListItem, Int64?, ASDisplayNode, ContextGesture?, CGPoint?) -> Void
let present: (ViewController) -> Void let present: (ViewController) -> Void
let openForumThread: (EnginePeer.Id, Int64) -> Void let openForumThread: (EnginePeer.Id, Int64) -> Void
let openStorageManagement: () -> Void
public var searchTextHighightState: String? public var searchTextHighightState: String?
var highlightedChatLocation: ChatListHighlightedLocation? var highlightedChatLocation: ChatListHighlightedLocation?
@ -132,7 +133,8 @@ public final class ChatListNodeInteraction {
hidePsa: @escaping (EnginePeer.Id) -> Void, hidePsa: @escaping (EnginePeer.Id) -> Void,
activateChatPreview: @escaping (ChatListItem, Int64?, ASDisplayNode, ContextGesture?, CGPoint?) -> Void, activateChatPreview: @escaping (ChatListItem, Int64?, ASDisplayNode, ContextGesture?, CGPoint?) -> Void,
present: @escaping (ViewController) -> Void, present: @escaping (ViewController) -> Void,
openForumThread: @escaping (EnginePeer.Id, Int64) -> Void openForumThread: @escaping (EnginePeer.Id, Int64) -> Void,
openStorageManagement: @escaping () -> Void
) { ) {
self.activateSearch = activateSearch self.activateSearch = activateSearch
self.peerSelected = peerSelected self.peerSelected = peerSelected
@ -162,6 +164,7 @@ public final class ChatListNodeInteraction {
self.animationCache = animationCache self.animationCache = animationCache
self.animationRenderer = animationRenderer self.animationRenderer = animationRenderer
self.openForumThread = openForumThread self.openForumThread = openForumThread
self.openStorageManagement = openStorageManagement
} }
} }
@ -567,6 +570,10 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
), directionHint: entry.directionHint) ), directionHint: entry.directionHint)
case let .ArchiveIntro(presentationData): case let .ArchiveIntro(presentationData):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint) return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
case let .StorageInfo(presentationData, sizeFraction):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(theme: presentationData.theme, strings: presentationData.strings, sizeFraction: sizeFraction, action: { [weak nodeInteraction] in
nodeInteraction?.openStorageManagement()
}), directionHint: entry.directionHint)
} }
} }
} }
@ -778,6 +785,10 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
), directionHint: entry.directionHint) ), directionHint: entry.directionHint)
case let .ArchiveIntro(presentationData): case let .ArchiveIntro(presentationData):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint) return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
case let .StorageInfo(presentationData, sizeFraction):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(theme: presentationData.theme, strings: presentationData.strings, sizeFraction: sizeFraction, action: { [weak nodeInteraction] in
nodeInteraction?.openStorageManagement()
}), directionHint: entry.directionHint)
case .HeaderEntry: case .HeaderEntry:
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint) return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint)
case let .AdditionalCategory(index: _, id, title, image, appearance, selected, presentationData): case let .AdditionalCategory(index: _, id, title, image, appearance, selected, presentationData):
@ -1258,6 +1269,12 @@ public final class ChatListNode: ListView {
} }
self.peerSelected?(peer, threadId, true, true, nil) self.peerSelected?(peer, threadId, true, true, nil)
}) })
}, openStorageManagement: { [weak self] in
guard let self else {
return
}
let controller = self.context.sharedContext.makeStorageManagementController(context: self.context)
self.push?(controller)
}) })
nodeInteraction.isInlineMode = isInlineMode nodeInteraction.isInlineMode = isInlineMode
@ -1323,15 +1340,94 @@ public final class ChatListNode: ListView {
displayArchiveIntro = .single(false) displayArchiveIntro = .single(false)
} }
let storageInfo: Signal<Double?, NoError>
if case .chatList(groupId: .root) = location, chatListFilter == nil {
let storageBox = context.account.postbox.mediaBox.storageBox
storageInfo = storageBox.totalSize()
|> take(1)
|> mapToSignal { initialSize -> Signal<Double?, NoError> in
let fractionLimit: Double = 0.3
let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
let deviceFreeSpace = (systemAttributes?[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0
let initialFraction: Double
if deviceFreeSpace != 0 && initialSize != 0 {
initialFraction = Double(initialSize) / Double(deviceFreeSpace + initialSize)
} else {
initialFraction = 0.0
}
let initialReportSize: Double?
if initialFraction > fractionLimit {
initialReportSize = Double(initialSize)
} else {
initialReportSize = nil
}
final class ReportState {
var lastSize: Int64
init(lastSize: Int64) {
self.lastSize = lastSize
}
}
let state = Atomic(value: ReportState(lastSize: initialSize))
let updatedReportSize: Signal<Double?, NoError> = Signal { subscriber in
let disposable = storageBox.totalSize().start(next: { size in
let updatedSize = state.with { state -> Int64 in
if abs(initialSize - size) > 50 * 1024 * 1024 {
state.lastSize = size
return size
} else {
return -1
}
}
if updatedSize >= 0 {
let deviceFreeSpace = (systemAttributes?[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0
let updatedFraction: Double
if deviceFreeSpace != 0 && updatedSize != 0 {
updatedFraction = Double(updatedSize) / Double(deviceFreeSpace + updatedSize)
} else {
updatedFraction = 0.0
}
let updatedReportSize: Double?
if updatedFraction > fractionLimit {
updatedReportSize = Double(updatedSize)
} else {
updatedReportSize = nil
}
subscriber.putNext(updatedReportSize)
}
})
return ActionDisposable {
disposable.dispose()
}
}
return .single(initialReportSize)
|> then(
updatedReportSize
)
}
} else {
storageInfo = .single(nil)
}
let currentPeerId: EnginePeer.Id = context.account.peerId let currentPeerId: EnginePeer.Id = context.account.peerId
let chatListNodeViewTransition = combineLatest(queue: viewProcessingQueue, hideArchivedFolderByDefault, displayArchiveIntro, savedMessagesPeer, chatListViewUpdate, self.statePromise.get()) let chatListNodeViewTransition = combineLatest(queue: viewProcessingQueue, hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, savedMessagesPeer, chatListViewUpdate, self.statePromise.get())
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, savedMessagesPeer, updateAndFilter, state) -> Signal<ChatListNodeListViewTransition, NoError> in |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, savedMessagesPeer, updateAndFilter, state) -> Signal<ChatListNodeListViewTransition, NoError> in
let (update, filter) = updateAndFilter let (update, filter) = updateAndFilter
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault) let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode, chatListLocation: location) let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, storageInfo: storageInfo, mode: mode, chatListLocation: location)
let entries = rawEntries.filter { entry in let entries = rawEntries.filter { entry in
switch entry { switch entry {
case let .PeerEntry(peerEntry): case let .PeerEntry(peerEntry):
@ -2361,7 +2457,7 @@ public final class ChatListNode: ListView {
} else { } else {
break loop break loop
} }
case .ArchiveIntro, .HeaderEntry, .AdditionalCategory: case .ArchiveIntro, .StorageInfo, .HeaderEntry, .AdditionalCategory:
break break
} }
} }

View File

@ -13,6 +13,7 @@ enum ChatListNodeEntryId: Hashable {
case ThreadId(Int64) case ThreadId(Int64)
case GroupId(EngineChatList.Group) case GroupId(EngineChatList.Group)
case ArchiveIntro case ArchiveIntro
case StorageInfo
case additionalCategory(Int) case additionalCategory(Int)
} }
@ -234,6 +235,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
case HoleEntry(EngineMessage.Index, theme: PresentationTheme) case HoleEntry(EngineMessage.Index, theme: PresentationTheme)
case GroupReferenceEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, editing: Bool, unreadCount: Int, revealed: Bool, hiddenByDefault: Bool) case GroupReferenceEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, editing: Bool, unreadCount: Int, revealed: Bool, hiddenByDefault: Bool)
case ArchiveIntro(presentationData: ChatListPresentationData) case ArchiveIntro(presentationData: ChatListPresentationData)
case StorageInfo(presentationData: ChatListPresentationData, sizeFraction: Double)
case AdditionalCategory(index: Int, id: Int, title: String, image: UIImage?, appearance: ChatListNodeAdditionalCategory.Appearance, selected: Bool, presentationData: ChatListPresentationData) case AdditionalCategory(index: Int, id: Int, title: String, image: UIImage?, appearance: ChatListNodeAdditionalCategory.Appearance, selected: Bool, presentationData: ChatListPresentationData)
var sortIndex: ChatListNodeEntrySortIndex { var sortIndex: ChatListNodeEntrySortIndex {
@ -248,6 +250,8 @@ enum ChatListNodeEntry: Comparable, Identifiable {
return .index(index) return .index(index)
case .ArchiveIntro: case .ArchiveIntro:
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor)) return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor))
case .StorageInfo:
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor.successor))
case let .AdditionalCategory(index, _, _, _, _, _, _): case let .AdditionalCategory(index, _, _, _, _, _, _):
return .additionalCategory(index) return .additionalCategory(index)
} }
@ -270,6 +274,8 @@ enum ChatListNodeEntry: Comparable, Identifiable {
return .GroupId(groupId) return .GroupId(groupId)
case .ArchiveIntro: case .ArchiveIntro:
return .ArchiveIntro return .ArchiveIntro
case .StorageInfo:
return .StorageInfo
case let .AdditionalCategory(_, id, _, _, _, _, _): case let .AdditionalCategory(_, id, _, _, _, _, _):
return .additionalCategory(id) return .additionalCategory(id)
} }
@ -342,6 +348,18 @@ enum ChatListNodeEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case let .StorageInfo(lhsPresentationData, lhsInfo):
if case let .StorageInfo(rhsPresentationData, rhsInfo) = rhs {
if lhsPresentationData !== rhsPresentationData {
return false
}
if lhsInfo != rhsInfo {
return false
}
return true
} else {
return false
}
case let .AdditionalCategory(lhsIndex, lhsId, lhsTitle, lhsImage, lhsAppearance, lhsSelected, lhsPresentationData): case let .AdditionalCategory(lhsIndex, lhsId, lhsTitle, lhsImage, lhsAppearance, lhsSelected, lhsPresentationData):
if case let .AdditionalCategory(rhsIndex, rhsId, rhsTitle, rhsImage, rhsAppearance, rhsSelected, rhsPresentationData) = rhs { if case let .AdditionalCategory(rhsIndex, rhsId, rhsTitle, rhsImage, rhsAppearance, rhsSelected, rhsPresentationData) = rhs {
if lhsIndex != rhsIndex { if lhsIndex != rhsIndex {
@ -381,7 +399,7 @@ private func offsetPinnedIndex(_ index: EngineChatList.Item.Index, offset: UInt1
} }
} }
func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation) -> (entries: [ChatListNodeEntry], loading: Bool) { func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, storageInfo: Double?, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation) -> (entries: [ChatListNodeEntry], loading: Bool) {
var result: [ChatListNodeEntry] = [] var result: [ChatListNodeEntry] = []
var pinnedIndexOffset: UInt16 = 0 var pinnedIndexOffset: UInt16 = 0
@ -643,6 +661,9 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
if displayArchiveIntro { if displayArchiveIntro {
result.append(.ArchiveIntro(presentationData: state.presentationData)) result.append(.ArchiveIntro(presentationData: state.presentationData))
} }
if let storageInfo {
result.append(.StorageInfo(presentationData: state.presentationData, sizeFraction: storageInfo))
}
result.append(.HeaderEntry) result.append(.HeaderEntry)
} }

View File

@ -0,0 +1,164 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Postbox
import Display
import SwiftSignalKit
import TelegramPresentationData
import ListSectionHeaderNode
import AppBundle
class ChatListStorageInfoItem: ListViewItem {
let theme: PresentationTheme
let strings: PresentationStrings
let sizeFraction: Double
let action: () -> Void
let selectable: Bool = true
init(theme: PresentationTheme, strings: PresentationStrings, sizeFraction: Double, action: @escaping () -> Void) {
self.theme = theme
self.strings = strings
self.sizeFraction = sizeFraction
self.action = action
}
func selected(listView: ListView) {
listView.clearHighlightAnimated(true)
self.action()
}
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = ChatListStorageInfoItemNode()
let (nodeLayout, apply) = node.asyncLayout()(self, params, false)
node.insets = nodeLayout.insets
node.contentSize = nodeLayout.contentSize
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in
apply()
})
})
}
}
}
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
assert(node() is ChatListStorageInfoItemNode)
if let nodeValue = node() as? ChatListStorageInfoItemNode {
let layout = nodeValue.asyncLayout()
async {
let (nodeLayout, apply) = layout(self, params, nextItem == nil)
Queue.mainQueue().async {
completion(nodeLayout, { _ in
apply()
})
}
}
}
}
}
}
private let separatorHeight = 1.0 / UIScreen.main.scale
private let titleFont = Font.semibold(15.0)
private let textFont = Font.regular(15.0)
class ChatListStorageInfoItemNode: ListViewItemNode {
private let titleNode: TextNode
private let textNode: TextNode
private let arrowNode: ASImageNode
private let separatorNode: ASDisplayNode
private var item: ChatListStorageInfoItem?
required init() {
self.titleNode = TextNode()
self.textNode = TextNode()
self.arrowNode = ASImageNode()
self.separatorNode = ASDisplayNode()
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.separatorNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
self.addSubnode(self.arrowNode)
}
override func didLoad() {
super.didLoad()
}
override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
let layout = self.asyncLayout()
let (_, apply) = layout(item as! ChatListStorageInfoItem, params, nextItem == nil)
apply()
}
func asyncLayout() -> (_ item: ChatListStorageInfoItem, _ params: ListViewItemLayoutParams, _ isLast: Bool) -> (ListViewItemNodeLayout, () -> Void) {
let previousItem = self.item
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeTextLayout = TextNode.asyncLayout(self.textNode)
return { item, params, last in
let baseWidth = params.width - params.leftInset - params.rightInset
let _ = baseWidth
let sideInset: CGFloat = params.leftInset + 16.0
let height: CGFloat = 54.0
let rightInset: CGFloat = sideInset + 24.0
let themeUpdated = item.theme !== previousItem?.theme
//TODO:localize
let titleString = NSMutableAttributedString()
titleString.append(NSAttributedString(string: "Free up to ", font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor))
let sizeString = dataSizeString(Int64(item.sizeFraction), formatting: DataSizeStringFormatting(strings: item.strings, decimalSeparator: "."))
titleString.append(NSAttributedString(string: sizeString, font: titleFont, textColor: item.theme.rootController.navigationBar.accentTextColor))
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
//TODO:localize
let textLayout = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Clear storage space on your iPhone", font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: height), insets: UIEdgeInsets())
return (layout, { [weak self] in
if let strongSelf = self {
strongSelf.item = item
if themeUpdated {
strongSelf.backgroundColor = item.theme.chatList.pinnedItemBackgroundColor
strongSelf.separatorNode.backgroundColor = item.theme.chatList.itemSeparatorColor
strongSelf.arrowNode.image = PresentationResourcesItemList.disclosureArrowImage(item.theme)
}
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))
let _ = titleLayout.1()
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: sideInset, y: 9.0), size: titleLayout.0.size)
let _ = textLayout.1()
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: sideInset, y: strongSelf.titleNode.frame.maxY - 0.0), size: textLayout.0.size)
if let image = strongSelf.arrowNode.image {
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: layout.size.width - sideInset - image.size.width + 8.0, y: floor((layout.size.height - image.size.height) / 2.0)), size: image.size)
}
strongSelf.contentSize = layout.contentSize
strongSelf.insets = layout.insets
}
})
}
}
}

View File

@ -91,6 +91,7 @@ public final class HashtagSearchController: TelegramBaseController {
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, present: { _ in
}, openForumThread: { _, _ in }, openForumThread: { _, _ in
}, openStorageManagement: {
}) })
let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil) let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)

View File

@ -158,6 +158,9 @@ public final class StorageBox {
let contentTypeStatsTable: ValueBoxTable let contentTypeStatsTable: ValueBoxTable
let metadataTable: ValueBoxTable let metadataTable: ValueBoxTable
let totalSizeSubscribers = Bag<(Int64) -> Void>()
private var totalSize: Int64 = 0
init(queue: Queue, logger: StorageBox.Logger, basePath: String) { init(queue: Queue, logger: StorageBox.Logger, basePath: String) {
self.queue = queue self.queue = queue
self.logger = logger self.logger = logger
@ -183,6 +186,8 @@ public final class StorageBox {
self.metadataTable = ValueBoxTable(id: 21, keyType: .binary, compactValuesOnCreation: true) self.metadataTable = ValueBoxTable(id: 21, keyType: .binary, compactValuesOnCreation: true)
self.performUpdatesIfNeeded() self.performUpdatesIfNeeded()
self.updateTotalSize()
} }
private func performUpdatesIfNeeded() { private func performUpdatesIfNeeded() {
@ -230,6 +235,35 @@ public final class StorageBox {
}) })
} }
func updateTotalSize() {
self.valueBox.begin()
var totalSize: Int64 = 0
self.valueBox.scan(self.contentTypeStatsTable, values: { key, value in
var size: Int64 = 0
value.read(&size, offset: 0, length: 8)
totalSize += size
return true
})
self.valueBox.commit()
if self.totalSize != totalSize {
self.totalSize = totalSize
for f in self.totalSizeSubscribers.copyItems() {
f(totalSize)
}
}
}
func incrementalUpdateTotalSize() {
for f in self.totalSizeSubscribers.copyItems() {
f(totalSize)
}
}
func reset() { func reset() {
self.valueBox.begin() self.valueBox.begin()
@ -241,6 +275,8 @@ public final class StorageBox {
self.valueBox.removeAllFromTable(self.metadataTable) self.valueBox.removeAllFromTable(self.metadataTable)
self.valueBox.commit() self.valueBox.commit()
self.updateTotalSize()
} }
private func internalAddSize(contentType: UInt8, delta: Int64) { private func internalAddSize(contentType: UInt8, delta: Int64) {
@ -260,6 +296,8 @@ public final class StorageBox {
} }
self.valueBox.set(self.contentTypeStatsTable, key: key, value: MemoryBuffer(memory: &currentSize, capacity: 8, length: 8, freeWhenDone: false)) self.valueBox.set(self.contentTypeStatsTable, key: key, value: MemoryBuffer(memory: &currentSize, capacity: 8, length: 8, freeWhenDone: false))
self.totalSize += delta
} }
private func internalAddSize(peerId: Int64, contentType: UInt8, delta: Int64) { private func internalAddSize(peerId: Int64, contentType: UInt8, delta: Int64) {
@ -390,6 +428,8 @@ public final class StorageBox {
} }
self.valueBox.commit() self.valueBox.commit()
self.incrementalUpdateTotalSize()
} }
private func peerIdsReferencing(hashId: HashId) -> Set<Int64> { private func peerIdsReferencing(hashId: HashId) -> Set<Int64> {
@ -435,6 +475,8 @@ public final class StorageBox {
} }
self.valueBox.commit() self.valueBox.commit()
self.incrementalUpdateTotalSize()
} }
func addEmptyReferencesIfNotReferenced(ids: [(id: Data, size: Int64)], contentType: UInt8) -> Int { func addEmptyReferencesIfNotReferenced(ids: [(id: Data, size: Int64)], contentType: UInt8) -> Int {
@ -508,6 +550,8 @@ public final class StorageBox {
} }
self.valueBox.commit() self.valueBox.commit()
self.incrementalUpdateTotalSize()
} }
func allPeerIds() -> [PeerId] { func allPeerIds() -> [PeerId] {
@ -615,6 +659,22 @@ public final class StorageBox {
return (ids, nextId) return (ids, nextId)
} }
func subscribeTotalSize(next: @escaping (Int64) -> Void) -> Disposable {
let index = self.totalSizeSubscribers.add(next)
next(self.totalSize)
let queue = self.queue
return ActionDisposable { [weak self] in
queue.async {
guard let self else {
return
}
self.totalSizeSubscribers.remove(index)
}
}
}
func all() -> [Entry] { func all() -> [Entry] {
var result: [Entry] = [] var result: [Entry] = []
@ -825,6 +885,8 @@ public final class StorageBox {
self.valueBox.commit() self.valueBox.commit()
self.incrementalUpdateTotalSize()
return Array(resultIds) return Array(resultIds)
} }
@ -854,6 +916,8 @@ public final class StorageBox {
self.valueBox.commit() self.valueBox.commit()
self.incrementalUpdateTotalSize()
return Array(resultIds) return Array(resultIds)
} }
} }
@ -973,4 +1037,12 @@ public final class StorageBox {
return EmptyDisposable return EmptyDisposable
} }
} }
public func totalSize() -> Signal<Int64, NoError> {
return self.impl.signalWith { impl, subscriber in
return impl.subscribeTotalSize(next: { value in
subscriber.putNext(value)
})
}
}
} }

View File

@ -598,7 +598,6 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
pushControllerImpl?(StorageUsageScreen(context: context, makeStorageUsageExceptionsScreen: { category in pushControllerImpl?(StorageUsageScreen(context: context, makeStorageUsageExceptionsScreen: { category in
return storageUsageExceptionsScreen(context: context, category: category) return storageUsageExceptionsScreen(context: context, category: category)
})) }))
//pushControllerImpl?(storageUsageController(context: context, cacheUsagePromise: cacheUsagePromise))
}, openNetworkUsage: { }, openNetworkUsage: {
pushControllerImpl?(networkUsageStatsController(context: context)) pushControllerImpl?(networkUsageStatsController(context: context))
}, openProxy: { }, openProxy: {

View File

@ -222,7 +222,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in
}, activateChatPreview: { _, _, _, gesture, _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }) }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {})
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true) let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)

View File

@ -843,7 +843,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
}, activateChatPreview: { _, _, _, gesture, _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, present: { _ in
}, openForumThread: { _, _ in }) }, openForumThread: { _, _ in },
openStorageManagement: {})
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true) let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
func makeChatListItem( func makeChatListItem(

View File

@ -367,7 +367,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
}, activateChatPreview: { _, _, _, gesture, _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, present: { _ in
}, openForumThread: { _, _ in }) }, openForumThread: { _, _ in }, openStorageManagement: {})
func makeChatListItem( func makeChatListItem(
peer: EnginePeer, peer: EnginePeer,

View File

@ -351,6 +351,7 @@ swift_library(
"//submodules/TelegramUI/Components/ChatListHeaderComponent", "//submodules/TelegramUI/Components/ChatListHeaderComponent",
"//submodules/TelegramUI/Components/ChatInputNode", "//submodules/TelegramUI/Components/ChatInputNode",
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode", "//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
"//submodules/TelegramUI/Components/StorageUsageScreen",
"//submodules/MediaPasteboardUI:MediaPasteboardUI", "//submodules/MediaPasteboardUI:MediaPasteboardUI",
"//submodules/DrawingUI:DrawingUI", "//submodules/DrawingUI:DrawingUI",
"//submodules/FeaturedStickersScreen:FeaturedStickersScreen", "//submodules/FeaturedStickersScreen:FeaturedStickersScreen",

View File

@ -1163,11 +1163,11 @@ final class PieChartComponent: Component {
let fractionValue: Double = floor(item.displayValue * 100.0 * 10.0) / 10.0 let fractionValue: Double = floor(item.displayValue * 100.0 * 10.0) / 10.0
let fractionString: String let fractionString: String
if fractionValue < 0.1 { if fractionValue < 0.1 {
fractionString = "<0.1" fractionString = "<0.1%"
} else if abs(Double(Int(fractionValue)) - fractionValue) < 0.001 { } else if abs(Double(Int(fractionValue)) - fractionValue) < 0.001 {
fractionString = "\(Int(fractionValue))" fractionString = "\(Int(fractionValue))%"
} else { } else {
fractionString = "\(fractionValue)" fractionString = "\(fractionValue)%"
} }
let tooltipSize = tooltip.update( let tooltipSize = tooltip.update(

View File

@ -262,6 +262,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
} }
}, present: { _ in }, present: { _ in
}, openForumThread: { _, _ in }, openForumThread: { _, _ in
}, openStorageManagement: {
}) })
interaction.searchTextHighightState = searchQuery interaction.searchTextHighightState = searchQuery
self.interaction = interaction self.interaction = interaction

View File

@ -29,6 +29,7 @@ import PremiumUI
import StickerPackPreviewUI import StickerPackPreviewUI
import ChatControllerInteraction import ChatControllerInteraction
import ChatPresentationInterfaceState import ChatPresentationInterfaceState
import StorageUsageScreen
private final class AccountUserInterfaceInUseContext { private final class AccountUserInterfaceInUseContext {
let subscribers = Bag<(Bool) -> Void>() let subscribers = Bag<(Bool) -> Void>()
@ -1424,6 +1425,15 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return SettingsUI.makePrivacyAndSecurityController(context: context) return SettingsUI.makePrivacyAndSecurityController(context: context)
} }
public func makeStorageManagementController(context: AccountContext) -> ViewController {
return StorageUsageScreen(context: context, makeStorageUsageExceptionsScreen: { [weak context] category in
guard let context else {
return nil
}
return storageUsageExceptionsScreen(context: context, category: category)
})
}
public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController { public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController {
let mappedSource: PremiumSource let mappedSource: PremiumSource
switch source { switch source {