This commit is contained in:
Alin
2024-06-13 18:14:26 -06:00
parent 72756ad196
commit 8a4a863015
10 changed files with 294 additions and 174 deletions
+4 -4
View File
@@ -568,8 +568,8 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APP_BUILD = 46;
APP_VERSION = 3.7.1;
APP_BUILD = 47;
APP_VERSION = 3.7.2;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -638,8 +638,8 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APP_BUILD = 46;
APP_VERSION = 3.7.1;
APP_BUILD = 47;
APP_VERSION = 3.7.2;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+27 -1
View File
@@ -928,7 +928,7 @@ extension View {
}
}
// Background color/glass setter
@ViewBuilder
func backgroundView(themeSettings: ThemeSettings, darker: Bool = false, glass: Bool = false) -> some View {
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 {
var fillColor: Color
var label: String
+90 -17
View File
@@ -170,35 +170,62 @@ func UnzipAndReplace(DownloadedFileURL fileURL: String, appState: AppState) {
// --- Updater check frequency
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
enum UpdateFrequency: String, CaseIterable, Identifiable {
case none = "Never"
case daily = "Daily"
case weekly = "Weekly"
case monthly = "Monthly"
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) {
@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
let updateSeconds = updateTimeframe.daysToSeconds
guard let updateInterval = updateFrequency.interval else {
printOS("Updater: no update frequency set, skipping check")
return
}
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")
printOS("Updater: next update date is in the future, skipping (\(nextUpdateDateLocal))")
return
}
// Update immediately and set next update date
updateApp(appState: appState)
setNextUpdateDate(interval: updateSeconds)
setNextUpdateDate(interval: updateInterval)
}
func updateApp(appState: AppState) {
@@ -210,13 +237,59 @@ func updateApp(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)
}
//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
+9 -8
View File
@@ -15,8 +15,7 @@ struct PearcleanerApp: App {
@StateObject var locations = Locations()
@StateObject var fsm = FolderSettingsManager()
@State private var windowSettings = WindowSettings()
// @AppStorage("settings.updater.updateTimeframe") private var updateTimeframe: Int = 1
@AppStorage("settings.updater.enableUpdates") private var enableUpdates: Bool = true
@AppStorage("settings.updater.updateFrequency") private var updateFrequency: UpdateFrequency = .daily
@AppStorage("settings.permissions.hasLaunched") private var hasLaunched: Bool = false
@AppStorage("displayMode") var displayMode: DisplayMode = .system
@AppStorage("settings.general.mini") private var mini: Bool = false
@@ -121,7 +120,7 @@ struct PearcleanerApp: App {
// Get GH releases
loadGithubReleases(appState: appState, releaseOnly: true)
if enableUpdates {
if updateFrequency != .none {
// Update checker
checkAndUpdateIfNeeded(appState: appState)
}
@@ -181,10 +180,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
findAndSetWindowFrame(named: ["Pearcleaner"], windowSettings: windowSettings)
if UserDefaults.standard.object(forKey: "themeColor") == nil {
self.appearanceChanged()
}
// if UserDefaults.standard.object(forKey: "themeColor") == nil {
// self.appearanceChanged()
// }
self.appearanceCheck()
if menubarEnabled {
findAndHideWindows(named: ["Pearcleaner"])
NSApplication.shared.setActivationPolicy(.accessory)
@@ -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
let themeMode = UserDefaults.standard.string(forKey: "settings.general.selectedTheme")
if themeMode == "Auto" {
self?.appearanceChanged()
self?.appearanceCheck()
}
}
}
func appearanceChanged() {
func appearanceCheck() {
let dm = UserDefaults.standard.integer(forKey: "displayMode")
var displayMode = DisplayMode(rawValue: dm)
let dark = isDarkMode()
+5 -2
View File
@@ -20,6 +20,7 @@ struct FolderSettingsTab: View {
@EnvironmentObject var appState: AppState
@EnvironmentObject var locations: Locations
@EnvironmentObject var fsm: FolderSettingsManager
@EnvironmentObject var themeSettings: ThemeSettings
@State private var isHovered = false
var body: some View {
@@ -81,7 +82,8 @@ struct FolderSettingsTab: View {
}
.scrollIndicators(.automatic)
.padding()
.background(Color("mode").opacity(0.05))
// .background(Color("mode").opacity(0.05))
.background(backgroundView(themeSettings: themeSettings, darker: true))
.clipShape(RoundedRectangle(cornerRadius: 10))
.onDrop(of: ["public.file-url"], isTargeted: nil) { providers -> Bool in
providers.forEach { provider in
@@ -173,7 +175,8 @@ struct FolderSettingsTab: View {
}
.scrollIndicators(.automatic)
.padding()
.background(Color("mode").opacity(0.05))
.background(backgroundView(themeSettings: themeSettings, darker: true))
// .background(Color("mode").opacity(0.05))
.clipShape(RoundedRectangle(cornerRadius: 10))
.onDrop(of: ["public.file-url"], isTargeted: nil) { providers -> Bool in
providers.forEach { provider in
+25 -67
View File
@@ -13,6 +13,7 @@ import FinderSync
struct GeneralSettingsTab: View {
@EnvironmentObject var appState: AppState
@EnvironmentObject var locations: Locations
@EnvironmentObject var themeSettings: ThemeSettings
@State private var windowSettings = WindowSettings()
@AppStorage("settings.general.glass") private var glass: Bool = true
@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)")
Spacer()
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)
}
})
Picker("", selection: $selectedSortAlpha) {
Text("Alpha")
.tag(true)
Text("Size")
.tag(false)
}
.pickerStyle(themeSettings: themeSettings)
}
.padding(5)
.padding(.leading)
@@ -145,48 +128,23 @@ struct GeneralSettingsTab: View {
.padding(.trailing)
.foregroundStyle(Color("mode").opacity(0.5))
VStack(alignment: .leading, spacing: 5) {
Text("File size display")
Text("File size display mode")
.font(.callout)
.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.")
Spacer()
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)
}
})
Picker("", selection: $sizeType) {
Text("Real")
.tag("Real")
Text("Logical")
.tag("Logical")
Text("Finder")
.tag("Finder")
}
.pickerStyle(themeSettings: themeSettings)
}
.padding(5)
.padding(.leading)
@@ -237,7 +195,7 @@ struct GeneralSettingsTab: View {
.frame(width: 20, height: 20)
.padding(.trailing)
.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")
.font(.callout)
.foregroundStyle(Color("mode").opacity(0.5))
@@ -265,7 +223,7 @@ struct GeneralSettingsTab: View {
.frame(width: 20, height: 20)
.padding(.trailing)
.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")
.font(.callout)
.foregroundStyle(Color("mode").opacity(0.5))
@@ -292,7 +250,7 @@ struct GeneralSettingsTab: View {
.frame(width: 20, height: 20)
.padding(.trailing)
.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")
.font(.callout)
.foregroundStyle(Color("mode").opacity(0.5))
@@ -330,7 +288,7 @@ struct GeneralSettingsTab: View {
.frame(width: 20, height: 20)
.padding(.trailing)
.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")
.font(.callout)
.foregroundStyle(Color("mode").opacity(0.5))
@@ -371,7 +329,7 @@ struct GeneralSettingsTab: View {
.frame(width: 20, height: 20)
.padding(.trailing)
.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")
.font(.callout)
.foregroundStyle(Color("mode").opacity(0.5))
+9 -34
View File
@@ -193,40 +193,15 @@ struct InterfaceSettingsTab: View {
}
InfoButton(text: "Changing the color mode will reset the base color to defaults")
Spacer()
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)
}
})
Picker("", selection: $selectedTheme) {
Text("Auto")
.tag("Auto")
Text("Dark")
.tag("Dark")
Text("Light")
.tag("Light")
}
.pickerStyle(themeSettings: themeSettings)
.onChange(of: selectedTheme) { newTheme in
switch newTheme {
case "Auto":
+22 -40
View File
@@ -11,44 +11,36 @@ import Foundation
struct UpdateSettingsTab: View {
@EnvironmentObject var appState: AppState
@EnvironmentObject var themeSettings: ThemeSettings
@State private var showAlert = false
@State private var showDone = false
@AppStorage("settings.updater.nextUpdateDate") private var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
@AppStorage("settings.updater.updateTimeframe") private var updateTimeframe: Int = 1
@AppStorage("settings.updater.enableUpdates") private var enableUpdates: Bool = true
@AppStorage("settings.updater.updateFrequency") private var updateFrequency: UpdateFrequency = .daily
var body: some View {
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 {
HStack(spacing: 0) {
Text("\(enableUpdates ? "Pearcleaner will check for updates every " : "Automatic updates are disabled")")
Text("Pearcleaner will check for updates")
.font(.callout)
.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()
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 {
Text("Next update check: \(formattedDate(Date(timeIntervalSinceReferenceDate: nextUpdateDate)))")
.font(.footnote)
@@ -56,18 +48,9 @@ struct UpdateSettingsTab: View {
Spacer()
}
}
}
Spacer()
Toggle(isOn: $enableUpdates, label: {
})
.toggleStyle(.switch)
}
.padding(5)
.padding(.leading)
.padding(5)
.padding(.horizontal)
ScrollView {
VStack() {
@@ -83,8 +66,11 @@ struct UpdateSettingsTab: View {
}
.frame(minHeight: 0, maxHeight: .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")
.font(.callout)
.foregroundStyle(Color("mode").opacity(0.5))
@@ -129,10 +115,6 @@ struct UpdateSettingsTab: View {
}
.padding(20)
.frame(width: 500, height: 520)
// .onAppear {
// // Convert TimeInterval to Date on appearance
// let _ = Date(timeIntervalSinceReferenceDate: nextUpdateDate)
// }
}
}
+1 -1
View File
@@ -79,7 +79,7 @@ struct AppListItems: View {
}
if bundleSize == 0 {
ProgressView().controlSize(.mini)
ProgressView().controlSize(.mini).padding(.leading, 5)
} else {
Text("\(isHovered ? "v\(appInfo.appVersion)" : formatByte(size: bundleSize).human)")
.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)
// }
// })