mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 06:09:01 +00:00
v3.7.2
This commit is contained in:
@@ -568,8 +568,8 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
APP_BUILD = 46;
|
APP_BUILD = 47;
|
||||||
APP_VERSION = 3.7.1;
|
APP_VERSION = 3.7.2;
|
||||||
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;
|
||||||
@@ -638,8 +638,8 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
APP_BUILD = 46;
|
APP_BUILD = 47;
|
||||||
APP_VERSION = 3.7.1;
|
APP_VERSION = 3.7.2;
|
||||||
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;
|
||||||
|
|||||||
@@ -928,7 +928,7 @@ extension View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Background color/glass setter
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func backgroundView(themeSettings: ThemeSettings, darker: Bool = false, glass: Bool = false) -> some View {
|
func backgroundView(themeSettings: ThemeSettings, darker: Bool = false, glass: Bool = false) -> some View {
|
||||||
if glass {
|
if glass {
|
||||||
@@ -940,6 +940,32 @@ func backgroundView(themeSettings: ThemeSettings, darker: Bool = false, glass: B
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct PickerModifier: ViewModifier {
|
||||||
|
@State private var isHovered: Bool = false
|
||||||
|
let themeSettings: ThemeSettings
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.buttonStyle(.borderless)
|
||||||
|
.padding(4)
|
||||||
|
.background {
|
||||||
|
backgroundView(themeSettings: themeSettings, darker: isHovered)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
|
}
|
||||||
|
.onHover { hovering in
|
||||||
|
isHovered = hovering
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
func pickerStyle(themeSettings: ThemeSettings) -> some View {
|
||||||
|
self.modifier(PickerModifier(themeSettings: themeSettings))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Preset view elements
|
||||||
struct PresetColor: ButtonStyle {
|
struct PresetColor: ButtonStyle {
|
||||||
var fillColor: Color
|
var fillColor: Color
|
||||||
var label: String
|
var label: String
|
||||||
|
|||||||
@@ -170,35 +170,62 @@ func UnzipAndReplace(DownloadedFileURL fileURL: String, appState: AppState) {
|
|||||||
|
|
||||||
// --- Updater check frequency
|
// --- Updater check frequency
|
||||||
|
|
||||||
func updateNextUpdateDate() {
|
enum UpdateFrequency: String, CaseIterable, Identifiable {
|
||||||
@AppStorage("settings.updater.updateTimeframe") var updateTimeframe: Int = 1
|
case none = "Never"
|
||||||
@AppStorage("settings.updater.nextUpdateDate") var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
|
case daily = "Daily"
|
||||||
let updateSeconds = updateTimeframe.daysToSeconds
|
case weekly = "Weekly"
|
||||||
let newUpdateDate = Calendar.current.startOfDay(for: Date().addingTimeInterval(updateSeconds))
|
case monthly = "Monthly"
|
||||||
nextUpdateDate = newUpdateDate.timeIntervalSinceReferenceDate
|
|
||||||
|
var id: String { self.rawValue }
|
||||||
|
|
||||||
|
var interval: TimeInterval? {
|
||||||
|
switch self {
|
||||||
|
case .none:
|
||||||
|
return nil
|
||||||
|
case .daily:
|
||||||
|
return 86400 // 1 day in seconds
|
||||||
|
case .weekly:
|
||||||
|
return 604800 // 7 days in seconds
|
||||||
|
case .monthly:
|
||||||
|
return 2592000 // 30 days in seconds
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateNextUpdateDate() {
|
||||||
|
guard let updateInterval = self.interval else { return }
|
||||||
|
let newUpdateDate = Calendar.current.startOfDay(for: Date().addingTimeInterval(updateInterval))
|
||||||
|
UserDefaults.standard.set(newUpdateDate.timeIntervalSinceReferenceDate, forKey: "settings.updater.nextUpdateDate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//func updateNextUpdateDate() {
|
||||||
|
// @AppStorage("settings.updater.updateFrequency") var updateFrequency: UpdateFrequency = .daily
|
||||||
|
// @AppStorage("settings.updater.nextUpdateDate") var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
|
||||||
|
//
|
||||||
|
// guard let updateInterval = updateFrequency.interval else { return }
|
||||||
|
// let newUpdateDate = Calendar.current.startOfDay(for: Date().addingTimeInterval(updateInterval))
|
||||||
|
// nextUpdateDate = newUpdateDate.timeIntervalSinceReferenceDate
|
||||||
|
//}
|
||||||
|
|
||||||
func checkAndUpdateIfNeeded(appState: AppState) {
|
func checkAndUpdateIfNeeded(appState: AppState) {
|
||||||
@AppStorage("settings.updater.updateTimeframe") var updateTimeframe: Int = 1
|
@AppStorage("settings.updater.updateFrequency") var updateFrequency: UpdateFrequency = .daily
|
||||||
@AppStorage("settings.updater.nextUpdateDate") var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
|
@AppStorage("settings.updater.nextUpdateDate") var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
|
||||||
|
|
||||||
let updateSeconds = updateTimeframe.daysToSeconds
|
guard let updateInterval = updateFrequency.interval else {
|
||||||
|
printOS("Updater: no update frequency set, skipping check")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let now = Date()
|
let now = Date()
|
||||||
|
|
||||||
// Retrieve the next update date from UserDefaults
|
|
||||||
let nextUpdateDateLocal = Date(timeIntervalSinceReferenceDate: nextUpdateDate)
|
let nextUpdateDateLocal = Date(timeIntervalSinceReferenceDate: nextUpdateDate)
|
||||||
// let nextUpdateDate = UserDefaults.standard.object(forKey: "settings.updater.nextUpdateDate") as? Date
|
|
||||||
|
|
||||||
// If there's no stored next update date or it's in the past, update immediately
|
|
||||||
if !isSameDay(date1: nextUpdateDateLocal, date2: now) {
|
if !isSameDay(date1: nextUpdateDateLocal, date2: now) {
|
||||||
// Next update date is in the future, no need to update
|
printOS("Updater: next update date is in the future, skipping (\(nextUpdateDateLocal))")
|
||||||
printOS("Updater: next update date is in the future, skipping")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update immediately and set next update date
|
|
||||||
updateApp(appState: appState)
|
updateApp(appState: appState)
|
||||||
setNextUpdateDate(interval: updateSeconds)
|
setNextUpdateDate(interval: updateInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateApp(appState: AppState) {
|
func updateApp(appState: AppState) {
|
||||||
@@ -210,13 +237,59 @@ func updateApp(appState: AppState) {
|
|||||||
func setNextUpdateDate(interval: TimeInterval) {
|
func setNextUpdateDate(interval: TimeInterval) {
|
||||||
let newUpdateDate = Calendar.current.startOfDay(for: Date().addingTimeInterval(interval))
|
let newUpdateDate = Calendar.current.startOfDay(for: Date().addingTimeInterval(interval))
|
||||||
UserDefaults.standard.set(newUpdateDate.timeIntervalSinceReferenceDate, forKey: "settings.updater.nextUpdateDate")
|
UserDefaults.standard.set(newUpdateDate.timeIntervalSinceReferenceDate, forKey: "settings.updater.nextUpdateDate")
|
||||||
// UserDefaults.standard.set(newUpdateDate, forKey: "settings.updater.nextUpdateDate")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isSameDay(date1: Date, date2: Date) -> Bool {
|
func isSameDay(date1: Date, date2: Date) -> Bool {
|
||||||
return Calendar.current.isDate(date1, inSameDayAs: date2)
|
return Calendar.current.isDate(date1, inSameDayAs: date2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//func updateNextUpdateDate() {
|
||||||
|
// @AppStorage("settings.updater.updateTimeframe") var updateTimeframe: Int = 1
|
||||||
|
// @AppStorage("settings.updater.nextUpdateDate") var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
|
||||||
|
// let updateSeconds = updateTimeframe.daysToSeconds
|
||||||
|
// let newUpdateDate = Calendar.current.startOfDay(for: Date().addingTimeInterval(updateSeconds))
|
||||||
|
// nextUpdateDate = newUpdateDate.timeIntervalSinceReferenceDate
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func checkAndUpdateIfNeeded(appState: AppState) {
|
||||||
|
// @AppStorage("settings.updater.updateTimeframe") var updateTimeframe: Int = 1
|
||||||
|
// @AppStorage("settings.updater.nextUpdateDate") var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
|
||||||
|
//
|
||||||
|
// let updateSeconds = updateTimeframe.daysToSeconds
|
||||||
|
// let now = Date()
|
||||||
|
//
|
||||||
|
// // Retrieve the next update date from UserDefaults
|
||||||
|
// let nextUpdateDateLocal = Date(timeIntervalSinceReferenceDate: nextUpdateDate)
|
||||||
|
//// let nextUpdateDate = UserDefaults.standard.object(forKey: "settings.updater.nextUpdateDate") as? Date
|
||||||
|
//
|
||||||
|
// // If there's no stored next update date or it's in the past, update immediately
|
||||||
|
// if !isSameDay(date1: nextUpdateDateLocal, date2: now) {
|
||||||
|
// // Next update date is in the future, no need to update
|
||||||
|
// printOS("Updater: next update date is in the future, skipping")
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Update immediately and set next update date
|
||||||
|
// updateApp(appState: appState)
|
||||||
|
// setNextUpdateDate(interval: updateSeconds)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func updateApp(appState: AppState) {
|
||||||
|
// // Perform your update logic here
|
||||||
|
// printOS("Updater: performing update")
|
||||||
|
// loadGithubReleases(appState: appState)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func setNextUpdateDate(interval: TimeInterval) {
|
||||||
|
// let newUpdateDate = Calendar.current.startOfDay(for: Date().addingTimeInterval(interval))
|
||||||
|
// UserDefaults.standard.set(newUpdateDate.timeIntervalSinceReferenceDate, forKey: "settings.updater.nextUpdateDate")
|
||||||
|
//// UserDefaults.standard.set(newUpdateDate, forKey: "settings.updater.nextUpdateDate")
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func isSameDay(date1: Date, date2: Date) -> Bool {
|
||||||
|
// return Calendar.current.isDate(date1, inSameDayAs: date2)
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --- Updater Badge View
|
// --- Updater Badge View
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ struct PearcleanerApp: App {
|
|||||||
@StateObject var locations = Locations()
|
@StateObject var locations = Locations()
|
||||||
@StateObject var fsm = FolderSettingsManager()
|
@StateObject var fsm = FolderSettingsManager()
|
||||||
@State private var windowSettings = WindowSettings()
|
@State private var windowSettings = WindowSettings()
|
||||||
// @AppStorage("settings.updater.updateTimeframe") private var updateTimeframe: Int = 1
|
@AppStorage("settings.updater.updateFrequency") private var updateFrequency: UpdateFrequency = .daily
|
||||||
@AppStorage("settings.updater.enableUpdates") private var enableUpdates: Bool = true
|
|
||||||
@AppStorage("settings.permissions.hasLaunched") private var hasLaunched: Bool = false
|
@AppStorage("settings.permissions.hasLaunched") private var hasLaunched: Bool = false
|
||||||
@AppStorage("displayMode") var displayMode: DisplayMode = .system
|
@AppStorage("displayMode") var displayMode: DisplayMode = .system
|
||||||
@AppStorage("settings.general.mini") private var mini: Bool = false
|
@AppStorage("settings.general.mini") private var mini: Bool = false
|
||||||
@@ -121,7 +120,7 @@ struct PearcleanerApp: App {
|
|||||||
// Get GH releases
|
// Get GH releases
|
||||||
loadGithubReleases(appState: appState, releaseOnly: true)
|
loadGithubReleases(appState: appState, releaseOnly: true)
|
||||||
|
|
||||||
if enableUpdates {
|
if updateFrequency != .none {
|
||||||
// Update checker
|
// Update checker
|
||||||
checkAndUpdateIfNeeded(appState: appState)
|
checkAndUpdateIfNeeded(appState: appState)
|
||||||
}
|
}
|
||||||
@@ -181,9 +180,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
|
|||||||
|
|
||||||
findAndSetWindowFrame(named: ["Pearcleaner"], windowSettings: windowSettings)
|
findAndSetWindowFrame(named: ["Pearcleaner"], windowSettings: windowSettings)
|
||||||
|
|
||||||
if UserDefaults.standard.object(forKey: "themeColor") == nil {
|
// if UserDefaults.standard.object(forKey: "themeColor") == nil {
|
||||||
self.appearanceChanged()
|
// self.appearanceChanged()
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
self.appearanceCheck()
|
||||||
|
|
||||||
if menubarEnabled {
|
if menubarEnabled {
|
||||||
findAndHideWindows(named: ["Pearcleaner"])
|
findAndHideWindows(named: ["Pearcleaner"])
|
||||||
@@ -194,14 +195,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
|
|||||||
observer = DistributedNotificationCenter.default().addObserver(forName: NSNotification.Name(rawValue: "AppleInterfaceThemeChangedNotification"), object: nil, queue: OperationQueue.main) { [weak self] _ in
|
observer = DistributedNotificationCenter.default().addObserver(forName: NSNotification.Name(rawValue: "AppleInterfaceThemeChangedNotification"), object: nil, queue: OperationQueue.main) { [weak self] _ in
|
||||||
let themeMode = UserDefaults.standard.string(forKey: "settings.general.selectedTheme")
|
let themeMode = UserDefaults.standard.string(forKey: "settings.general.selectedTheme")
|
||||||
if themeMode == "Auto" {
|
if themeMode == "Auto" {
|
||||||
self?.appearanceChanged()
|
self?.appearanceCheck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func appearanceChanged() {
|
func appearanceCheck() {
|
||||||
let dm = UserDefaults.standard.integer(forKey: "displayMode")
|
let dm = UserDefaults.standard.integer(forKey: "displayMode")
|
||||||
var displayMode = DisplayMode(rawValue: dm)
|
var displayMode = DisplayMode(rawValue: dm)
|
||||||
let dark = isDarkMode()
|
let dark = isDarkMode()
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ struct FolderSettingsTab: View {
|
|||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@EnvironmentObject var locations: Locations
|
@EnvironmentObject var locations: Locations
|
||||||
@EnvironmentObject var fsm: FolderSettingsManager
|
@EnvironmentObject var fsm: FolderSettingsManager
|
||||||
|
@EnvironmentObject var themeSettings: ThemeSettings
|
||||||
@State private var isHovered = false
|
@State private var isHovered = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -81,7 +82,8 @@ struct FolderSettingsTab: View {
|
|||||||
}
|
}
|
||||||
.scrollIndicators(.automatic)
|
.scrollIndicators(.automatic)
|
||||||
.padding()
|
.padding()
|
||||||
.background(Color("mode").opacity(0.05))
|
// .background(Color("mode").opacity(0.05))
|
||||||
|
.background(backgroundView(themeSettings: themeSettings, darker: true))
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
.onDrop(of: ["public.file-url"], isTargeted: nil) { providers -> Bool in
|
.onDrop(of: ["public.file-url"], isTargeted: nil) { providers -> Bool in
|
||||||
providers.forEach { provider in
|
providers.forEach { provider in
|
||||||
@@ -173,7 +175,8 @@ struct FolderSettingsTab: View {
|
|||||||
}
|
}
|
||||||
.scrollIndicators(.automatic)
|
.scrollIndicators(.automatic)
|
||||||
.padding()
|
.padding()
|
||||||
.background(Color("mode").opacity(0.05))
|
.background(backgroundView(themeSettings: themeSettings, darker: true))
|
||||||
|
// .background(Color("mode").opacity(0.05))
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
.onDrop(of: ["public.file-url"], isTargeted: nil) { providers -> Bool in
|
.onDrop(of: ["public.file-url"], isTargeted: nil) { providers -> Bool in
|
||||||
providers.forEach { provider in
|
providers.forEach { provider in
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import FinderSync
|
|||||||
struct GeneralSettingsTab: View {
|
struct GeneralSettingsTab: View {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@EnvironmentObject var locations: Locations
|
@EnvironmentObject var locations: Locations
|
||||||
|
@EnvironmentObject var themeSettings: ThemeSettings
|
||||||
@State private var windowSettings = WindowSettings()
|
@State private var windowSettings = WindowSettings()
|
||||||
@AppStorage("settings.general.glass") private var glass: Bool = true
|
@AppStorage("settings.general.glass") private var glass: Bool = true
|
||||||
@AppStorage("settings.general.mini") private var mini: Bool = false
|
@AppStorage("settings.general.mini") private var mini: Bool = false
|
||||||
@@ -107,31 +108,13 @@ struct GeneralSettingsTab: View {
|
|||||||
}
|
}
|
||||||
InfoButton(text: "When searching for app files or leftover files, the list will be sorted either alphabetically or by size(large to small)")
|
InfoButton(text: "When searching for app files or leftover files, the list will be sorted either alphabetically or by size(large to small)")
|
||||||
Spacer()
|
Spacer()
|
||||||
SegmentedPicker(
|
Picker("", selection: $selectedSortAlpha) {
|
||||||
["Alpha", "Size"],
|
Text("Alpha")
|
||||||
selectedIndex: Binding(
|
.tag(true)
|
||||||
get: { selectedSortAlpha ? 0 : 1 },
|
Text("Size")
|
||||||
set: { newIndex in
|
.tag(false)
|
||||||
withAnimation(.easeInOut(duration: 0.3)) {
|
|
||||||
selectedSortAlpha = (newIndex == 0)
|
|
||||||
}
|
}
|
||||||
}),
|
.pickerStyle(themeSettings: themeSettings)
|
||||||
selectionAlignment: .bottom,
|
|
||||||
content: { item, isSelected in
|
|
||||||
Text(item)
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundColor(isSelected ? Color("mode") : Color("mode").opacity(0.5))
|
|
||||||
.padding(.horizontal)
|
|
||||||
.padding(.bottom, 5)
|
|
||||||
.frame(width: 75)
|
|
||||||
|
|
||||||
},
|
|
||||||
selection: {
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
Spacer()
|
|
||||||
Color("pear").frame(height: 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
@@ -145,48 +128,23 @@ struct GeneralSettingsTab: View {
|
|||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(Color("mode").opacity(0.5))
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
Text("File size display")
|
Text("File size display mode")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(Color("mode").opacity(0.5))
|
||||||
}
|
}
|
||||||
InfoButton(text: "Real size type will show how much actual allocated space the file has on disk. Logical type shows the binary size. The filesystem can compress and deduplicate sectors on disk, so real size is sometimes smaller(or bigger) than logical size. Finder size is similar to if you right click > Get Info on a file in Finder, which will show both the logical and real sizes together.")
|
InfoButton(text: "Real size type will show how much actual allocated space the file has on disk. Logical type shows the binary size. The filesystem can compress and deduplicate sectors on disk, so real size is sometimes smaller(or bigger) than logical size. Finder size is similar to if you right click > Get Info on a file in Finder, which will show both the logical and real sizes together.")
|
||||||
Spacer()
|
Spacer()
|
||||||
SegmentedPicker(
|
Picker("", selection: $sizeType) {
|
||||||
["Real", "Logical", "Finder"],
|
Text("Real")
|
||||||
selectedIndex: Binding(
|
.tag("Real")
|
||||||
get: {
|
Text("Logical")
|
||||||
switch sizeType {
|
.tag("Logical")
|
||||||
case "Real": return 0
|
Text("Finder")
|
||||||
case "Logical": return 1
|
.tag("Finder")
|
||||||
case "Finder": return 2
|
|
||||||
default: return 0
|
|
||||||
}
|
}
|
||||||
},
|
.pickerStyle(themeSettings: themeSettings)
|
||||||
set: { newIndex in
|
|
||||||
withAnimation(.easeInOut(duration: 0.3)) {
|
|
||||||
switch newIndex {
|
|
||||||
case 0: sizeType = "Real"
|
|
||||||
case 1: sizeType = "Logical"
|
|
||||||
case 2: sizeType = "Finder"
|
|
||||||
default: sizeType = "Real"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
selectionAlignment: .bottom,
|
|
||||||
content: { item, isSelected in
|
|
||||||
Text(item)
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundColor(isSelected ? Color("mode") : Color("mode").opacity(0.5) )
|
|
||||||
.padding(.horizontal)
|
|
||||||
.padding(.bottom, 5)
|
|
||||||
.frame(width: 75)
|
|
||||||
},
|
|
||||||
selection: {
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
Spacer()
|
|
||||||
Color("pear").frame(height: 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
@@ -237,7 +195,7 @@ struct GeneralSettingsTab: View {
|
|||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(diskStatus ? .green : .red)
|
.foregroundStyle(diskStatus ? .green : .red)
|
||||||
.saturation(displayMode.colorScheme == .dark ? 0.5 : 1)
|
.saturation(displayMode.colorScheme == .dark ? 0.8 : 1)
|
||||||
Text(diskStatus ? "Full Disk permission granted" : "Full Disk permission not granted")
|
Text(diskStatus ? "Full Disk permission granted" : "Full Disk permission not granted")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(Color("mode").opacity(0.5))
|
||||||
@@ -265,7 +223,7 @@ struct GeneralSettingsTab: View {
|
|||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(accessStatus ? .green : .red)
|
.foregroundStyle(accessStatus ? .green : .red)
|
||||||
.saturation(displayMode.colorScheme == .dark ? 0.5 : 1)
|
.saturation(displayMode.colorScheme == .dark ? 0.8 : 1)
|
||||||
Text(accessStatus ? "Accessibility permission granted" : "Accessibility permission not granted")
|
Text(accessStatus ? "Accessibility permission granted" : "Accessibility permission not granted")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(Color("mode").opacity(0.5))
|
||||||
@@ -292,7 +250,7 @@ struct GeneralSettingsTab: View {
|
|||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(autoStatus ? .green : .red)
|
.foregroundStyle(autoStatus ? .green : .red)
|
||||||
.saturation(displayMode.colorScheme == .dark ? 0.5 : 1)
|
.saturation(displayMode.colorScheme == .dark ? 0.8 : 1)
|
||||||
Text(autoStatus ? "Automation permission granted" : "Automation permission not granted")
|
Text(autoStatus ? "Automation permission granted" : "Automation permission not granted")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(Color("mode").opacity(0.5))
|
||||||
@@ -330,7 +288,7 @@ struct GeneralSettingsTab: View {
|
|||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(sentinel ? .green : .red)
|
.foregroundStyle(sentinel ? .green : .red)
|
||||||
.saturation(displayMode.colorScheme == .dark ? 0.5 : 1)
|
.saturation(displayMode.colorScheme == .dark ? 0.8 : 1)
|
||||||
Text(sentinel ? "Detecting when apps are moved to Trash" : "**NOT** detecting when apps are moved to Trash")
|
Text(sentinel ? "Detecting when apps are moved to Trash" : "**NOT** detecting when apps are moved to Trash")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(Color("mode").opacity(0.5))
|
||||||
@@ -371,7 +329,7 @@ struct GeneralSettingsTab: View {
|
|||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(appState.finderExtensionEnabled ? .green : .red)
|
.foregroundStyle(appState.finderExtensionEnabled ? .green : .red)
|
||||||
.saturation(displayMode.colorScheme == .dark ? 0.5 : 1)
|
.saturation(displayMode.colorScheme == .dark ? 0.8 : 1)
|
||||||
Text(appState.finderExtensionEnabled ? "Context menu extension for Finder is enabled" : "Context menu extension for Finder is disabled")
|
Text(appState.finderExtensionEnabled ? "Context menu extension for Finder is enabled" : "Context menu extension for Finder is disabled")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(Color("mode").opacity(0.5))
|
||||||
|
|||||||
@@ -193,40 +193,15 @@ struct InterfaceSettingsTab: View {
|
|||||||
}
|
}
|
||||||
InfoButton(text: "Changing the color mode will reset the base color to defaults")
|
InfoButton(text: "Changing the color mode will reset the base color to defaults")
|
||||||
Spacer()
|
Spacer()
|
||||||
SegmentedPicker(
|
Picker("", selection: $selectedTheme) {
|
||||||
["Auto", "Dark", "Light"],
|
Text("Auto")
|
||||||
selectedIndex: Binding(
|
.tag("Auto")
|
||||||
get: {
|
Text("Dark")
|
||||||
switch selectedTheme {
|
.tag("Dark")
|
||||||
case "Dark": return 1
|
Text("Light")
|
||||||
case "Light": return 2
|
.tag("Light")
|
||||||
default: return 0
|
|
||||||
}
|
}
|
||||||
},
|
.pickerStyle(themeSettings: themeSettings)
|
||||||
set: { newIndex in
|
|
||||||
withAnimation(.easeInOut(duration: 0.3)) {
|
|
||||||
switch newIndex {
|
|
||||||
case 1: selectedTheme = "Dark"
|
|
||||||
case 2: selectedTheme = "Light"
|
|
||||||
default: selectedTheme = "Auto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
selectionAlignment: .bottom,
|
|
||||||
content: { item, isSelected in
|
|
||||||
Text(item)
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundColor(isSelected ? Color("mode") : Color("mode").opacity(0.5) )
|
|
||||||
.padding(.horizontal)
|
|
||||||
.padding(.bottom, 5)
|
|
||||||
.frame(width: 65)
|
|
||||||
},
|
|
||||||
selection: {
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
Spacer()
|
|
||||||
Color("pear").frame(height: 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onChange(of: selectedTheme) { newTheme in
|
.onChange(of: selectedTheme) { newTheme in
|
||||||
switch newTheme {
|
switch newTheme {
|
||||||
case "Auto":
|
case "Auto":
|
||||||
|
|||||||
@@ -11,44 +11,36 @@ import Foundation
|
|||||||
|
|
||||||
struct UpdateSettingsTab: View {
|
struct UpdateSettingsTab: View {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
|
@EnvironmentObject var themeSettings: ThemeSettings
|
||||||
@State private var showAlert = false
|
@State private var showAlert = false
|
||||||
@State private var showDone = false
|
@State private var showDone = false
|
||||||
@AppStorage("settings.updater.nextUpdateDate") private var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
|
@AppStorage("settings.updater.nextUpdateDate") private var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
|
||||||
@AppStorage("settings.updater.updateTimeframe") private var updateTimeframe: Int = 1
|
@AppStorage("settings.updater.updateFrequency") private var updateFrequency: UpdateFrequency = .daily
|
||||||
@AppStorage("settings.updater.enableUpdates") private var enableUpdates: Bool = true
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Image(systemName: "arrow.down.square")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.padding(.trailing)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Text("\(enableUpdates ? "Pearcleaner will check for updates every " : "Automatic updates are disabled")")
|
|
||||||
|
Text("Pearcleaner will check for updates")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(Color("mode").opacity(0.5))
|
||||||
|
|
||||||
if enableUpdates {
|
|
||||||
Text("**\(updateTimeframe)**").font(.system(.callout, design: .monospaced)).monospacedDigit()
|
|
||||||
Text(updateTimeframe == 1 ? " day" : " days")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
|
|
||||||
Stepper("", value: $updateTimeframe, in: 0...30)
|
|
||||||
.onChange(of: updateTimeframe, perform: { _ in
|
|
||||||
updateNextUpdateDate()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
Picker("", selection: $updateFrequency) {
|
||||||
|
ForEach(UpdateFrequency.allCases, id: \.self) { frequency in
|
||||||
|
Text(frequency.rawValue).tag(frequency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: updateFrequency) { frequency in
|
||||||
|
updateFrequency.updateNextUpdateDate()
|
||||||
|
}
|
||||||
|
.pickerStyle(themeSettings: themeSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
if enableUpdates {
|
if updateFrequency != .none {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Next update check: \(formattedDate(Date(timeIntervalSinceReferenceDate: nextUpdateDate)))")
|
Text("Next update check: \(formattedDate(Date(timeIntervalSinceReferenceDate: nextUpdateDate)))")
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
@@ -56,18 +48,9 @@ struct UpdateSettingsTab: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
Toggle(isOn: $enableUpdates, label: {
|
|
||||||
})
|
|
||||||
.toggleStyle(.switch)
|
|
||||||
}
|
}
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.padding(.leading)
|
.padding(.horizontal)
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack() {
|
VStack() {
|
||||||
@@ -83,7 +66,10 @@ struct UpdateSettingsTab: View {
|
|||||||
}
|
}
|
||||||
.frame(minHeight: 0, maxHeight: .infinity)
|
.frame(minHeight: 0, maxHeight: .infinity)
|
||||||
.frame(minWidth: 0, maxWidth: .infinity)
|
.frame(minWidth: 0, maxWidth: .infinity)
|
||||||
.padding()
|
// .background(Color("mode").opacity(0.05))
|
||||||
|
.background(backgroundView(themeSettings: themeSettings, darker: true))
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||||
|
.padding(.bottom)
|
||||||
|
|
||||||
Text("Showing last 3 releases")
|
Text("Showing last 3 releases")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
@@ -129,10 +115,6 @@ struct UpdateSettingsTab: View {
|
|||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.frame(width: 500, height: 520)
|
.frame(width: 500, height: 520)
|
||||||
// .onAppear {
|
|
||||||
// // Convert TimeInterval to Date on appearance
|
|
||||||
// let _ = Date(timeIntervalSinceReferenceDate: nextUpdateDate)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ struct AppListItems: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if bundleSize == 0 {
|
if bundleSize == 0 {
|
||||||
ProgressView().controlSize(.mini)
|
ProgressView().controlSize(.mini).padding(.leading, 5)
|
||||||
} else {
|
} else {
|
||||||
Text("\(isHovered ? "v\(appInfo.appVersion)" : formatByte(size: bundleSize).human)")
|
Text("\(isHovered ? "v\(appInfo.appVersion)" : formatByte(size: bundleSize).human)")
|
||||||
.font(.system(size: (isHovered || isSelected) ? 12 : 10))
|
.font(.system(size: (isHovered || isSelected) ? 12 : 10))
|
||||||
|
|||||||
@@ -107,3 +107,105 @@ extension View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// SegmentedPicker(
|
||||||
|
// ["Alpha", "Size"],
|
||||||
|
// selectedIndex: Binding(
|
||||||
|
// get: { selectedSortAlpha ? 0 : 1 },
|
||||||
|
// set: { newIndex in
|
||||||
|
// withAnimation(.easeInOut(duration: 0.3)) {
|
||||||
|
// selectedSortAlpha = (newIndex == 0)
|
||||||
|
// }
|
||||||
|
// }),
|
||||||
|
// selectionAlignment: .bottom,
|
||||||
|
// content: { item, isSelected in
|
||||||
|
// Text(item)
|
||||||
|
// .font(.callout)
|
||||||
|
// .foregroundColor(isSelected ? Color("mode") : Color("mode").opacity(0.5))
|
||||||
|
// .padding(.horizontal)
|
||||||
|
// .padding(.bottom, 5)
|
||||||
|
// .frame(width: 75)
|
||||||
|
//
|
||||||
|
// },
|
||||||
|
// selection: {
|
||||||
|
// VStack(spacing: 0) {
|
||||||
|
// Spacer()
|
||||||
|
// Color("pear").frame(height: 1)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
// SegmentedPicker(
|
||||||
|
// ["Real", "Logical", "Finder"],
|
||||||
|
// selectedIndex: Binding(
|
||||||
|
// get: {
|
||||||
|
// switch sizeType {
|
||||||
|
// case "Real": return 0
|
||||||
|
// case "Logical": return 1
|
||||||
|
// case "Finder": return 2
|
||||||
|
// default: return 0
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// set: { newIndex in
|
||||||
|
// withAnimation(.easeInOut(duration: 0.3)) {
|
||||||
|
// switch newIndex {
|
||||||
|
// case 0: sizeType = "Real"
|
||||||
|
// case 1: sizeType = "Logical"
|
||||||
|
// case 2: sizeType = "Finder"
|
||||||
|
// default: sizeType = "Real"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }),
|
||||||
|
// selectionAlignment: .bottom,
|
||||||
|
// content: { item, isSelected in
|
||||||
|
// Text(item)
|
||||||
|
// .font(.callout)
|
||||||
|
// .foregroundColor(isSelected ? Color("mode") : Color("mode").opacity(0.5) )
|
||||||
|
// .padding(.horizontal)
|
||||||
|
// .padding(.bottom, 5)
|
||||||
|
// .frame(width: 75)
|
||||||
|
// },
|
||||||
|
// selection: {
|
||||||
|
// VStack(spacing: 0) {
|
||||||
|
// Spacer()
|
||||||
|
// Color("pear").frame(height: 1)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// SegmentedPicker(
|
||||||
|
// ["Auto", "Dark", "Light"],
|
||||||
|
// selectedIndex: Binding(
|
||||||
|
// get: {
|
||||||
|
// switch selectedTheme {
|
||||||
|
// case "Dark": return 1
|
||||||
|
// case "Light": return 2
|
||||||
|
// default: return 0
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// set: { newIndex in
|
||||||
|
// withAnimation(.easeInOut(duration: 0.3)) {
|
||||||
|
// switch newIndex {
|
||||||
|
// case 1: selectedTheme = "Dark"
|
||||||
|
// case 2: selectedTheme = "Light"
|
||||||
|
// default: selectedTheme = "Auto"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }),
|
||||||
|
// selectionAlignment: .bottom,
|
||||||
|
// content: { item, isSelected in
|
||||||
|
// Text(item)
|
||||||
|
// .font(.callout)
|
||||||
|
// .foregroundColor(isSelected ? Color("mode") : Color("mode").opacity(0.5) )
|
||||||
|
// .padding(.horizontal)
|
||||||
|
// .padding(.bottom, 5)
|
||||||
|
// .frame(width: 65)
|
||||||
|
// },
|
||||||
|
// selection: {
|
||||||
|
// VStack(spacing: 0) {
|
||||||
|
// Spacer()
|
||||||
|
// Color("pear").frame(height: 1)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|||||||
Reference in New Issue
Block a user