diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 0d139dc0fd..706d2a2559 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -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 makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?) -> ViewController func makePrivacyAndSecurityController(context: AccountContext) -> ViewController + func makeStorageManagementController(context: AccountContext) -> ViewController func navigateToChatController(_ params: NavigateToChatControllerParams) 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 diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 4a098de8ec..ac4db22ac0 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -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 }, 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() - }, present: { _ in }, openForumThread: { _, _ in }) + }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}) interaction.isInlineMode = isInlineMode let items = (0 ..< 2).map { _ -> ChatListItem in diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index c19d67c859..25d43653be 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -2004,6 +2004,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { interaction.openPeer(peer, peer, threadId, false) self.listNode.clearHighlightAnimated(true) }) + }, openStorageManagement: { }) 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 }, 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() - }, present: { _ in }, openForumThread: { _, _ in }) + }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}) var isInlineMode = false if case .topics = key { isInlineMode = false diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index a672e32274..1a8d0a3f4c 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -91,6 +91,7 @@ public final class ChatListNodeInteraction { let activateChatPreview: (ChatListItem, Int64?, ASDisplayNode, ContextGesture?, CGPoint?) -> Void let present: (ViewController) -> Void let openForumThread: (EnginePeer.Id, Int64) -> Void + let openStorageManagement: () -> Void public var searchTextHighightState: String? var highlightedChatLocation: ChatListHighlightedLocation? @@ -132,7 +133,8 @@ public final class ChatListNodeInteraction { hidePsa: @escaping (EnginePeer.Id) -> Void, activateChatPreview: @escaping (ChatListItem, Int64?, ASDisplayNode, ContextGesture?, CGPoint?) -> Void, present: @escaping (ViewController) -> Void, - openForumThread: @escaping (EnginePeer.Id, Int64) -> Void + openForumThread: @escaping (EnginePeer.Id, Int64) -> Void, + openStorageManagement: @escaping () -> Void ) { self.activateSearch = activateSearch self.peerSelected = peerSelected @@ -162,6 +164,7 @@ public final class ChatListNodeInteraction { self.animationCache = animationCache self.animationRenderer = animationRenderer self.openForumThread = openForumThread + self.openStorageManagement = openStorageManagement } } @@ -567,6 +570,10 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL ), directionHint: entry.directionHint) case let .ArchiveIntro(presentationData): 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) case let .ArchiveIntro(presentationData): 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: return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint) 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) }) + }, openStorageManagement: { [weak self] in + guard let self else { + return + } + let controller = self.context.sharedContext.makeStorageManagementController(context: self.context) + self.push?(controller) }) nodeInteraction.isInlineMode = isInlineMode @@ -1323,15 +1340,94 @@ public final class ChatListNode: ListView { displayArchiveIntro = .single(false) } + let storageInfo: Signal + if case .chatList(groupId: .root) = location, chatListFilter == nil { + let storageBox = context.account.postbox.mediaBox.storageBox + storageInfo = storageBox.totalSize() + |> take(1) + |> mapToSignal { initialSize -> Signal 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 = 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 chatListNodeViewTransition = combineLatest(queue: viewProcessingQueue, hideArchivedFolderByDefault, displayArchiveIntro, savedMessagesPeer, chatListViewUpdate, self.statePromise.get()) - |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, savedMessagesPeer, updateAndFilter, state) -> Signal in + let chatListNodeViewTransition = combineLatest(queue: viewProcessingQueue, hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, savedMessagesPeer, chatListViewUpdate, self.statePromise.get()) + |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, savedMessagesPeer, updateAndFilter, state) -> Signal in let (update, filter) = updateAndFilter 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 switch entry { case let .PeerEntry(peerEntry): @@ -2361,7 +2457,7 @@ public final class ChatListNode: ListView { } else { break loop } - case .ArchiveIntro, .HeaderEntry, .AdditionalCategory: + case .ArchiveIntro, .StorageInfo, .HeaderEntry, .AdditionalCategory: break } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 571ccd6a23..d9de6b9510 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -13,6 +13,7 @@ enum ChatListNodeEntryId: Hashable { case ThreadId(Int64) case GroupId(EngineChatList.Group) case ArchiveIntro + case StorageInfo case additionalCategory(Int) } @@ -234,6 +235,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { 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 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) var sortIndex: ChatListNodeEntrySortIndex { @@ -248,6 +250,8 @@ enum ChatListNodeEntry: Comparable, Identifiable { return .index(index) case .ArchiveIntro: 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, _, _, _, _, _, _): return .additionalCategory(index) } @@ -270,6 +274,8 @@ enum ChatListNodeEntry: Comparable, Identifiable { return .GroupId(groupId) case .ArchiveIntro: return .ArchiveIntro + case .StorageInfo: + return .StorageInfo case let .AdditionalCategory(_, id, _, _, _, _, _): return .additionalCategory(id) } @@ -342,6 +348,18 @@ enum ChatListNodeEntry: Comparable, Identifiable { } else { 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): if case let .AdditionalCategory(rhsIndex, rhsId, rhsTitle, rhsImage, rhsAppearance, rhsSelected, rhsPresentationData) = rhs { 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 pinnedIndexOffset: UInt16 = 0 @@ -643,6 +661,9 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState if displayArchiveIntro { result.append(.ArchiveIntro(presentationData: state.presentationData)) } + if let storageInfo { + result.append(.StorageInfo(presentationData: state.presentationData, sizeFraction: storageInfo)) + } result.append(.HeaderEntry) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift new file mode 100644 index 0000000000..1fdd1cd32a --- /dev/null +++ b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift @@ -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?, (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 + } + }) + } + } +} diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index e2cc9fd261..4269a0d8e4 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -91,6 +91,7 @@ public final class HashtagSearchController: TelegramBaseController { gesture?.cancel() }, present: { _ in }, openForumThread: { _, _ in + }, openStorageManagement: { }) let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil) diff --git a/submodules/Postbox/Sources/StorageBox/StorageBox.swift b/submodules/Postbox/Sources/StorageBox/StorageBox.swift index b70f733cfa..930b8933a2 100644 --- a/submodules/Postbox/Sources/StorageBox/StorageBox.swift +++ b/submodules/Postbox/Sources/StorageBox/StorageBox.swift @@ -158,6 +158,9 @@ public final class StorageBox { let contentTypeStatsTable: ValueBoxTable let metadataTable: ValueBoxTable + let totalSizeSubscribers = Bag<(Int64) -> Void>() + private var totalSize: Int64 = 0 + init(queue: Queue, logger: StorageBox.Logger, basePath: String) { self.queue = queue self.logger = logger @@ -183,6 +186,8 @@ public final class StorageBox { self.metadataTable = ValueBoxTable(id: 21, keyType: .binary, compactValuesOnCreation: true) self.performUpdatesIfNeeded() + + self.updateTotalSize() } 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() { self.valueBox.begin() @@ -241,6 +275,8 @@ public final class StorageBox { self.valueBox.removeAllFromTable(self.metadataTable) self.valueBox.commit() + + self.updateTotalSize() } 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: ¤tSize, capacity: 8, length: 8, freeWhenDone: false)) + + self.totalSize += delta } private func internalAddSize(peerId: Int64, contentType: UInt8, delta: Int64) { @@ -390,6 +428,8 @@ public final class StorageBox { } self.valueBox.commit() + + self.incrementalUpdateTotalSize() } private func peerIdsReferencing(hashId: HashId) -> Set { @@ -435,6 +475,8 @@ public final class StorageBox { } self.valueBox.commit() + + self.incrementalUpdateTotalSize() } func addEmptyReferencesIfNotReferenced(ids: [(id: Data, size: Int64)], contentType: UInt8) -> Int { @@ -508,6 +550,8 @@ public final class StorageBox { } self.valueBox.commit() + + self.incrementalUpdateTotalSize() } func allPeerIds() -> [PeerId] { @@ -615,6 +659,22 @@ public final class StorageBox { 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] { var result: [Entry] = [] @@ -825,6 +885,8 @@ public final class StorageBox { self.valueBox.commit() + self.incrementalUpdateTotalSize() + return Array(resultIds) } @@ -854,6 +916,8 @@ public final class StorageBox { self.valueBox.commit() + self.incrementalUpdateTotalSize() + return Array(resultIds) } } @@ -973,4 +1037,12 @@ public final class StorageBox { return EmptyDisposable } } + + public func totalSize() -> Signal { + return self.impl.signalWith { impl, subscriber in + return impl.subscribeTotalSize(next: { value in + subscriber.putNext(value) + }) + } + } } diff --git a/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift index c6228fbbd4..b0722eba61 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift @@ -598,7 +598,6 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da pushControllerImpl?(StorageUsageScreen(context: context, makeStorageUsageExceptionsScreen: { category in return storageUsageExceptionsScreen(context: context, category: category) })) - //pushControllerImpl?(storageUsageController(context: context, cacheUsagePromise: cacheUsagePromise)) }, openNetworkUsage: { pushControllerImpl?(networkUsageStatsController(context: context)) }, openProxy: { diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 8321e35880..b5fa7159d1 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -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 }, activateChatPreview: { _, _, _, gesture, _ in 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) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 1fda96884f..2741d9e64c 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -843,7 +843,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() }, 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) func makeChatListItem( diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index c1724ac735..a29dc13b84 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -367,7 +367,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() }, present: { _ in - }, openForumThread: { _, _ in }) + }, openForumThread: { _, _ in }, openStorageManagement: {}) func makeChatListItem( peer: EnginePeer, diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 5e24fa5ba7..81ee929b36 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -351,6 +351,7 @@ swift_library( "//submodules/TelegramUI/Components/ChatListHeaderComponent", "//submodules/TelegramUI/Components/ChatInputNode", "//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode", + "//submodules/TelegramUI/Components/StorageUsageScreen", "//submodules/MediaPasteboardUI:MediaPasteboardUI", "//submodules/DrawingUI:DrawingUI", "//submodules/FeaturedStickersScreen:FeaturedStickersScreen", diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/PieChartComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/PieChartComponent.swift index a4487ca02d..ad48e6b6da 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/PieChartComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/PieChartComponent.swift @@ -1163,11 +1163,11 @@ final class PieChartComponent: Component { let fractionValue: Double = floor(item.displayValue * 100.0 * 10.0) / 10.0 let fractionString: String if fractionValue < 0.1 { - fractionString = "<0.1" + fractionString = "<0.1%" } else if abs(Double(Int(fractionValue)) - fractionValue) < 0.001 { - fractionString = "\(Int(fractionValue))" + fractionString = "\(Int(fractionValue))%" } else { - fractionString = "\(fractionValue)" + fractionString = "\(fractionValue)%" } let tooltipSize = tooltip.update( diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 2f791a6e9b..45eed334bd 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -262,6 +262,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe } }, present: { _ in }, openForumThread: { _, _ in + }, openStorageManagement: { }) interaction.searchTextHighightState = searchQuery self.interaction = interaction diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 802a86a466..080818c108 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -29,6 +29,7 @@ import PremiumUI import StickerPackPreviewUI import ChatControllerInteraction import ChatPresentationInterfaceState +import StorageUsageScreen private final class AccountUserInterfaceInUseContext { let subscribers = Bag<(Bool) -> Void>() @@ -1424,6 +1425,15 @@ public final class SharedAccountContextImpl: SharedAccountContext { 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 { let mappedSource: PremiumSource switch source {