mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-20 08:02:16 +00:00
added storage
This commit is contained in:
parent
b4b20154a5
commit
3a68ded645
6 changed files with 296 additions and 2 deletions
|
|
@ -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 */,
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,12 @@ struct SettingsView: View {
|
|||
Text("Modules")
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: StorageSettingsView()) {
|
||||
HStack {
|
||||
Image(systemName: "externaldrive.fill")
|
||||
Text("Storage")
|
||||
}
|
||||
}
|
||||
Button(action: {
|
||||
isDocumentPickerPresented = true
|
||||
}) {
|
||||
|
|
|
|||
|
|
@ -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.")) {
|
||||
|
|
|
|||
269
Sora/Views/SettingsViews/SubPages/StorageSettingsView.swift
Normal file
269
Sora/Views/SettingsViews/SubPages/StorageSettingsView.swift
Normal 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 }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue