added storage

This commit is contained in:
Francesco 2024-12-19 17:15:31 +01:00
parent b4b20154a5
commit 3a68ded645
6 changed files with 296 additions and 2 deletions

View file

@ -35,6 +35,7 @@
132417D52D13240200B4F2D2 /* EpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417D42D13240200B4F2D2 /* EpisodeCell.swift */; };
132417D72D13242400B4F2D2 /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417D62D13242400B4F2D2 /* CircularProgressBar.swift */; };
132417D92D1328B900B4F2D2 /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417D82D1328B900B4F2D2 /* VideoPlayerView.swift */; };
13B3A4B22D1477F100BCC0D5 /* StorageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B3A4B12D1477F100BCC0D5 /* StorageSettingsView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@ -66,6 +67,7 @@
132417D42D13240200B4F2D2 /* EpisodeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeCell.swift; sourceTree = "<group>"; };
132417D62D13242400B4F2D2 /* CircularProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = "<group>"; };
132417D82D1328B900B4F2D2 /* VideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = "<group>"; };
13B3A4B12D1477F100BCC0D5 /* StorageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageSettingsView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -211,6 +213,7 @@
132417AD2D131A0600B4F2D2 /* SettingsLogsView.swift */,
132417AE2D131A0600B4F2D2 /* SettingsModuleView.swift */,
132417AF2D131A0600B4F2D2 /* SettingsPlayerView.swift */,
13B3A4B12D1477F100BCC0D5 /* StorageSettingsView.swift */,
);
path = SubPages;
sourceTree = "<group>";
@ -321,6 +324,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13B3A4B22D1477F100BCC0D5 /* StorageSettingsView.swift in Sources */,
132417BB2D131A0600B4F2D2 /* SettingsIUView.swift in Sources */,
132417C42D131A0600B4F2D2 /* AnimeInfoExtraction.swift in Sources */,
132417B82D131A0600B4F2D2 /* SearchView.swift in Sources */,

View file

@ -52,7 +52,7 @@ struct AnimeInfoView: View {
Spacer()
HStack(alignment: .top, spacing: 12) {
HStack(alignment: .center, spacing: 12) {
Text(module.name)
.font(.system(size: 13))
.padding(4)

View file

@ -49,6 +49,12 @@ struct SettingsView: View {
Text("Modules")
}
}
NavigationLink(destination: StorageSettingsView()) {
HStack {
Image(systemName: "externaldrive.fill")
Text("Storage")
}
}
Button(action: {
isDocumentPickerPresented = true
}) {

View file

@ -13,7 +13,7 @@ struct AboutView: View {
Form {
Section(footer: Text("Sora is a free open source app, under the GPLv3.0 License. You can find the entire Sora code in the github repo.")) {
HStack(alignment: .center, spacing: 10) {
KFImage(URL(string: "https://raw.githubusercontent.com/cranci1/Sora/main/Sora/Assets.xcassets/AppIcon.appiconset/1024.jpg"))
KFImage(URL(string: "https://raw.githubusercontent.com/cranci1/Sora/main/Sora/Assets.xcassets/AppIcon.appiconset/180.png"))
.resizable()
.frame(width: 80, height: 80)
.clipShape(Circle())
@ -93,6 +93,21 @@ struct AboutView: View {
.foregroundColor(.orange)
}
}
HStack {
KFImage(URL(string: "https://cdn.discordapp.com/avatars/1072985316916469870/6bc6c23a6f22f932d23f2c2452f755f7.png?size=4096"))
.resizable()
.frame(width: 40, height: 40)
.clipShape(Circle())
VStack(alignment: .leading) {
Text("50/50")
.font(.headline)
.foregroundColor(.orange)
Text("Discord Helper & Designer")
.font(.subheadline)
.foregroundColor(.secondary)
}
}
}
Section(header: Text("Acknowledgements"), footer: Text("Thanks to the creators of this frameworks, that made Sora creation much simplier.")) {

View file

@ -0,0 +1,269 @@
//
// StorageSettingsView.swift
// Sora
//
// Created by Francesco on 19/12/24.
//
import SwiftUI
struct StorageSettingsView: View {
@State private var appSize: String = "Calculating..."
@State private var storageDetails: [(String, Double, Color)] = []
@State private var deviceStorage: (total: Int64, used: Int64) = (0, 0)
@State private var showingClearCacheAlert = false
@State private var showingClearDocumentsAlert = false
var body: some View {
List {
Section {
VStack(alignment: .leading, spacing: 8) {
HStack(alignment: .center) {
Text(appSize)
.font(.system(size: 28, weight: .bold))
Text("of \(ByteCountFormatter.string(fromByteCount: deviceStorage.total, countStyle: .file))")
.foregroundColor(.secondary)
}
VStack(alignment: .leading, spacing: 4) {
GeometryReader { geometry in
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray.opacity(0.2))
.frame(height: 24)
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray.opacity(0.3))
.frame(width: geometry.size.width * CGFloat(deviceStorage.used) / CGFloat(deviceStorage.total))
.frame(height: 24)
HStack(spacing: 0) {
ForEach(storageDetails, id: \.0) { detail in
RoundedRectangle(cornerRadius: 0)
.fill(detail.2)
.frame(width: geometry.size.width * CGFloat(detail.1 * 1024 * 1024) / CGFloat(deviceStorage.total))
}
}
.frame(height: 24)
.clipShape(RoundedRectangle(cornerRadius: 8))
}
}
.frame(height: 24)
HStack(spacing: 16) {
ForEach(storageDetails, id: \.0) { detail in
HStack(spacing: 4) {
Circle()
.fill(detail.2)
.frame(width: 8, height: 8)
Text(detail.0)
.font(.caption)
.foregroundColor(.secondary)
}
}
HStack(spacing: 4) {
Circle()
.fill(Color.gray.opacity(0.3))
.frame(width: 8, height: 8)
Text("System")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
}
.padding(.vertical, 8)
}
Section {
ForEach(storageDetails, id: \.0) { detail in
HStack {
Image(systemName: categoryIcon(for: detail.0))
.foregroundColor(.white)
.frame(width: 28, height: 28)
.background(detail.2)
.clipShape(RoundedRectangle(cornerRadius: 6))
VStack(alignment: .leading, spacing: 2) {
Text(detail.0)
Text("\(detail.1, specifier: "%.2f") MB")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
}
Section(header: Text("Actions")) {
Button(action: { showingClearCacheAlert = true }) {
actionRow(
icon: "clock.fill",
title: "Clear Cache",
subtitle: "Free up space used by cached items",
iconColor: .accentColor
)
}
Button(action: { showingClearDocumentsAlert = true }) {
actionRow(
icon: "doc.fill",
title: "Clear Documents",
subtitle: "Check and remove unnecessary files",
iconColor: .accentColor
)
}
}
}
.navigationTitle("Storage")
.onAppear {
calculateAppSize()
getDeviceStorage()
}
.alert("Clear Cache", isPresented: $showingClearCacheAlert) {
Button("Cancel", role: .cancel) { }
Button("Clear", role: .destructive) {
clearCache()
}
} message: {
Text("Are you sure you want to clear the cache? This action cannot be undone.")
}
.alert("Clear Documents", isPresented: $showingClearDocumentsAlert) {
Button("Cancel", role: .cancel) { }
Button("Clear", role: .destructive) {
clearDocuments()
}
} message: {
Text("Are you sure you want to clear all documents? This action cannot be undone.")
}
}
private func clearCache() {
if let cacheURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first {
do {
let fileURLs = try FileManager.default.contentsOfDirectory(
at: cacheURL,
includingPropertiesForKeys: nil,
options: .skipsHiddenFiles
)
for fileURL in fileURLs {
try FileManager.default.removeItem(at: fileURL)
}
calculateAppSize()
getDeviceStorage()
Logger.shared.log("Cleared Cache")
} catch {
print("Error clearing cache: \(error)")
Logger.shared.log("Error clearing cache: \(error)")
}
}
}
private func clearDocuments() {
if let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
do {
let fileURLs = try FileManager.default.contentsOfDirectory(
at: documentsURL,
includingPropertiesForKeys: nil,
options: .skipsHiddenFiles
)
for fileURL in fileURLs {
try FileManager.default.removeItem(at: fileURL)
}
calculateAppSize()
getDeviceStorage()
Logger.shared.log("Cleared Documents")
} catch {
print("Error clearing documents: \(error)")
Logger.shared.log("Error clearing documents: \(error)")
}
}
}
private func getDeviceStorage() {
do {
let fileURL = URL(fileURLWithPath: NSHomeDirectory() as String)
let values = try fileURL.resourceValues(forKeys: [.volumeTotalCapacityKey, .volumeAvailableCapacityKey])
if let total = values.volumeTotalCapacity,
let available = values.volumeAvailableCapacity {
deviceStorage.total = Int64(total)
deviceStorage.used = Int64(total - available)
}
} catch {
print("Error getting device storage: \(error)")
Logger.shared.log("Error getting device storage: \(error)")
}
}
private func getTotalAppBytes() -> Int64 {
return Int64(totalSize() * 1024 * 1024)
}
private func actionRow(icon: String, title: String, subtitle: String, iconColor: Color) -> some View {
HStack {
Image(systemName: icon)
.foregroundColor(.white)
.frame(width: 28, height: 28)
.background(iconColor)
.clipShape(RoundedRectangle(cornerRadius: 6))
VStack(alignment: .leading, spacing: 2) {
Text(title)
Text(subtitle)
.font(.caption)
.foregroundColor(.secondary)
}
}
}
private func categoryIcon(for category: String) -> String {
switch category {
case "Documents":
return "doc.fill"
case "Cache":
return "clock.fill"
case "Temporary":
return "trash.fill"
default:
return "questionmark"
}
}
private func calculateAppSize() {
let cacheSize = getDirectorySize(url: FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!)
let documentsSize = getDirectorySize(url: FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!)
let tmpSize = getDirectorySize(url: FileManager.default.temporaryDirectory)
let totalSize = cacheSize + documentsSize + tmpSize
self.appSize = ByteCountFormatter.string(fromByteCount: Int64(totalSize), countStyle: .file)
self.storageDetails = [
("Documents", documentsSize / 1024 / 1024, .green),
("Cache", cacheSize / 1024 / 1024, .orange),
("Temporary", tmpSize / 1024 / 1024, .red)
]
}
private func getDirectorySize(url: URL) -> Double {
var size: Double = 0
if let enumerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: [.fileSizeKey], options: [], errorHandler: nil) {
for case let fileURL as URL in enumerator {
do {
let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey])
size += Double(resourceValues.fileSize ?? 0)
} catch {
print("Error calculating size for file \(fileURL): \(error)")
Logger.shared.log("Error calculating size for file \(fileURL): \(error)")
}
}
}
return size
}
private func totalSize() -> Double {
return storageDetails.reduce(0) { $0 + $1.1 }
}
}