mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 04:58:56 +00:00
v3.9.1
This commit is contained in:
@@ -537,8 +537,8 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
APP_BUILD = 62;
|
APP_BUILD = 63;
|
||||||
APP_VERSION = 3.9.0;
|
APP_VERSION = 3.9.1;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
@@ -607,8 +607,8 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
APP_BUILD = 62;
|
APP_BUILD = 63;
|
||||||
APP_VERSION = 3.9.0;
|
APP_VERSION = 3.9.1;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import AlinFoundation
|
|||||||
class MetadataAppInfoFetcher {
|
class MetadataAppInfoFetcher {
|
||||||
static func getAppInfo(fromMetadata metadata: [String: Any], atPath path: URL) -> AppInfo? {
|
static func getAppInfo(fromMetadata metadata: [String: Any], atPath path: URL) -> AppInfo? {
|
||||||
// Extract metadata attributes for known fields
|
// Extract metadata attributes for known fields
|
||||||
let displayName = metadata["kMDItemDisplayName"] as? String ?? ""
|
var displayName = metadata["kMDItemDisplayName"] as? String ?? ""
|
||||||
|
displayName = displayName.replacingOccurrences(of: ".app", with: "").capitalizingFirstLetter()
|
||||||
let fsName = metadata["kMDItemFSName"] as? String ?? path.lastPathComponent
|
let fsName = metadata["kMDItemFSName"] as? String ?? path.lastPathComponent
|
||||||
let appName = displayName.isEmpty ? fsName : displayName
|
let appName = displayName.isEmpty ? fsName : displayName
|
||||||
|
|
||||||
@@ -152,7 +153,7 @@ class AppInfoUtils {
|
|||||||
static func fetchAppIcon(for path: URL, wrapped: Bool, md: Bool = false) -> NSImage? {
|
static func fetchAppIcon(for path: URL, wrapped: Bool, md: Bool = false) -> NSImage? {
|
||||||
let iconPath = wrapped ? (md ? path : path.deletingLastPathComponent().deletingLastPathComponent()) : path
|
let iconPath = wrapped ? (md ? path : path.deletingLastPathComponent().deletingLastPathComponent()) : path
|
||||||
if let appIcon = getIconForFileOrFolderNS(atPath: iconPath) {
|
if let appIcon = getIconForFileOrFolderNS(atPath: iconPath) {
|
||||||
return convertICNSToPNG(icon: appIcon, size: NSSize(width: 100, height: 100))
|
return convertICNSToPNG(icon: appIcon, size: NSSize(width: 50, height: 50))
|
||||||
} else {
|
} else {
|
||||||
printOS("App Icon not found for app at path: \(path)")
|
printOS("App Icon not found for app at path: \(path)")
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -208,6 +208,21 @@ enum CurrentPage:Int, CaseIterable, Identifiable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SortOption:Int, CaseIterable, Identifiable {
|
||||||
|
case alphabetical
|
||||||
|
case size
|
||||||
|
case creationDate
|
||||||
|
case contentChangeDate
|
||||||
|
case lastUsedDate
|
||||||
|
|
||||||
|
var id: Int { rawValue }
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
let titles: [String] = ["App Name", "App Size", "Install Date", "Modified Date", "Last Used Date"]
|
||||||
|
return titles[rawValue]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
enum CurrentTabView:Int
|
enum CurrentTabView:Int
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ struct Sponsor: Identifiable {
|
|||||||
let url: URL
|
let url: URL
|
||||||
|
|
||||||
static let sponsors: [Sponsor] = [
|
static let sponsors: [Sponsor] = [
|
||||||
|
Sponsor(name: "mzdr (monthly)", url: URL(string: "https://github.com/mzdr")!),
|
||||||
Sponsor(name: "chris3ware", url: URL(string: "https://github.com/chris3ware")!),
|
Sponsor(name: "chris3ware", url: URL(string: "https://github.com/chris3ware")!),
|
||||||
Sponsor(name: "fpuhan", url: URL(string: "https://github.com/fpuhan")!),
|
Sponsor(name: "fpuhan", url: URL(string: "https://github.com/fpuhan")!),
|
||||||
Sponsor(name: "HungThinhIT", url: URL(string: "https://github.com/HungThinhIT")!),
|
Sponsor(name: "HungThinhIT", url: URL(string: "https://github.com/HungThinhIT")!),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ struct GeneralSettingsTab: View {
|
|||||||
@AppStorage("settings.general.brew") private var brew: Bool = false
|
@AppStorage("settings.general.brew") private var brew: Bool = false
|
||||||
@AppStorage("settings.general.oneshot") private var oneShotMode: Bool = false
|
@AppStorage("settings.general.oneshot") private var oneShotMode: Bool = false
|
||||||
@AppStorage("settings.general.confirmAlert") private var confirmAlert: Bool = false
|
@AppStorage("settings.general.confirmAlert") private var confirmAlert: Bool = false
|
||||||
@AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true
|
// @AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true
|
||||||
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
||||||
@State private var isCLISymlinked = false
|
@State private var isCLISymlinked = false
|
||||||
|
|
||||||
@@ -107,28 +107,28 @@ struct GeneralSettingsTab: View {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
// HStack(spacing: 0) {
|
||||||
Image(systemName: selectedSortAlpha ? "textformat.abc" : "textformat.123")
|
// Image(systemName: selectedSortAlpha ? "textformat.abc" : "textformat.123")
|
||||||
.resizable()
|
// .resizable()
|
||||||
.scaledToFit()
|
// .scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
// .frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
// .padding(.trailing)
|
||||||
.foregroundStyle(.primary)
|
// .foregroundStyle(.primary)
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
// VStack(alignment: .leading, spacing: 5) {
|
||||||
Text("File list sorting order")
|
// Text("File list sorting order")
|
||||||
.font(.callout)
|
// .font(.callout)
|
||||||
.foregroundStyle(.primary)
|
// .foregroundStyle(.primary)
|
||||||
}
|
// }
|
||||||
Spacer()
|
// Spacer()
|
||||||
Picker("", selection: $selectedSortAlpha) {
|
// Picker("", selection: $selectedSortAlpha) {
|
||||||
Text("Alphabetical")
|
// Text("Alphabetical")
|
||||||
.tag(true)
|
// .tag(true)
|
||||||
Text("File Size")
|
// Text("File Size")
|
||||||
.tag(false)
|
// .tag(false)
|
||||||
}
|
// }
|
||||||
.buttonStyle(.borderless)
|
// .buttonStyle(.borderless)
|
||||||
}
|
// }
|
||||||
.padding(5)
|
// .padding(5)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -151,8 +151,8 @@ struct GeneralSettingsTab: View {
|
|||||||
.tag("Real")
|
.tag("Real")
|
||||||
Text("Logical")
|
Text("Logical")
|
||||||
.tag("Logical")
|
.tag("Logical")
|
||||||
Text("Finder")
|
// Text("Finder")
|
||||||
.tag("Finder")
|
// .tag("Finder")
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderless)
|
.buttonStyle(.borderless)
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ struct AppSearchView: View {
|
|||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
@State private var showMenu = false
|
@State private var showMenu = false
|
||||||
@Binding var isMenuBar: Bool
|
@Binding var isMenuBar: Bool
|
||||||
@AppStorage("settings.general.selectedSortAppsList") var selectedSortAlpha: Bool = true
|
@AppStorage("settings.general.selectedSortAppsList") var selectedSortOption: SortOption = .alphabetical
|
||||||
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
|
|
||||||
|
|
||||||
@@ -81,10 +81,10 @@ struct AppSearchView: View {
|
|||||||
.buttonStyle(SimpleButtonStyle(icon: "arrow.counterclockwise.circle", help: "Refresh apps (⌘+R)", size: 16))
|
.buttonStyle(SimpleButtonStyle(icon: "arrow.counterclockwise.circle", help: "Refresh apps (⌘+R)", size: 16))
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchBar(search: $search, darker: (mini || menubarEnabled) ? false : true, glass: glass)
|
SearchBar(search: $search, darker: (mini || menubarEnabled) ? false : true, glass: glass, sidebar: false)
|
||||||
|
|
||||||
|
|
||||||
if search.isEmpty {
|
if search.isEmpty && (mini || menubarEnabled) {
|
||||||
Button("More") {
|
Button("More") {
|
||||||
self.showMenu.toggle()
|
self.showMenu.toggle()
|
||||||
}
|
}
|
||||||
@@ -94,10 +94,19 @@ struct AppSearchView: View {
|
|||||||
|
|
||||||
Button("") {
|
Button("") {
|
||||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
selectedSortAlpha.toggle()
|
// Cycle through all enum cases using `CaseIterable`
|
||||||
|
if let nextSortOption = SortOption(rawValue: selectedSortOption.rawValue + 1) {
|
||||||
|
selectedSortOption = nextSortOption
|
||||||
|
showPopover = false
|
||||||
|
showMenu = false
|
||||||
|
} else {
|
||||||
|
selectedSortOption = .alphabetical
|
||||||
|
showPopover = false
|
||||||
|
showMenu = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Sorting: \(selectedSortAlpha ? "Name" : "Size")", help: "Sort app list alphabetically by name or by size", size: 5))
|
}
|
||||||
|
.buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Sorting: \(selectedSortOption.title)", help: "Sort app list alphabetically by name or by size", size: 5))
|
||||||
|
|
||||||
if mini && !menubarEnabled {
|
if mini && !menubarEnabled {
|
||||||
Button("") {
|
Button("") {
|
||||||
@@ -149,15 +158,48 @@ struct AppSearchView: View {
|
|||||||
Button("Quit") {
|
Button("Quit") {
|
||||||
NSApp.terminate(nil)
|
NSApp.terminate(nil)
|
||||||
}
|
}
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Quit Pearcleaner", help: "Quit Pearcleaner", size: 5))
|
.buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Quit", help: "Quit Pearcleaner", size: 5))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.background(backgroundView(themeManager: themeManager, glass: glass).padding(-80))
|
.background(backgroundView(themeManager: themeManager, glass: glass).padding(-80))
|
||||||
.frame(width: 150)
|
// .frame(width: 200)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
} else if search.isEmpty && (!mini || !menubarEnabled) {
|
||||||
|
|
||||||
|
Button("") {
|
||||||
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
|
showMenu.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(SimpleButtonStyle(icon: "line.3.horizontal.decrease.circle", help: selectedSortOption.title, size: 16))
|
||||||
|
.popover(isPresented: $showMenu, arrowEdge: .bottom) {
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Text("Sorting Options").font(.subheadline).foregroundStyle(.secondary)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
Divider()
|
||||||
|
ForEach(SortOption.allCases) { option in
|
||||||
|
Button("") {
|
||||||
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
|
selectedSortOption = option
|
||||||
|
showMenu = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(SimpleButtonStyle(icon: selectedSortOption == option ? "circle.inset.filled" : "circle", label: option.title, help: "", size: 5))
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(backgroundView(themeManager: themeManager, glass: glass).padding(-80))
|
||||||
|
// .frame(width: 160)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -174,12 +216,28 @@ struct AppSearchView: View {
|
|||||||
apps = appState.sortedApps.filter { $0.appName.localizedCaseInsensitiveContains(search) }
|
apps = appState.sortedApps.filter { $0.appName.localizedCaseInsensitiveContains(search) }
|
||||||
}
|
}
|
||||||
|
|
||||||
switch selectedSortAlpha {
|
// Sort based on the selected option
|
||||||
case true:
|
switch selectedSortOption {
|
||||||
return apps.sorted { $0.appName.replacingOccurrences(of: ".", with: "").lowercased() < $1.appName.replacingOccurrences(of: ".", with: "").lowercased() }
|
case .alphabetical:
|
||||||
case false:
|
return apps.sorted {
|
||||||
return apps.sorted { $0.bundleSize > $1.bundleSize }
|
$0.appName.replacingOccurrences(of: ".", with: "").lowercased() < $1.appName.replacingOccurrences(of: ".", with: "").lowercased()
|
||||||
}
|
}
|
||||||
|
case .size:
|
||||||
|
return apps.sorted { $0.bundleSize > $1.bundleSize }
|
||||||
|
case .creationDate:
|
||||||
|
return apps.sorted { ($0.creationDate ?? Date.distantPast) > ($1.creationDate ?? Date.distantPast) }
|
||||||
|
case .contentChangeDate:
|
||||||
|
return apps.sorted { ($0.contentChangeDate ?? Date.distantPast) > ($1.contentChangeDate ?? Date.distantPast) }
|
||||||
|
case .lastUsedDate:
|
||||||
|
return apps.sorted { ($0.lastUsedDate ?? Date.distantPast) > ($1.lastUsedDate ?? Date.distantPast) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// switch selectedSortAlpha {
|
||||||
|
// case true:
|
||||||
|
// return apps.sorted { $0.appName.replacingOccurrences(of: ".", with: "").lowercased() < $1.appName.replacingOccurrences(of: ".", with: "").lowercased() }
|
||||||
|
// case false:
|
||||||
|
// return apps.sorted { $0.bundleSize > $1.bundleSize }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -218,7 +276,7 @@ struct SimpleSearchStyle: TextFieldStyle {
|
|||||||
Spacer()
|
Spacer()
|
||||||
Text(isFocused ? "Type to search" : "Click to search")
|
Text(isFocused ? "Type to search" : "Click to search")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.primary.opacity(0.2))
|
.foregroundColor(.secondary)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ struct FilesView: View {
|
|||||||
@AppStorage("settings.general.filesWarning") private var warning: Bool = false
|
@AppStorage("settings.general.filesWarning") private var warning: Bool = false
|
||||||
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
@AppStorage("settings.general.confirmAlert") private var confirmAlert: Bool = false
|
@AppStorage("settings.general.confirmAlert") private var confirmAlert: Bool = false
|
||||||
|
@AppStorage("settings.interface.details") private var detailsEnabled: Bool = true
|
||||||
@State private var showAlert = false
|
@State private var showAlert = false
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
@@ -30,7 +31,7 @@ struct FilesView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
|
|
||||||
var totalSelectedSize: (real: String, logical: String, finder: String) {
|
var totalSelectedSize: (real: String, logical: String) {
|
||||||
var totalReal: Int64 = 0
|
var totalReal: Int64 = 0
|
||||||
var totalLogical: Int64 = 0
|
var totalLogical: Int64 = 0
|
||||||
for url in appState.selectedItems {
|
for url in appState.selectedItems {
|
||||||
@@ -39,12 +40,11 @@ struct FilesView: View {
|
|||||||
totalReal += realSize
|
totalReal += realSize
|
||||||
totalLogical += logicalSize
|
totalLogical += logicalSize
|
||||||
}
|
}
|
||||||
return (real: formatByte(size: totalReal).human, logical: formatByte(size: totalLogical).human, finder: "\(formatByte(size: totalLogical).byte) (\(formatByte(size: totalReal).human))")
|
return (real: formatByte(size: totalReal).human, logical: formatByte(size: totalLogical).human)
|
||||||
}
|
}
|
||||||
|
|
||||||
let displaySizeTotal = sizeType == "Real" ? formatByte(size: appState.appInfo.totalSize).human :
|
let displaySizeTotal = sizeType == "Real" ? formatByte(size: appState.appInfo.totalSize).human :
|
||||||
sizeType == "Logical" ? formatByte(size: appState.appInfo.totalSizeLogical).human :
|
formatByte(size: appState.appInfo.totalSizeLogical).human
|
||||||
"\(formatByte(size: appState.appInfo.totalSizeLogical).byte) (\(formatByte(size: appState.appInfo.totalSize).human))"
|
|
||||||
|
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
if appState.showProgress {
|
if appState.showProgress {
|
||||||
@@ -91,55 +91,96 @@ struct FilesView: View {
|
|||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
|
|
||||||
//app icon, title, size and items
|
//app icon, title, size and items
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .leading, spacing: 5){
|
||||||
HStack(alignment: .center) {
|
PearGroupBox(header: {
|
||||||
|
HStack(alignment: .center, spacing: 15) {
|
||||||
if let appIcon = appState.appInfo.appIcon {
|
if let appIcon = appState.appInfo.appIcon {
|
||||||
Image(nsImage: appIcon)
|
Image(nsImage: appIcon)
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 50, height: 50)
|
.frame(width: 50, height: 50)
|
||||||
.padding()
|
.padding(5)
|
||||||
.background{
|
.background{
|
||||||
RoundedRectangle(cornerRadius: 16)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.fill(Color((appState.appInfo.appIcon?.averageColor)!))
|
.fill(Color((appState.appInfo.appIcon?.averageColor)!))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
VStack(alignment: .leading) {
|
||||||
VStack(alignment: .leading, spacing: 5){
|
HStack(alignment: .center) {
|
||||||
HStack {
|
|
||||||
Text("\(appState.appInfo.appName)").font(.title).fontWeight(.bold).lineLimit(1)
|
Text("\(appState.appInfo.appName)").font(.title).fontWeight(.bold).lineLimit(1)
|
||||||
Text("•").foregroundStyle(Color("AccentColor"))
|
Image(systemName: "circle.fill").foregroundStyle(Color("AccentColor")).font(.system(size: 5))
|
||||||
Text("v\(appState.appInfo.appVersion)").font(.title3)
|
Text("\(appState.appInfo.appVersion)").font(.title3)
|
||||||
|
|
||||||
}
|
}
|
||||||
Text("\(appState.appInfo.bundleIdentifier)")
|
Text("\(appState.appInfo.bundleIdentifier)")
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.foregroundStyle((.primary.opacity(0.5)))
|
.foregroundStyle((.primary.opacity(0.5)))
|
||||||
|
|
||||||
if let creationDate = appState.appInfo.creationDate {
|
|
||||||
Text("Installed: \(formattedMDDate(from: creationDate))")
|
|
||||||
.font(.footnote)
|
|
||||||
}
|
}
|
||||||
if let modificationDate = appState.appInfo.contentChangeDate {
|
|
||||||
Text("Modified: \(formattedMDDate(from: modificationDate))")
|
|
||||||
.font(.footnote)
|
|
||||||
}
|
|
||||||
if let lastUsedDate = appState.appInfo.lastUsedDate {
|
|
||||||
Text("Last Used: \(formattedMDDate(from: lastUsedDate))")
|
|
||||||
.font(.footnote)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.leading)
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
Button("\(detailsEnabled ? "Hide Details" : "Show Details")") {
|
||||||
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
|
detailsEnabled.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}, content: {
|
||||||
|
|
||||||
|
if detailsEnabled {
|
||||||
|
HStack(spacing: 20) {
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("Total size of files:")
|
||||||
|
.font(.callout).fontWeight(.bold)
|
||||||
|
Text("")
|
||||||
|
.font(.footnote)
|
||||||
|
Text("Installed Date:")
|
||||||
|
.font(.footnote)
|
||||||
|
Text("Modified Date:")
|
||||||
|
.font(.footnote)
|
||||||
|
Text("Last Used Date:")
|
||||||
|
.font(.footnote)
|
||||||
|
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
VStack(alignment: .trailing, spacing: 5) {
|
VStack(alignment: .trailing, spacing: 5) {
|
||||||
|
Text("\(displaySizeTotal)").font(.callout).fontWeight(.bold)//.help("Total size on disk")
|
||||||
|
|
||||||
|
Text("\(appState.appInfo.fileSize.count == 1 ? "\(appState.selectedItems.count) of \(appState.appInfo.fileSize.count) item" : "\(appState.selectedItems.count) of \(appState.appInfo.fileSize.count) items")")
|
||||||
|
.font(.footnote)
|
||||||
|
|
||||||
|
if let creationDate = appState.appInfo.creationDate {
|
||||||
|
Text(formattedMDDate(from: creationDate))
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
} else {
|
||||||
|
Text("Not available")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let modificationDate = appState.appInfo.contentChangeDate {
|
||||||
|
Text(formattedMDDate(from: modificationDate))
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
} else {
|
||||||
|
Text("Not available")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let lastUsedDate = appState.appInfo.lastUsedDate {
|
||||||
|
Text(formattedMDDate(from: lastUsedDate))
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
} else {
|
||||||
|
Text("Not available")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
Text("\(displaySizeTotal)").font(.title).fontWeight(.bold).help("Total size on disk")
|
|
||||||
Text("\(appState.appInfo.fileSize.count == 1 ? "\(appState.selectedItems.count) / \(appState.appInfo.fileSize.count) item" : "\(appState.selectedItems.count) / \(appState.appInfo.fileSize.count) items")")
|
|
||||||
.font(.callout).foregroundStyle((.primary.opacity(0.5)))
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -147,9 +188,15 @@ struct FilesView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
.padding(.top, 0)
|
.padding(.top, 0)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -166,7 +213,6 @@ struct FilesView: View {
|
|||||||
.toggleStyle(SimpleCheckboxToggleStyle())
|
.toggleStyle(SimpleCheckboxToggleStyle())
|
||||||
.help("All checkboxes")
|
.help("All checkboxes")
|
||||||
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
|
||||||
@@ -228,7 +274,7 @@ struct FilesView: View {
|
|||||||
Button("") {
|
Button("") {
|
||||||
selectedSortAlpha.toggle()
|
selectedSortAlpha.toggle()
|
||||||
}
|
}
|
||||||
.buttonStyle(SimpleButtonStyle(icon: selectedSortAlpha ? "textformat.abc" : "textformat.123", help: selectedSortAlpha ? "Sorted alphabetically" : "Sorted by size"))
|
.buttonStyle(SimpleButtonStyle(icon: "line.3.horizontal.decrease.circle", label: selectedSortAlpha ? "Name" : "Size", help: selectedSortAlpha ? "Sorted alphabetically" : "Sorted by size", size: 16))
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
@@ -290,11 +336,11 @@ struct FilesView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
InfoButton(text: "Always double-check the files/folders marked for removal. In some rare cases, Pearcleaner may find some unrelated files when app names are too similar.", color: .primary.opacity(0.5), warning: true, edge: .top)
|
// InfoButton(text: "Always double-check the files/folders marked for removal. In some rare cases, Pearcleaner may find some unrelated files when app names are too similar.", color: .primary.opacity(0.5), warning: true, edge: .top)
|
||||||
.padding(.top)
|
// .padding(.top)
|
||||||
|
|
||||||
|
|
||||||
Button("\(sizeType == "Logical" ? totalSelectedSize.logical : sizeType == "Finder" ? totalSelectedSize.finder : totalSelectedSize.real)") {
|
Button("\(sizeType == "Logical" ? totalSelectedSize.logical : totalSelectedSize.real)") {
|
||||||
showCustomAlert(enabled: confirmAlert, title: "Warning", message: "Are you sure you want to remove these files?", style: .warning, onOk: {
|
showCustomAlert(enabled: confirmAlert, title: "Warning", message: "Are you sure you want to remove these files?", style: .warning, onOk: {
|
||||||
Task {
|
Task {
|
||||||
|
|
||||||
@@ -377,7 +423,7 @@ struct FilesView: View {
|
|||||||
}
|
}
|
||||||
.buttonStyle(UninstallButton(isEnabled: !appState.selectedItems.isEmpty))
|
.buttonStyle(UninstallButton(isEnabled: !appState.selectedItems.isEmpty))
|
||||||
.disabled(appState.selectedItems.isEmpty)
|
.disabled(appState.selectedItems.isEmpty)
|
||||||
.padding(.top)
|
.padding(.top, 5)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -509,9 +555,7 @@ struct FileDetailsItem: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
let displaySize = sizeType == "Real" ? formatByte(size: size!).human :
|
let displaySize = sizeType == "Real" ? formatByte(size: size!).human :
|
||||||
sizeType == "Logical" ? formatByte(size: sizeL!).human :
|
formatByte(size: sizeL!).human
|
||||||
"\(formatByte(size: sizeL!).byte) (\(formatByte(size: size!).human))"
|
|
||||||
|
|
||||||
Text("\(displaySize)")
|
Text("\(displaySize)")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,10 +114,10 @@ struct RegularMode: View {
|
|||||||
.buttonStyle(.borderless)
|
.buttonStyle(.borderless)
|
||||||
.padding(2)
|
.padding(2)
|
||||||
.padding(.vertical, 2)
|
.padding(.vertical, 2)
|
||||||
.background {
|
// .background {
|
||||||
RoundedRectangle(cornerRadius: 8)
|
// RoundedRectangle(cornerRadius: 8)
|
||||||
.strokeBorder(.secondary.opacity(0.5), lineWidth: 1)
|
// .strokeBorder(.secondary.opacity(0.5), lineWidth: 1)
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
.padding(6)
|
.padding(6)
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ struct RegularMode: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.frame(minWidth: 900, minHeight: 600)
|
.frame(minWidth: appState.currentPage == .orphans ? 700 : 900, minHeight: 600)
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ struct ZombieView: View {
|
|||||||
@State private var totalLogicalSize: Int64 = 0
|
@State private var totalLogicalSize: Int64 = 0
|
||||||
@State private var totalRealSizeUninstallBtn: String = ""
|
@State private var totalRealSizeUninstallBtn: String = ""
|
||||||
@State private var totalLogicalSizeUninstallBtn: String = ""
|
@State private var totalLogicalSizeUninstallBtn: String = ""
|
||||||
@State private var totalFinderSizeUninstallBtn: String = ""
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
@@ -80,39 +79,45 @@ struct ZombieView: View {
|
|||||||
.padding(.trailing, (mini || menubarEnabled) ? 6 : 0)
|
.padding(.trailing, (mini || menubarEnabled) ? 6 : 0)
|
||||||
|
|
||||||
|
|
||||||
VStack() {
|
VStack(spacing: 0) {
|
||||||
|
|
||||||
// Main Group
|
// Main Group
|
||||||
HStack() {
|
|
||||||
|
|
||||||
VStack(alignment: .center) {
|
|
||||||
|
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 5){
|
||||||
|
|
||||||
|
PearGroupBox(header: {
|
||||||
|
HStack(alignment: .center, spacing: 15) {
|
||||||
Image(systemName: "doc.badge.clock.fill")
|
Image(systemName: "doc.badge.clock.fill")
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 50, height: 50)
|
.frame(width: 50, height: 50)
|
||||||
.symbolRenderingMode(.hierarchical)
|
.symbolRenderingMode(.hierarchical)
|
||||||
.padding(.trailing)
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 10){
|
VStack(alignment: .leading){
|
||||||
HStack {
|
|
||||||
Text("Orphaned Files").font(.title).fontWeight(.bold)
|
Text("Orphaned Files").font(.title).fontWeight(.bold)
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
Text("Remaining files and folders from previous applications")
|
Text("Remaining files and folders from previous applications")
|
||||||
.font(.callout).foregroundStyle(.primary.opacity(0.5))
|
.font(.callout).foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}, content: {
|
||||||
|
HStack(spacing: 20) {
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("Total size of files:")
|
||||||
|
.font(.callout).fontWeight(.bold)
|
||||||
|
Text("")
|
||||||
|
.font(.footnote)
|
||||||
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
VStack(alignment: .trailing, spacing: 5) {
|
VStack(alignment: .trailing, spacing: 5) {
|
||||||
Text("\(displaySizeTotal)").font(.title).fontWeight(.bold).help("Total size on disk")
|
Text("\(displaySizeTotal)").font(.callout).fontWeight(.bold)//.help("Total size on disk")
|
||||||
|
|
||||||
Text("\(selectedZombieItemsLocal.count) / \(searchZ.isEmpty ? appState.zombieFile.fileSize.count : memoizedFiles.count) \(appState.zombieFile.fileSize.count == 1 ? "item" : "items")")
|
Text("\(selectedZombieItemsLocal.count) of \(searchZ.isEmpty ? appState.zombieFile.fileSize.count : memoizedFiles.count) \(appState.zombieFile.fileSize.count == 1 ? "item" : "items")")
|
||||||
.font(.callout).foregroundStyle(.primary.opacity(0.5))
|
.font(.footnote).foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
@@ -157,7 +162,6 @@ struct ZombieView: View {
|
|||||||
.toggleStyle(SimpleCheckboxToggleStyle())
|
.toggleStyle(SimpleCheckboxToggleStyle())
|
||||||
.help("All checkboxes")
|
.help("All checkboxes")
|
||||||
|
|
||||||
|
|
||||||
SearchBar(search: $searchZ, darker: true, glass: glass, sidebar: false)
|
SearchBar(search: $searchZ, darker: true, glass: glass, sidebar: false)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.onChange(of: searchZ) { newValue in
|
.onChange(of: searchZ) { newValue in
|
||||||
@@ -170,7 +174,7 @@ struct ZombieView: View {
|
|||||||
selectedSortAlpha.toggle()
|
selectedSortAlpha.toggle()
|
||||||
updateMemoizedFiles(for: searchZ, sizeType: sizeType, selectedSortAlpha: selectedSortAlpha, force: true)
|
updateMemoizedFiles(for: searchZ, sizeType: sizeType, selectedSortAlpha: selectedSortAlpha, force: true)
|
||||||
}
|
}
|
||||||
.buttonStyle(SimpleButtonStyle(icon: selectedSortAlpha ? "textformat.abc" : "textformat.123", help: selectedSortAlpha ? "Sorted alphabetically" : "Sorted by size"))
|
.buttonStyle(SimpleButtonStyle(icon: "line.3.horizontal.decrease.circle", label: selectedSortAlpha ? "Name" : "Size", help: selectedSortAlpha ? "Sorted alphabetically" : "Sorted by size", size: 16))
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -203,7 +207,7 @@ struct ZombieView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
InfoButton(text: "Leftover file search is not 100% accurate as it doesn't have any uninstalled app bundles to check against for file exclusion. This does a best guess search for files/folders and excludes the ones that have overlap with your currently installed applications. Please confirm files marked for deletion really do belong to uninstalled applications.", color: .orange, warning: true, edge: .top)
|
// InfoButton(text: "Leftover file search is not 100% accurate as it doesn't have any uninstalled app bundles to check against for file exclusion. This does a best guess search for files/folders and excludes the ones that have overlap with your currently installed applications. Please confirm files marked for deletion really do belong to uninstalled applications.", color: .orange, warning: true, edge: .top)
|
||||||
|
|
||||||
Button("Rescan") {
|
Button("Rescan") {
|
||||||
updateOnMain {
|
updateOnMain {
|
||||||
@@ -214,7 +218,7 @@ struct ZombieView: View {
|
|||||||
}
|
}
|
||||||
.buttonStyle(RescanButton())
|
.buttonStyle(RescanButton())
|
||||||
|
|
||||||
Button("\(sizeType == "Logical" ? totalLogicalSizeUninstallBtn : sizeType == "Finder" ? totalFinderSizeUninstallBtn : totalRealSizeUninstallBtn)") {
|
Button("\(sizeType == "Logical" ? totalLogicalSizeUninstallBtn : totalRealSizeUninstallBtn)") {
|
||||||
showCustomAlert(enabled: confirmAlert, title: "Warning", message: "Are you sure you want to remove these files?", style: .warning, onOk: {
|
showCustomAlert(enabled: confirmAlert, title: "Warning", message: "Are you sure you want to remove these files?", style: .warning, onOk: {
|
||||||
Task {
|
Task {
|
||||||
|
|
||||||
@@ -342,7 +346,7 @@ struct ZombieView: View {
|
|||||||
let filteredFilesReal = fileSizeReal.filter { url, _ in searchTerm.isEmpty || url.lastPathComponent.localizedCaseInsensitiveContains(searchTerm) }
|
let filteredFilesReal = fileSizeReal.filter { url, _ in searchTerm.isEmpty || url.lastPathComponent.localizedCaseInsensitiveContains(searchTerm) }
|
||||||
let filteredFilesLogical = fileSizeLogical.filter { url, _ in searchTerm.isEmpty || url.lastPathComponent.localizedCaseInsensitiveContains(searchTerm) }
|
let filteredFilesLogical = fileSizeLogical.filter { url, _ in searchTerm.isEmpty || url.lastPathComponent.localizedCaseInsensitiveContains(searchTerm) }
|
||||||
|
|
||||||
let filesToSort = sizeType == "Real" || sizeType == "Finder" ? filteredFilesReal : filteredFilesLogical
|
let filesToSort = sizeType == "Real" ? filteredFilesReal : filteredFilesLogical
|
||||||
let sortedFilteredFiles = filesToSort.sorted { (left, right) -> Bool in
|
let sortedFilteredFiles = filesToSort.sorted { (left, right) -> Bool in
|
||||||
if selectedSortAlpha {
|
if selectedSortAlpha {
|
||||||
return left.key.lastPathComponent.pearFormat() < right.key.lastPathComponent.pearFormat()
|
return left.key.lastPathComponent.pearFormat() < right.key.lastPathComponent.pearFormat()
|
||||||
@@ -377,15 +381,14 @@ struct ZombieView: View {
|
|||||||
let sizes = calculateTotalSelectedZombieSize()
|
let sizes = calculateTotalSelectedZombieSize()
|
||||||
totalRealSizeUninstallBtn = sizes.real
|
totalRealSizeUninstallBtn = sizes.real
|
||||||
totalLogicalSizeUninstallBtn = sizes.logical
|
totalLogicalSizeUninstallBtn = sizes.logical
|
||||||
totalFinderSizeUninstallBtn = "\(sizes.logical) (\(sizes.real))"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var displaySizeText: String {
|
private var displaySizeText: String {
|
||||||
switch sizeType {
|
switch sizeType {
|
||||||
case "Logical":
|
case "Logical":
|
||||||
return totalLogicalSizeUninstallBtn
|
return totalLogicalSizeUninstallBtn
|
||||||
case "Finder":
|
case "Real":
|
||||||
return totalFinderSizeUninstallBtn
|
return totalRealSizeUninstallBtn
|
||||||
default:
|
default:
|
||||||
return totalRealSizeUninstallBtn
|
return totalRealSizeUninstallBtn
|
||||||
}
|
}
|
||||||
@@ -481,8 +484,7 @@ struct ZombieFileDetailsItem: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
let displaySize = sizeType == "Real" ? formatByte(size: size!).human :
|
let displaySize = sizeType == "Real" ? formatByte(size: size!).human :
|
||||||
sizeType == "Logical" ? formatByte(size: sizeL!).human :
|
formatByte(size: sizeL!).human
|
||||||
"\(formatByte(size: sizeL!).byte) (\(formatByte(size: size!).human))"
|
|
||||||
|
|
||||||
Text("\(displaySize)")
|
Text("\(displaySize)")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user