Files
simplex-chat/apps/ios/Shared/Views/UserSettings/StorageView.swift
T
Narasimha-scandEvgeny Poberezkin d457bc18d2 ios: delete temp data from storage view (#7626)
* ios: delete temp data from storage view

The temp folder is the XFTP agent work directory and holds scratch
files for received-file encryption and roster transfers, so deleting it
under a running chat breaks those writes mid-flight. The button is only
enabled while chat is stopped, the same precondition core deleteStorage
relies on, and the row says how to stop chat.

* ios: show delete temp data alert over settings sheet

AlertManager presents from ContentView, which is behind the settings
sheet, so the confirmation never appeared. The alert now also warns
that interrupted transfers never complete and that videos in unsent
drafts, which live in the temp folder, are lost, and the caption names
the screen where chat is stopped.

* ios: say unsent draft videos are sent without the file

A draft video whose converted file was deleted from the temp folder is
still sent, as a preview with no file, so "lost" understated it.

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2026-10-10 22:38:14 +01:00

90 lines
3.5 KiB
Swift

//
// StorageView.swift
// SimpleX (iOS)
//
// Created by Stanislav Dmitrenko on 13.01.2025.
// Copyright © 2025 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct StorageView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@State var appGroupFiles: [String: Int64] = [:]
@State var documentsFiles: [String: Int64] = [:]
var body: some View {
ScrollView {
VStack(alignment: .leading) {
directoryView("App group:", getGroupContainerDirectory(), appGroupFiles)
if !documentsFiles.isEmpty {
directoryView("Documents:", getDocumentsDirectory(), documentsFiles)
}
}
}
.padding()
.onAppear(perform: loadFiles)
}
private func loadFiles() {
appGroupFiles = traverseFiles(in: getGroupContainerDirectory())
documentsFiles = traverseFiles(in: getDocumentsDirectory())
}
@ViewBuilder
private func directoryView(_ name: LocalizedStringKey, _ dir: URL, _ contents: [String: Int64]) -> some View {
Text(name).font(.headline)
ForEach(Array(contents), id: \.key) { (key, value) in
let sizeText = Text(key).bold() + Text(verbatim: " ") + Text((ByteCountFormatter.string(fromByteCount: value, countStyle: .binary)))
if dir.appendingPathComponent(key).path == getTempFilesDirectory().path {
let stopped = m.chatRunning == false
HStack {
sizeText
Button("Delete temp data", role: .destructive, action: confirmDeleteTempFiles)
.disabled(!stopped)
}
if !stopped {
Text("Stop chat in Settings → Chat data → Database passphrase & export to delete temp data.")
.font(.footnote)
.foregroundColor(theme.colors.secondary)
}
} else {
sizeText
}
}
}
private func confirmDeleteTempFiles() {
showAlert(
NSLocalizedString("Delete temp data?", comment: "alert title"),
message: NSLocalizedString("Files being sent or received will never complete. Videos in unsent drafts will be sent without the file.", comment: "alert message"),
actions: {[
UIAlertAction(title: NSLocalizedString("Delete", comment: "alert action"), style: .destructive) { _ in
deleteTempFiles()
loadFiles()
},
cancelAlertAction
]}
)
}
private func traverseFiles(in dir: URL) -> [String: Int64] {
var res: [String: Int64] = [:]
let fm = FileManager.default
do {
if let enumerator = fm.enumerator(at: dir, includingPropertiesForKeys: [.isDirectoryKey, .fileSizeKey, .fileAllocatedSizeKey]) {
for case let url as URL in enumerator {
let attrs = try url.resourceValues(forKeys: [/*.isDirectoryKey, .fileSizeKey,*/ .fileAllocatedSizeKey])
let root = String(url.absoluteString.replacingOccurrences(of: dir.absoluteString, with: "").split(separator: "/")[0])
res[root] = (res[root] ?? 0) + Int64(attrs.fileAllocatedSize ?? 0)
}
}
} catch {
logger.error("Error traversing files: \(error)")
}
return res
}
}